diff --git a/client/src/locales/en.json b/client/src/locales/en.json index bd2395f..05f7e4a 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -412,6 +412,7 @@ "minShort": "min", "position": "Position", "nextLabel": "NEXT", + "practitionerFallback": "Practitioner", "ticker": "✨ Welcome to {{clinic}} — Scan the QR code at reception to join the online queue — Track your position in real time on your phone — You'll be notified when your turn approaches" }, "analytics": { @@ -440,13 +441,21 @@ "kpiAbsent": "No-shows", "kpiAvgWait": "Avg wait", "kpiAvgConsultation": "Avg cons.", + "kpiNoShowRate": "No-show rate", + "kpiPeakHour": "Peak hour", + "kpiBusiestDay": "Busiest day", "flowJoined": "Joined", "flowServed": "Served", "flowAbsent": "No-shows", + "noShowServed": "Served", + "noShowAbsent": "No-shows", "chartByHour": "Patient flow by hour", "chartByDay": "Patient flow by day", "chartFlow": "Patient flow", "chartAvgWait": "Average wait time", + "chartWaitTrend": "Average wait per day", + "chartNoShow": "Attendance rate", + "noTrendData": "Not enough data for trend yet", "peakHour": "Peak hour:", "peakDay": "Busiest day:", "minutesOnAverage": "minutes on average", diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json index 362a98f..0ec76f0 100644 --- a/client/src/locales/fr.json +++ b/client/src/locales/fr.json @@ -412,6 +412,7 @@ "minShort": "min", "position": "Position", "nextLabel": "SUIVANT", + "practitionerFallback": "Praticien", "ticker": "✨ Bienvenue au {{clinic}} — Scannez le QR code à l'accueil pour rejoindre la file en ligne — Suivez votre position en temps réel sur votre téléphone — Vous serez notifié quand votre tour approche" }, "analytics": { @@ -440,13 +441,21 @@ "kpiAbsent": "Absents", "kpiAvgWait": "Attente moy.", "kpiAvgConsultation": "Cons. moy.", + "kpiNoShowRate": "Taux d'absence", + "kpiPeakHour": "Heure de pointe", + "kpiBusiestDay": "Jour le plus chargé", "flowJoined": "Joints", "flowServed": "Servis", "flowAbsent": "Absents", + "noShowServed": "Servis", + "noShowAbsent": "Absents", "chartByHour": "Affluence par heure", "chartByDay": "Affluence par jour", "chartFlow": "Flux patients", "chartAvgWait": "Temps d'attente moyen", + "chartWaitTrend": "Attente moyenne par jour", + "chartNoShow": "Taux de présence", + "noTrendData": "Pas encore de données pour la tendance", "peakHour": "Pic d'affluence :", "peakDay": "Jour le plus chargé :", "minutesOnAverage": "minutes en moyenne", diff --git a/client/src/pages/Analytics.tsx b/client/src/pages/Analytics.tsx index c2f4098..c37fa54 100644 --- a/client/src/pages/Analytics.tsx +++ b/client/src/pages/Analytics.tsx @@ -7,7 +7,7 @@ import { } from "lucide-react"; import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid, - AreaChart, Area, Cell, PieChart, Pie, + AreaChart, Area, Cell, PieChart, Pie, Legend, } from "recharts"; import { trpc } from "@/lib/trpc"; import { Button } from "@/components/ui/button"; @@ -32,9 +32,11 @@ export default function Analytics() { const clinicsQuery = trpc.clinic.list.useQuery(); const summaryQuery = trpc.analytics.summary.useQuery({ days, clinicId }); + const advancedQuery = trpc.analytics.getAdvanced.useQuery({ days, clinicId }); const clinics = clinicsQuery.data ?? []; const summary = summaryQuery.data; + const advanced = advancedQuery.data; const exportCsv = trpc.analytics.exportCsv.useQuery( { clinicId: clinicId ?? clinics[0]?.id ?? 0, days }, @@ -63,15 +65,25 @@ export default function Analytics() { toast.success(t("analytics.toastExportSuccess")); }; - const hourData = (summary?.byHour ?? []).map((count, hour) => ({ hour: `${hour}${t("analytics.hourSuffix")}`, count })); + const hourSource = advanced?.byHour ?? summary?.byHour ?? []; + const hourData = hourSource.map((count, hour) => ({ hour: `${hour}${t("analytics.hourSuffix")}`, count })); const dayData = (summary?.byDay ?? []).map((count, dow) => ({ day: DAY_NAMES[dow], count })); - const flowData = [ - { name: t("analytics.flowJoined"), value: summary?.totalJoined ?? 0 }, - { name: t("analytics.flowServed"), value: summary?.totalServed ?? 0 }, - { name: t("analytics.flowAbsent"), value: summary?.totalAbsent ?? 0 }, + const noShowPct = Math.round((advanced?.noShowRate ?? 0) * 100); + const noShowData = [ + { name: t("analytics.noShowServed"), value: 100 - noShowPct }, + { name: t("analytics.noShowAbsent"), value: noShowPct }, ]; + const waitTrendData = (advanced?.avgWaitByDay ?? []).map((d) => ({ + date: d.date.slice(5), + avgWaitMinutes: d.avgWaitMinutes, + count: d.count, + })); + + const advancedPeakHour = advanced?.peakHour ?? -1; + const advancedBusiestDay = advanced?.busiestDayOfWeek ?? -1; + return (
- {t("analytics.peakHour")} {summary.peakHour}{t("analytics.hourSuffix")} + {t("analytics.peakHour")} {advancedPeakHour}{t("analytics.hourSuffix")}
)}{t("analytics.noTrendData")}
+ )} +- {t("analytics.peakDay")} {DAY_NAMES[summary.peakDay]} + {t("analytics.peakDay")} {DAY_NAMES[advancedBusiestDay]}
)}