Refactor InvoicingBillingPeriodStatistics.vue: add department totals calculation, enhance subscription and pricing views, improve font and color styling, and update conditional rendering logic.
This commit is contained in:
+161
-59
@@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from "vue";
|
||||
import {computed, ref, watch, onMounted} from "vue";
|
||||
import StatisticsIncomeCard
|
||||
from "@/views/dashboards/superUserDashboard/statistics/displays/overview/StatisticsIncomeCard.vue";
|
||||
import WhiteBoxCard from "@/components/displays/boxes/WhiteBoxCard.vue";
|
||||
import { dates } from "../../imports/InvoicingBillingPeriodImportDates.vue";
|
||||
import { view } from "../../imports/InvoicingBillingPeriodImportView.vue";
|
||||
import {dates} from "../../imports/InvoicingBillingPeriodImportDates.vue";
|
||||
import {view} from "../../imports/InvoicingBillingPeriodImportView.vue";
|
||||
import {departments} from "@/components/pagination/departmentTabs.vue";
|
||||
import {SessionUser} from "@/components/session/token/SessionUser.vue";
|
||||
|
||||
@@ -47,7 +47,7 @@ const getPercentageOfTotal = (value: number, total: number) => {
|
||||
watch(
|
||||
() => view.variables.currentView.value,
|
||||
(newView) => {
|
||||
if (newView === 'fixed_pricing') {
|
||||
if (['all', 'fixed_pricing'].includes(newView)) {
|
||||
fetchFixedPricingDistribution();
|
||||
}
|
||||
},
|
||||
@@ -57,7 +57,7 @@ watch(
|
||||
watch(
|
||||
() => [dates.variables.start.value, dates.variables.end.value],
|
||||
([newStart, newEnd]) => {
|
||||
if (view.variables.currentView.value === 'fixed_pricing') {
|
||||
if (['all', 'fixed_pricing'].includes(view.variables.currentView.value)) {
|
||||
clearFixedPricingDistribution();
|
||||
fetchFixedPricingDistribution();
|
||||
}
|
||||
@@ -96,60 +96,57 @@ const clearVehicleSubscriptionDistribution = () => {
|
||||
watch(
|
||||
() => view.variables.currentView.value,
|
||||
(newView) => {
|
||||
if (newView === 'vehicle_subscriptions') {
|
||||
if (['all', 'vehicle_subscriptions'].includes(newView)) {
|
||||
fetchVehicleSubscriptionDistribution();
|
||||
} else {
|
||||
clearVehicleSubscriptionDistribution();
|
||||
}
|
||||
},
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
watch(
|
||||
() => [dates.variables.start.value, dates.variables.end.value],
|
||||
([newStart, newEnd]) => {
|
||||
if (view.variables.currentView.value === 'vehicle_subscriptions') {
|
||||
if (['all', 'vehicle_subscriptions'].includes(view.variables.currentView.value)) {
|
||||
clearVehicleSubscriptionDistribution();
|
||||
fetchVehicleSubscriptionDistribution();
|
||||
}
|
||||
}
|
||||
);
|
||||
const department75_total = ref<number | null>(null);
|
||||
const calculate_department75_total = () => {
|
||||
if (fixed_pricing_department_distribution.value?.total_fixed_price !== undefined && vehicleSubscriptionDistribution.value?.total_subscription_price !== undefined) {
|
||||
return fixed_pricing_department_distribution.value?.total_fixed_price + vehicleSubscriptionDistribution.value?.total_subscription_price;
|
||||
}
|
||||
// If either is undefined, return undefined
|
||||
return null;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (['all', 'fixed_pricing'].includes(view.variables.currentView.value)) {
|
||||
fetchFixedPricingDistribution();
|
||||
}
|
||||
if (['all', 'vehicle_subscriptions'].includes(view.variables.currentView.value)) {
|
||||
fetchVehicleSubscriptionDistribution();
|
||||
}
|
||||
department75_total.value = calculate_department75_total();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [fixed_pricing_department_distribution.value, vehicleSubscriptionDistribution.value],
|
||||
() => {
|
||||
department75_total.value = calculate_department75_total();
|
||||
if (department75_total.value === null) {
|
||||
console.warn('Department 75 total calculation resulted in null');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Booked, not booked and total income -->
|
||||
<div class="columns is-multiline is-mobile">
|
||||
<div class="column is-one-third">
|
||||
<WhiteBoxCard :has-selection-style="true" :has-hover-effect="true" :has-border="true">
|
||||
<!-- Income booked -->
|
||||
<StatisticsIncomeCard
|
||||
v-bind:force-display="{
|
||||
value: view.computed.currentViewTotalNetAmountBooked.value,
|
||||
start_date: dates.variables.start.value.toISOString().split('T')[0],
|
||||
end_date: dates.variables.end.value.toISOString().split('T')[0],
|
||||
error: null,
|
||||
}"
|
||||
v-bind:icon="'fas fa-money-bill-wave'"
|
||||
v-bind:name="'Bogført'"
|
||||
v-bind:color="'has-text-success'"
|
||||
/>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
<div class="column is-one-third">
|
||||
<WhiteBoxCard :has-selection-style="true" :has-hover-effect="true" :has-border="true">
|
||||
<!-- Income not booked -->
|
||||
<StatisticsIncomeCard
|
||||
v-bind:force-display="{
|
||||
value: view.computed.currentViewTotalNetAmountNotBooked.value,
|
||||
start_date: dates.variables.start.value.toISOString().split('T')[0],
|
||||
end_date: dates.variables.end.value.toISOString().split('T')[0],
|
||||
error: null,
|
||||
}"
|
||||
v-bind:icon="'fas fa-money-bill-wave'"
|
||||
v-bind:name="'Ikke bogført'"
|
||||
/>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
<div class="column is-one-third">
|
||||
<WhiteBoxCard :has-selection-style="true" :has-hover-effect="true" :has-border="true">
|
||||
<!-- Income total -->
|
||||
@@ -165,30 +162,114 @@ watch(
|
||||
/>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
<div class="column is-one-third">
|
||||
<WhiteBoxCard :has-selection-style="true" :has-hover-effect="true" :has-border="true">
|
||||
<!-- Income booked -->
|
||||
<StatisticsIncomeCard
|
||||
v-bind:force-display="{
|
||||
value: view.computed.currentViewTotalNetAmountBooked.value,
|
||||
start_date: dates.variables.start.value.toISOString().split('T')[0],
|
||||
end_date: dates.variables.end.value.toISOString().split('T')[0],
|
||||
error: null,
|
||||
}"
|
||||
v-bind:icon="'fas fa-money-bill-wave'"
|
||||
v-bind:name="'Bogført'"
|
||||
v-bind:color="'has-text-success'"
|
||||
v-bind:font-style="'oblique'"
|
||||
/>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
<div class="column is-one-third">
|
||||
<WhiteBoxCard :has-selection-style="true" :has-hover-effect="true" :has-border="true">
|
||||
<!-- Income not booked -->
|
||||
<StatisticsIncomeCard
|
||||
v-bind:force-display="{
|
||||
value: view.computed.currentViewTotalNetAmountNotBooked.value,
|
||||
start_date: dates.variables.start.value.toISOString().split('T')[0],
|
||||
end_date: dates.variables.end.value.toISOString().split('T')[0],
|
||||
error: null,
|
||||
}"
|
||||
v-bind:icon="'fas fa-money-bill-wave'"
|
||||
v-bind:name="'Ikke bogført'"
|
||||
v-bind:color="(view.computed.currentViewTotalNetAmountNotBooked.value > 0) ? 'danger' : 'black'"
|
||||
v-bind:font-style="'oblique'"
|
||||
/>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
|
||||
<!-- If an entire month is selected, show overall distribution -->
|
||||
<template v-if="dates.computed.isEntireMonth.value">
|
||||
<!-- Divider -->
|
||||
<div class="column is-full">
|
||||
<div class="divider">
|
||||
<div class="divider-text has-text-weight-bold">
|
||||
Heraf afdeling 75
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Department 75 -->
|
||||
<div class="column is-one-third">
|
||||
<WhiteBoxCard :has-selection-style="true" :has-hover-effect="true" :has-border="true">
|
||||
<StatisticsIncomeCard v-bind:force-display="{
|
||||
value: department75_total || 0,
|
||||
start_date: dates.variables.start.value.toISOString().split('T')[0],
|
||||
end_date: dates.variables.end.value.toISOString().split('T')[0],
|
||||
error: null,
|
||||
}"
|
||||
v-bind:loading="department75_total === null"
|
||||
v-bind:icon="'fas fa-money-bill-wave'"
|
||||
v-bind:name="'Afdeling 75'"
|
||||
v-bind:color="'black'"
|
||||
v-bind:font-style="'normal'"
|
||||
/>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
<!-- Wash subscriptions -->
|
||||
<div class="column is-one-third">
|
||||
<WhiteBoxCard :has-selection-style="true" :has-hover-effect="true" :has-border="true">
|
||||
<StatisticsIncomeCard v-bind:force-display="{
|
||||
value: vehicleSubscriptionDistribution?.total_subscription_price || 0,
|
||||
start_date: dates.variables.start.value.toISOString().split('T')[0],
|
||||
end_date: dates.variables.end.value.toISOString().split('T')[0],
|
||||
error: null,
|
||||
}"
|
||||
v-bind:loading="vehicleSubscriptionDistribution?.total_subscription_price === undefined"
|
||||
v-bind:icon="'fas fa-money-bill-wave'"
|
||||
v-bind:name="'Vaske-aftale'"
|
||||
v-bind:color="'black'"
|
||||
v-bind:font-style="'oblique'"
|
||||
/>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
<!-- Fixed pricing -->
|
||||
<div class="column is-one-third">
|
||||
<WhiteBoxCard :has-selection-style="true" :has-hover-effect="true" :has-border="true">
|
||||
<StatisticsIncomeCard v-bind:force-display="{
|
||||
value: fixed_pricing_department_distribution?.total_fixed_price || 0,
|
||||
start_date: dates.variables.start.value.toISOString().split('T')[0],
|
||||
end_date: dates.variables.end.value.toISOString().split('T')[0],
|
||||
error: null,
|
||||
}"
|
||||
v-bind:loading="fixed_pricing_department_distribution?.total_fixed_price === undefined"
|
||||
v-bind:icon="'fas fa-money-bill-wave'"
|
||||
v-bind:name="'Fast-pris'"
|
||||
v-bind:color="'black'"
|
||||
v-bind:font-style="'oblique'"
|
||||
/>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<template v-if="view.functions.isCurrentView('fixed_pricing')">
|
||||
<!-- Divider -->
|
||||
<div class="column is-full">
|
||||
<div class="divider">
|
||||
<div class="divider-text has-text-weight-bold">
|
||||
Fast-pris fordeling
|
||||
Afdeling 75 - Fast-pris fordeling
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Total original price -->
|
||||
<div class="column is-one-third">
|
||||
<WhiteBoxCard :has-selection-style="true" :has-hover-effect="true" :has-border="true">
|
||||
<StatisticsIncomeCard v-bind:force-display="{
|
||||
value: fixed_pricing_department_distribution?.total_original_price || 0,
|
||||
start_date: dates.variables.start.value.toISOString().split('T')[0],
|
||||
end_date: dates.variables.end.value.toISOString().split('T')[0],
|
||||
error: null,
|
||||
}"
|
||||
v-bind:loading="fixed_pricing_department_distribution?.total_original_price === undefined"
|
||||
v-bind:icon="'fas fa-money-bill-wave'"
|
||||
v-bind:name="'Før pris'"
|
||||
/>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
<!-- Total fixed price -->
|
||||
<div class="column is-one-third">
|
||||
<WhiteBoxCard :has-selection-style="true" :has-hover-effect="true" :has-border="true">
|
||||
@@ -202,7 +283,25 @@ watch(
|
||||
v-bind:loading="fixed_pricing_department_distribution?.total_fixed_price === undefined"
|
||||
v-bind:icon="'fas fa-money-bill-wave'"
|
||||
v-bind:name="'Faktisk pris'"
|
||||
v-bind:color="'has-text-info'"
|
||||
v-bind:color="'black'"
|
||||
/>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
<!-- Total original price -->
|
||||
<div class="column is-one-third">
|
||||
<WhiteBoxCard :has-selection-style="true" :has-hover-effect="true" :has-border="true">
|
||||
<StatisticsIncomeCard v-bind:force-display="{
|
||||
value: fixed_pricing_department_distribution?.total_original_price || 0,
|
||||
start_date: dates.variables.start.value.toISOString().split('T')[0],
|
||||
end_date: dates.variables.end.value.toISOString().split('T')[0],
|
||||
error: null,
|
||||
}"
|
||||
v-bind:loading="fixed_pricing_department_distribution?.total_original_price === undefined"
|
||||
v-bind:icon="'fas fa-money-bill-wave'"
|
||||
v-bind:name="'Før pris'"
|
||||
v-bind:color="'black'"
|
||||
v-bind:font-style="'oblique'"
|
||||
|
||||
/>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
@@ -219,7 +318,8 @@ watch(
|
||||
v-bind:loading="fixed_pricing_department_distribution?.total_fixed_price === undefined || fixed_pricing_department_distribution?.total_original_price === undefined"
|
||||
v-bind:icon="((fixed_pricing_department_distribution?.total_fixed_price || 0) - (fixed_pricing_department_distribution?.total_original_price || 0)) >= 0 ? 'fas fa-arrow-up' : 'fas fa-arrow-down'"
|
||||
v-bind:name="'Difference'"
|
||||
v-bind:color="((fixed_pricing_department_distribution?.total_fixed_price || 0) - (fixed_pricing_department_distribution?.total_original_price || 0)) >= 0 ? 'has-text-success' : 'has-text-danger'"
|
||||
v-bind:color="((fixed_pricing_department_distribution?.total_fixed_price || 0) - (fixed_pricing_department_distribution?.total_original_price || 0)) >= 0 ? 'black' : 'danger'"
|
||||
v-bind:font-style="'oblique'"
|
||||
/>
|
||||
</WhiteBoxCard>
|
||||
</div>
|
||||
@@ -255,7 +355,7 @@ watch(
|
||||
<!-- Total row -->
|
||||
<tr v-if="fixed_pricing_department_distribution && fixed_pricing_department_distribution.total_original_price !== undefined && fixed_pricing_department_distribution.total_fixed_price !== undefined">
|
||||
<td><strong>Total</strong></td>
|
||||
<td><strong>{{ SessionUser.functions.currency.toLocal(fixed_pricing_department_distribution.total_original_price) }}</strong></td>
|
||||
<td><strong class="has-text-strikethrough">{{ SessionUser.functions.currency.toLocal(fixed_pricing_department_distribution.total_original_price) }}</strong></td>
|
||||
<td><strong>{{ SessionUser.functions.currency.toLocal(fixed_pricing_department_distribution.total_fixed_price) }}</strong></td>
|
||||
<td><strong>{{ getPercentageOfTotal(fixed_pricing_department_distribution.total_fixed_price, fixed_pricing_department_distribution.total_original_price) }}%</strong></td>
|
||||
</tr>
|
||||
@@ -349,5 +449,7 @@ watch(
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.has-text-strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
</style>
|
||||
+49
-13
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { onMounted, watch} from 'vue'
|
||||
import {computed, onMounted, watch} from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import {authenticatedRequest} from "@/components/session/authenticatedRequest.vue";
|
||||
const props = defineProps({
|
||||
@@ -13,7 +13,13 @@ const props = defineProps({
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'black'
|
||||
default: 'black',
|
||||
enum: ['black', 'info', 'success', 'warning', 'danger'],
|
||||
},
|
||||
fontStyle: {
|
||||
type: String,
|
||||
default: 'normal',
|
||||
enum: ['normal', 'italic', 'oblique', 'strikethrough'],
|
||||
},
|
||||
endpoint: {
|
||||
type: String,
|
||||
@@ -97,8 +103,30 @@ watch(() => props.forceDisplay, (newValue) => {
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
const cardClass = ref(props.forceDisplay.card_class || 'card')
|
||||
const fontStyleClass = computed(() => {
|
||||
switch (props.fontStyle.toLowerCase()) {
|
||||
case 'italic':
|
||||
return 'is-italic'
|
||||
case 'oblique':
|
||||
return 'is-oblique'
|
||||
case 'strikethrough':
|
||||
return 'has-text-decoration-line-through'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
const cardClass = ref(props.forceDisplay.card_class || 'card')
|
||||
// Color class nuances (title, subtitle, icon)
|
||||
const colorClassNuances = computed(() => {
|
||||
return {
|
||||
title: [`has-text-${props.color}`, 'is-size-5', 'has-text-weight-bold'],
|
||||
subtitle: [`has-text-${props.color}-light`, 'is-size-6', 'has-text-weight-semibold', fontStyleClass.value],
|
||||
icon: [`has-text-${props.color}`, 'is-size-4'],
|
||||
footer: [`has-text-${props.color}`, 'is-size-7', 'has-text-weight-semibold'],
|
||||
footerIcon: [`has-text-${props.color}`, 'is-size-7'],
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -107,16 +135,16 @@ const cardClass = ref(props.forceDisplay.card_class || 'card')
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<i :class="icon" :style="{ color: props.color }" v-if="value !== null && !props.loading"></i>
|
||||
<i :class="[icon, colorClassNuances.icon]" v-if="value !== null && !props.loading"></i>
|
||||
<i class="fas fa-exclamation-triangle has-text-warning" v-else-if="error"></i>
|
||||
<i class="fas fa-spinner fa-spin" v-else></i>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-5">{{ name }}</p>
|
||||
<p class="subtitle is-6" v-if="value !== null && !props.loading">{{ renderCurrency(value) }}</p>
|
||||
<p class="subtitle is-6" v-else-if="error">{{ error }}</p>
|
||||
<p class="subtitle is-6" v-else>{{ $t('common.loading') }}</p>
|
||||
<p class="title is-5" :class="colorClassNuances.title">{{ name }}</p>
|
||||
<p class="subtitle is-6" :class="colorClassNuances.subtitle" v-if="value !== null && !props.loading">{{ renderCurrency(value) }}</p>
|
||||
<p class="subtitle is-6" :class="colorClassNuances.subtitle" v-else-if="error">{{ error }}</p>
|
||||
<p class="subtitle is-6" :class="colorClassNuances.subtitle" v-else>{{ $t('common.loading') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -124,20 +152,20 @@ const cardClass = ref(props.forceDisplay.card_class || 'card')
|
||||
<p class="card-footer-item">
|
||||
<span>
|
||||
<span class="icon" v-if="start_date && end_date">
|
||||
<i class="fas fa-calendar-day"></i>
|
||||
<i class="fas fa-calendar-day" :class="colorClassNuances.footerIcon"></i>
|
||||
</span>
|
||||
<span v-if="start_date && end_date" class="is-size-7 has-text-weight-bold">
|
||||
<span v-if="start_date && end_date" :class="colorClassNuances.footer">
|
||||
{{ start_date }} - {{ end_date }} ({{ getDaysBetweenDates(start_date, end_date) === 0 ? '1 ' + $t('statistics.day') : getDaysBetweenDates(start_date, end_date) + ' ' + $t('statistics.days') }})
|
||||
</span>
|
||||
<span v-else-if="error">
|
||||
<span class="icon">
|
||||
<i class="fas fa-exclamation-triangle has-text-warning"></i>
|
||||
<i class="fas fa-exclamation-triangle has-text-warning" :class="colorClassNuances.footerIcon"></i>
|
||||
</span>
|
||||
<span>{{ $t('common.error') }}</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
<span class="icon">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
<i class="fas fa-spinner fa-spin" :class="colorClassNuances.footerIcon"></i>
|
||||
</span>
|
||||
<span>{{ $t('common.loading') }}</span>
|
||||
</span>
|
||||
@@ -148,5 +176,13 @@ const cardClass = ref(props.forceDisplay.card_class || 'card')
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.has-text-decoration-line-through {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.is-oblique {
|
||||
font-style: oblique;
|
||||
}
|
||||
.is-italic {
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user