Shitty gadgets removed. Some useful gadgets added.

This commit is contained in:
2026-07-11 03:12:56 +03:30
parent 2f8c9f0f4d
commit ed8ff61205
31 changed files with 1489 additions and 170 deletions

View File

@@ -1,14 +1,23 @@
'use client';
import { useMemo } from 'react';
import { useTranslations } from 'next-intl';
import { useAuth } from '@/lib/hooks/useAuth';
import {
canViewTasks,
canViewTreatment,
} from '@/components/shared/permissions';
import { ChartCard } from '@/components/today/ChartCard';
import { TodayAreaChart } from '@/components/today/TodayAreaChart';
import { TodayBarChart } from '@/components/today/TodayBarChart';
import {
formatTodayChartDayLabel,
mapWeekChartBuckets,
useTodayDayLabelFormatter,
} from '@/components/today/chart-day-labels';
import { TodayDonutChart } from '@/components/today/TodayDonutChart';
import { TodayHorizontalBarChart } from '@/components/today/TodayHorizontalBarChart';
import { TodayRadialGaugeChart } from '@/components/today/TodayRadialGaugeChart';
import { TodayStackedBarChart } from '@/components/today/TodayStackedBarChart';
import { ChartCardSkeleton } from '@/components/today/TodaySkeleton';
import { prosthesisTypeColor, prosthesisTypeSwatchStyle } from '@/components/ui/treatment/prosthesisTypeDisplay';
import { treatmentTypeColor } from '@/components/ui/treatment/treatmentTypeDisplay';
import type { TodaySummaryCharts } from '@/types/today';
interface TodayChartsSectionProps {
@@ -16,6 +25,8 @@ interface TodayChartsSectionProps {
loading?: boolean;
isInitialLoad?: boolean;
className?: string;
/** When true, chart cards render as siblings (no outer grid wrapper). */
embedded?: boolean;
}
export function TodayChartsSection({
@@ -23,38 +34,155 @@ export function TodayChartsSection({
loading = false,
isInitialLoad = false,
className = '',
embedded = false,
}: TodayChartsSectionProps) {
const t = useTranslations('today');
const dayLabelFormatter = useTodayDayLabelFormatter();
const { currentOrganization } = useAuth();
const orgType = currentOrganization?.type;
const showAppointmentsByProvider =
orgType === 'CLINIC' && charts.appointmentsByProvider !== undefined;
const showAppointmentsWeekAll =
orgType === 'CLINIC' && charts.appointmentsWeekAll !== undefined;
const showAppointmentsWeekMine =
orgType === 'CLINIC' && charts.appointmentsWeekMine !== undefined;
const showTreatmentMix =
orgType === 'CLINIC' && canViewTreatment(currentOrganization);
orgType === 'CLINIC' && charts.treatmentMixWeek !== undefined;
const showCaseCompletion =
orgType === 'LAB' && charts.caseCompletion !== undefined;
const showTasksByStep =
orgType === 'LAB' && canViewTasks(currentOrganization);
orgType === 'LAB' && charts.tasksByWorkflowStep !== undefined;
const showLabTaskActivityWeek =
orgType === 'LAB' && charts.labTaskActivityWeek !== undefined;
const showInProgressTasksByProsthesis =
orgType === 'LAB' && charts.inProgressTasksByProsthesis !== undefined;
if (!showTreatmentMix && !showTasksByStep) {
const visibleChartCount =
Number(showAppointmentsByProvider) +
Number(showAppointmentsWeekAll) +
Number(showAppointmentsWeekMine) +
Number(showTreatmentMix) +
Number(showCaseCompletion) +
Number(showTasksByStep) +
Number(showLabTaskActivityWeek) +
Number(showInProgressTasksByProsthesis);
const appointmentsByProviderData = charts.appointmentsByProvider ?? [];
const appointmentsWeekAllData = useMemo(
() => mapWeekChartBuckets(charts.appointmentsWeekAll ?? [], dayLabelFormatter),
[charts.appointmentsWeekAll, dayLabelFormatter],
);
const appointmentsWeekMineData = useMemo(
() => mapWeekChartBuckets(charts.appointmentsWeekMine ?? [], dayLabelFormatter),
[charts.appointmentsWeekMine, dayLabelFormatter],
);
const treatmentData = charts.treatmentMixWeek ?? [];
const tasksData = charts.tasksByWorkflowStep ?? [];
const labTaskActivityData = useMemo(
() => mapWeekChartBuckets(charts.labTaskActivityWeek ?? [], dayLabelFormatter),
[charts.labTaskActivityWeek, dayLabelFormatter],
);
const prosthesisData = charts.inProgressTasksByProsthesis ?? [];
const caseCompletion = charts.caseCompletion ?? { completed: 0, total: 0, percent: 0 };
const formatDayLabel = (code: string) =>
formatTodayChartDayLabel(code, dayLabelFormatter);
if (visibleChartCount === 0) {
return null;
}
const treatmentData = charts.treatmentMixWeek ?? [];
const tasksData = charts.tasksByWorkflowStep ?? [];
if (isInitialLoad) {
const skeletons = Array.from({ length: Math.min(visibleChartCount, 4) }).map((_, index) => (
<ChartCardSkeleton key={index} />
));
if (embedded) {
return <>{skeletons}</>;
}
return (
<div className={`grid grid-cols-1 gap-4 ${className}`}>
{showTreatmentMix ? <ChartCardSkeleton /> : null}
{showTasksByStep ? <ChartCardSkeleton /> : null}
</div>
<div className={`grid grid-cols-1 gap-4 lg:grid-cols-2 ${className}`}>{skeletons}</div>
);
}
const chartCount = (showTreatmentMix ? 1 : 0) + (showTasksByStep ? 1 : 0);
const gridClass =
visibleChartCount > 1 ? 'grid grid-cols-1 lg:grid-cols-2' : 'grid grid-cols-1';
const chartCards = (
<>
{showAppointmentsWeekAll ? (
<ChartCard
title={t('chartAppointmentsWeekAllTitle')}
subtitle={t('chartAppointmentsWeekAllSubtitle')}
isEmpty={appointmentsWeekAllData.every((row) => row.count === 0)}
emptyMessage={t('chartEmpty')}
>
<TodayAreaChart data={appointmentsWeekAllData} />
</ChartCard>
) : null}
{showAppointmentsWeekMine ? (
<ChartCard
title={t('chartAppointmentsWeekMineTitle')}
subtitle={t('chartAppointmentsWeekMineSubtitle')}
isEmpty={appointmentsWeekMineData.every((row) => row.count === 0)}
emptyMessage={t('chartEmpty')}
>
<TodayAreaChart data={appointmentsWeekMineData} />
</ChartCard>
) : null}
{showLabTaskActivityWeek ? (
<ChartCard
title={t('chartLabTaskActivityTitle')}
subtitle={t('chartLabTaskActivitySubtitle')}
isEmpty={labTaskActivityData.every(
(row) => row.completed === 0 && row.received === 0,
)}
emptyMessage={t('chartEmpty')}
>
<TodayStackedBarChart
data={labTaskActivityData}
completedLabel={t('chartLabTaskCompletedLegend')}
receivedLabel={t('chartLabTaskReceivedLegend')}
formatDayLabel={formatDayLabel}
/>
</ChartCard>
) : null}
{showInProgressTasksByProsthesis ? (
<ChartCard
title={t('chartProsthesisMixTitle')}
subtitle={t('chartProsthesisMixSubtitle')}
isEmpty={prosthesisData.length === 0}
emptyMessage={t('chartEmpty')}
>
<TodayDonutChart
data={prosthesisData}
labelForCode={(code) =>
prosthesisData.find((row) => row.code === code)?.label ?? code
}
colorForCode={(code, index) => prosthesisTypeColor(code, index)}
swatchStyleForCode={(code, index) => prosthesisTypeSwatchStyle(code, index)}
variant="pie"
sideLegend
/>
</ChartCard>
) : null}
{showAppointmentsByProvider ? (
<ChartCard
title={t('chartAppointmentsByProviderTitle')}
subtitle={t('chartAppointmentsByProviderSubtitle')}
isEmpty={appointmentsByProviderData.length === 0}
emptyMessage={t('chartEmpty')}
>
<TodayHorizontalBarChart data={appointmentsByProviderData} />
</ChartCard>
) : null}
return (
<div
className={`grid grid-cols-1 ${chartCount > 1 ? 'lg:grid-cols-2' : ''} gap-4 ${loading ? 'opacity-70 transition-opacity' : ''} ${className}`}
>
{showTreatmentMix ? (
<ChartCard
title={t('chartTreatmentMixTitle')}
@@ -62,7 +190,29 @@ export function TodayChartsSection({
isEmpty={treatmentData.length === 0}
emptyMessage={t('chartEmpty')}
>
<TodayBarChart data={treatmentData} />
<TodayBarChart
data={treatmentData}
colorForCode={(code, index) => treatmentTypeColor(code, index)}
/>
</ChartCard>
) : null}
{showCaseCompletion ? (
<ChartCard
title={t('chartCaseCompletionTitle')}
subtitle={t('chartCaseCompletionSubtitle')}
isEmpty={caseCompletion.total === 0}
emptyMessage={t('chartEmpty')}
>
<TodayRadialGaugeChart
percent={caseCompletion.percent}
completed={caseCompletion.completed}
total={caseCompletion.total}
percentLabel={t('chartCaseCompletionPercent', {
percent: caseCompletion.percent,
})}
tasksLabel={t('chartCaseCompletionTasks')}
/>
</ChartCard>
) : null}
@@ -76,6 +226,18 @@ export function TodayChartsSection({
<TodayBarChart data={tasksData} />
</ChartCard>
) : null}
</>
);
if (embedded) {
return chartCards;
}
return (
<div
className={`${gridClass} gap-4 ${loading ? 'opacity-70 transition-opacity' : ''} ${className}`}
>
{chartCards}
</div>
);
}