Teren-app/resources/js/Pages/Dashboard/Partials/CompletedFieldJobsTrend.vue

174 lines
5.6 KiB
Vue

<script setup>
import { computed, ref, watch } from "vue";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/Components/ui/card";
import { VisAxis, VisGroupedBar, VisXYContainer } from "@unovis/vue";
import {
ChartAutoLegend,
ChartContainer,
ChartTooltipContent,
ChartCrosshair,
provideChartContext,
componentToString,
} from "@/Components/ui/chart";
import AppChartDisplay from "@/Components/app/ui/charts/AppChartDisplay.vue";
const props = defineProps({
trends: Object,
});
const chartData = computed(() => {
if (
!props.trends?.labels ||
!props.trends?.field_jobs_completed ||
!props.trends?.field_jobs
) {
return [];
}
return props.trends.labels.map((label, i) => ({
date: new Date(label),
dateLabel: label,
completed: props.trends.field_jobs_completed[i] || 0,
assigned: props.trends.field_jobs[i] || 0,
}));
});
const chartConfig = {
completed: {
label: "Zaključeni",
color: "var(--chart-1)",
},
assigned: {
label: "Dodeljeni",
color: "var(--chart-2)",
},
};
// Provide chart context at component root so legend (outside ChartContainer) can access it
provideChartContext(chartConfig);
// (No gradients needed for bar chart)
// Active series keys controlled by auto legend
const activeKeys = ref(["completed", "assigned"]);
const activeSeries = computed(() => activeKeys.value);
const yAccessors = computed(() => activeSeries.value.map((key) => (d) => d[key]));
// Prevent all series from being disabled (Unovis crosshair needs at least one component with x accessor)
let _lastNonEmpty = [...activeKeys.value];
watch(activeKeys, (val, oldVal) => {
if (val.length === 0) {
// revert to previous non-empty selection
activeKeys.value = _lastNonEmpty.length ? _lastNonEmpty : oldVal;
} else {
_lastNonEmpty = [...val];
}
});
// Crosshair template using componentToString to render advanced tooltip component
const crosshairTemplate = componentToString(chartConfig, ChartTooltipContent, {
labelKey: "dateLabel",
labelFormatter: (x) => crosshairLabelFormatter(x),
});
const totalCompleted = computed(() => {
return chartData.value.reduce((sum, item) => sum + item.completed, 0);
});
const totalAssigned = computed(() => {
return chartData.value.reduce((sum, item) => sum + item.assigned, 0);
});
// Formatter for tooltip title (date) and potential item labels
const crosshairLabelFormatter = (value) => {
// Handle Date objects or parsable date strings
const date = value instanceof Date ? value : new Date(value);
if (isNaN(date)) return value?.toString?.() ?? "";
return date.toLocaleDateString("sl-SI", { month: "long", day: "numeric" });
};
</script>
<template>
<AppChartDisplay name="ChartLine" class="md:col-span-2 lg:col-span-3">
<Card class="p-0">
<CardHeader class="flex flex-col items-stretch border-b p-0! sm:flex-row">
<div class="flex flex-1 flex-col justify-center gap-1 px-6 py-5 sm:py-6">
<CardTitle>Terenska dela - Pregled</CardTitle>
<CardDescription>Zadnjih 7 dni</CardDescription>
</div>
<div class="flex">
<div
class="relative z-30 flex flex-1 flex-col justify-center gap-1 border-t px-6 py-4 text-left sm:border-l sm:border-t-0 sm:px-8 sm:py-6"
>
<span class="text-xs text-muted-foreground">Zaključeni</span>
<span class="text-lg font-bold leading-none sm:text-3xl">
{{ totalCompleted.toLocaleString() }}
</span>
</div>
<div
class="relative z-30 flex flex-1 flex-col justify-center gap-1 border-t px-6 py-4 text-left sm:border-l sm:border-t-0 sm:px-8 sm:py-6"
>
<span class="text-xs text-muted-foreground">Dodeljeni</span>
<span class="text-lg font-bold leading-none sm:text-3xl">
{{ totalAssigned.toLocaleString() }}
</span>
</div>
</div>
</CardHeader>
<CardContent class="px-2 sm:px-6 sm:pt-6 pb-4">
<div v-if="chartData.length" class="w-full aspect-auto h-[250px]">
<ChartContainer :config="chartConfig" class="h-full">
<VisXYContainer
:data="chartData"
:height="250"
:margin="{ left: 5, right: 5 }"
>
<VisGroupedBar
:x="(d) => d.date"
:y="yAccessors"
:color="(d, i) => chartConfig[activeSeries[i]].color"
:bar-padding="0.3"
/>
<VisAxis
type="x"
:tick-line="false"
:grid-line="false"
:num-ticks="7"
:tick-format="
(d) => {
const date = new Date(d);
return date.toLocaleDateString('sl-SI', {
month: 'short',
day: 'numeric',
});
}
"
/>
<VisAxis type="y" :num-ticks="4" :tick-line="false" :grid-line="true" />
<ChartCrosshair
:index="'date'"
:template="crosshairTemplate"
:colors="[chartConfig.completed.color, chartConfig.assigned.color]"
/>
</VisXYContainer>
</ChartContainer>
</div>
<div v-else class="h-[250px] animate-pulse bg-gray-100 rounded" />
</CardContent>
<div class="border-t px-6 py-2 flex justify-center">
<ChartAutoLegend
v-model:activeKeys="activeKeys"
:order="['completed', 'assigned']"
/>
</div>
</Card>
</AppChartDisplay>
</template>