"use client"; import { type ReactNode, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { LiveHospitalChart } from "@/components/analysis/live-hospital-chart"; import { Area, AreaChart } from "@/components/charts/area-chart"; import { Bar } from "@/components/charts/bar"; import { BarChart } from "@/components/charts/bar-chart"; import { BarXAxis } from "@/components/charts/bar-x-axis"; import { BarYAxis } from "@/components/charts/bar-y-axis"; import { Grid } from "@/components/charts/grid"; import { ChartTooltip } from "@/components/charts/tooltip"; import { XAxis } from "@/components/charts/x-axis"; import { Card } from "@/components/ui/card"; import { type Analytics, getAnalytics } from "@/lib/analytics"; // Clinic analytics computed on the server from real data (patients, // appointments, prescriptions, tasks). No fabricated financials — temetro has no // billing data source. type Metric = { label: string; value: string }; function StatCard({ label, value }: Metric) { return ( {label}
{value}
); } // Each section's grid fills its row evenly: the column count matches the number // of cards so there's never an orphan card on its own row. Static class strings // (no interpolation) so Tailwind can see them. const GRID_BY_COLUMNS: Record<2 | 3 | 4, string> = { 2: "grid grid-cols-1 gap-4 sm:grid-cols-2", 3: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", 4: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", }; function Section({ title, description, columns, children, }: { title: string; description: string; columns: 2 | 3 | 4; children: ReactNode; }) { return (

{title}

{description}

{children}
); } // A titled card that frames a chart, used for the trend visualisations. function ChartCard({ title, total, children, }: { title: string; total: number; children: ReactNode; }) { return (
{title} {total}
{children}
); } export function AnalysisView() { const { t } = useTranslation(); const [data, setData] = useState(null); useEffect(() => { let active = true; getAnalytics() .then((a) => { if (active) setData(a); }) .catch(() => { /* api-client redirects on 401; otherwise leave it loading */ }); return () => { active = false; }; }, []); const n = (v: number | undefined) => String(v ?? 0); // The area chart needs real Date x-values: synthesise one month per point, // ending with the current month. const monthData = useMemo(() => { const points = data?.trends.patientsByMonth ?? []; const now = new Date(); return points.map((p, i) => ({ date: new Date( now.getFullYear(), now.getMonth() - (points.length - 1 - i), 1, ), patients: p.count, })); }, [data]); // The bar chart is categorical (one bar per weekday). const weekdayData = useMemo( () => (data?.trends.appointmentsByWeekday ?? []).map((p) => ({ name: p.label, appointments: p.count, })), [data], ); const monthTotal = monthData.reduce((sum, p) => sum + p.patients, 0); const weekdayTotal = weekdayData.reduce((sum, p) => sum + p.appointments, 0); return (

{t("analysis.title")}

{t("analysis.subtitle")}

{t("analysis.live.title")}

{t("analysis.live.subtitle")}

{t("analysis.live.label")}

{t("analysis.charts.title")}

{t("analysis.charts.subtitle")}

); }