Case completion and tasks completion gadgets added.

This commit is contained in:
2026-07-12 02:08:54 +03:30
parent d79aab907c
commit 0f1004a34a
10 changed files with 616 additions and 87 deletions

View File

@@ -4,6 +4,8 @@ import { useMemo } from 'react';
import { useTranslations } from 'next-intl';
import { useAuth } from '@/lib/hooks/useAuth';
import {
canEditCases,
canEditTreatment,
canViewAppointmentsTab,
canViewCases,
canViewLabCasesOrTasks,
@@ -22,7 +24,9 @@ import {
import { TodayDashboardGrid } from '@/components/today/TodayDashboardGrid';
import { TodayDonutChart, TodayDonutChartLegend } from '@/components/today/TodayDonutChart';
import { TodayHorizontalBarChart } from '@/components/today/TodayHorizontalBarChart';
import { TodayCaseCompletionKpiCard } from '@/components/today/TodayCaseCompletionKpiCard';
import { TodayPartnerCasesStackedBarChart } from '@/components/today/TodayPartnerCasesStackedBarChart';
import { Package, Stethoscope, type LucideIcon } from 'lucide-react';
import { TodayCompletionGaugeKpiCard } from '@/components/today/TodayCompletionGaugeKpiCard';
import {
mapLabTaskActivityChartData,
TodayLabTaskActivityChart,
@@ -42,6 +46,7 @@ import { getEligibleTodayKpis, getVisibleTodayKpis } from '@/components/today/wi
import { prosthesisTypeColor } from '@/components/ui/treatment/prosthesisTypeDisplay';
import { treatmentTypeColor } from '@/components/ui/treatment/treatmentTypeDisplay';
import type {
TodayCompletionGauge,
TodaySubscriptionSnapshot,
TodaySummaryActions,
TodaySummaryCharts,
@@ -78,6 +83,12 @@ export function TodayDashboard({
currentOrganization &&
canViewMyAppointmentsWeekChart(currentOrganization);
const showCasePartnersChart = Boolean(
currentOrganization &&
((orgType === 'CLINIC' && canEditTreatment(currentOrganization)) ||
(orgType === 'LAB' && canEditCases(currentOrganization))),
);
const showCharts = useMemo(() => {
if (!orgType || !currentOrganization) return false;
if (orgType === 'CLINIC') {
@@ -104,12 +115,18 @@ export function TodayDashboard({
Boolean(currentOrganization && canViewCases(currentOrganization)) &&
(isInitialLoad || charts.caseCompletion !== undefined);
const showTreatmentPlanCompletionCard =
orgType === 'CLINIC' &&
Boolean(currentOrganization && canEditTreatment(currentOrganization)) &&
(isInitialLoad || charts.treatmentPlanCompletion !== undefined);
const cells = useMemo(() => {
if (isInitialLoad) {
return buildSkeletonCells({
kpiDefinitions,
showSubscriptionCard,
showCaseCompletionCard,
showTreatmentPlanCompletionCard,
showUpcoming: Boolean(showUpcoming),
showCharts,
orgType,
@@ -118,6 +135,7 @@ export function TodayDashboard({
currentOrganization &&
canViewMyAppointmentsWeekChart(currentOrganization),
),
showCasePartnersChart,
charts,
});
}
@@ -133,6 +151,9 @@ export function TodayDashboard({
showSubscriptionCard: showSubscriptionCard && Boolean(subscription),
showCaseCompletionCard:
showCaseCompletionCard && charts.caseCompletion !== undefined,
showTreatmentPlanCompletionCard:
showTreatmentPlanCompletionCard &&
charts.treatmentPlanCompletion !== undefined,
showUpcoming: Boolean(showUpcoming),
showCharts,
orgType,
@@ -144,6 +165,7 @@ export function TodayDashboard({
kpiDefinitions,
showSubscriptionCard,
showCaseCompletionCard,
showTreatmentPlanCompletionCard,
showUpcoming,
showCharts,
orgType,
@@ -176,11 +198,13 @@ function buildSkeletonCells(options: {
kpiDefinitions: ReturnType<typeof getEligibleTodayKpis>;
showSubscriptionCard: boolean;
showCaseCompletionCard: boolean;
showTreatmentPlanCompletionCard: boolean;
showUpcoming: boolean;
showCharts: boolean;
orgType?: 'CLINIC' | 'LAB';
isOwner: boolean;
showMyAppointmentsWeekChart: boolean;
showCasePartnersChart: boolean;
charts: TodaySummaryCharts;
}): TodayDashboardCell[] {
const cells: TodayDashboardCell[] = [];
@@ -191,6 +215,7 @@ function buildSkeletonCells(options: {
options.orgType,
options.isOwner,
options.showMyAppointmentsWeekChart,
options.showCasePartnersChart,
);
for (let index = 0; index < Math.min(chartCount, 4); index += 1) {
cells.push({
@@ -237,6 +262,14 @@ function buildSkeletonCells(options: {
});
}
if (options.showTreatmentPlanCompletionCard) {
cells.push({
id: 'treatment-plan-completion-skeleton',
layout: TODAY_DASHBOARD_LAYOUT.subscription,
content: <KpiCardSkeleton tall />,
});
}
for (const definition of options.kpiDefinitions) {
cells.push({
id: `kpi-skeleton-${definition.key}`,
@@ -258,6 +291,7 @@ function buildDashboardCells(options: {
kpiDefinitions: ReturnType<typeof getVisibleTodayKpis>;
showSubscriptionCard: boolean;
showCaseCompletionCard: boolean;
showTreatmentPlanCompletionCard: boolean;
showUpcoming: boolean;
showCharts: boolean;
orgType?: 'CLINIC' | 'LAB';
@@ -277,6 +311,12 @@ function buildDashboardCells(options: {
options.currentOrganization &&
canViewMyAppointmentsWeekChart(options.currentOrganization),
),
showCasePartnersChart:
Boolean(options.currentOrganization) &&
((options.orgType === 'CLINIC' &&
canEditTreatment(options.currentOrganization)) ||
(options.orgType === 'LAB' &&
canEditCases(options.currentOrganization))),
dayLabelFormatter: options.dayLabelFormatter,
}),
);
@@ -301,17 +341,35 @@ function buildDashboardCells(options: {
}
if (options.showCaseCompletionCard && options.charts.caseCompletion !== undefined) {
const caseCompletion = options.charts.caseCompletion;
cells.push({
pushCompletionGaugeCell(cells, {
id: 'case-completion',
layout: TODAY_DASHBOARD_LAYOUT.subscription,
content: (
<TodayCaseCompletionKpiCard
completed={caseCompletion.completed}
total={caseCompletion.total}
percent={caseCompletion.percent}
/>
),
gauge: options.charts.caseCompletion,
title: options.t('chartCaseCompletionTitle'),
subtitle: options.t('chartCaseCompletionSubtitle'),
percentLabel: options.t('chartCaseCompletionPercent', {
percent: options.charts.caseCompletion.percent,
}),
ratioLabel: options.t('chartCaseCompletionTasks'),
href: '/cases',
icon: Package,
});
}
if (
options.showTreatmentPlanCompletionCard &&
options.charts.treatmentPlanCompletion !== undefined
) {
pushCompletionGaugeCell(cells, {
id: 'treatment-plan-completion',
gauge: options.charts.treatmentPlanCompletion,
title: options.t('chartTreatmentPlanCompletionTitle'),
subtitle: options.t('chartTreatmentPlanCompletionSubtitle'),
percentLabel: options.t('chartCaseCompletionPercent', {
percent: options.charts.treatmentPlanCompletion.percent,
}),
ratioLabel: options.t('chartTreatmentPlanCompletionRatio'),
href: '/appointments',
icon: Stethoscope,
});
}
@@ -345,6 +403,7 @@ function buildChartCells(options: {
orgType?: 'CLINIC' | 'LAB';
isOwner: boolean;
showMyAppointmentsWeekChart: boolean;
showCasePartnersChart: boolean;
dayLabelFormatter: ReturnType<typeof useTodayDayLabelFormatter>;
}): TodayDashboardCell[] {
const { t, charts, orgType, isOwner, showMyAppointmentsWeekChart } = options;
@@ -528,6 +587,38 @@ function buildChartCells(options: {
});
}
const casePartnersData = charts.casePartnersMonth ?? [];
if (options.showCasePartnersChart && charts.casePartnersMonth !== undefined) {
cells.push({
id: 'chart-case-partners-month',
layout: barChart,
content: (
<ChartCard
title={
orgType === 'CLINIC'
? t('chartCasePartnersClinicTitle')
: t('chartCasePartnersLabTitle')
}
subtitle={t('chartCasePartnersSubtitle')}
isEmpty={casePartnersData.every(
(row) => row.completed === 0 && row.pending === 0,
)}
emptyMessage={t('chartEmpty')}
>
<TodayPartnerCasesStackedBarChart
data={casePartnersData}
completedLabel={t('chartLabTaskCompletedLegend')}
pendingLabel={
orgType === 'CLINIC'
? t('chartCasePartnersSentLegend')
: t('chartCasePartnersOpenLegend')
}
/>
</ChartCard>
),
});
}
return cells;
}
@@ -536,6 +627,7 @@ function countVisibleCharts(
orgType?: 'CLINIC' | 'LAB',
isOwner = false,
showMyAppointmentsWeekChart = false,
showCasePartnersChart = false,
): number {
let count = 0;
if (orgType === 'CLINIC') {
@@ -544,10 +636,12 @@ function countVisibleCharts(
showMyAppointmentsWeekChart && charts.appointmentsWeekMine !== undefined ? 1 : 0;
count += charts.appointmentsByProvider !== undefined ? 1 : 0;
count += charts.treatmentMixWeek !== undefined ? 1 : 0;
count += showCasePartnersChart && charts.casePartnersMonth !== undefined ? 1 : 0;
}
if (orgType === 'LAB') {
count += charts.labTaskActivityWeek !== undefined ? 1 : 0;
count += charts.tasksByProsthesis !== undefined ? 1 : 0;
count += showCasePartnersChart && charts.casePartnersMonth !== undefined ? 1 : 0;
}
if (
isOwner &&
@@ -558,3 +652,35 @@ function countVisibleCharts(
}
return count;
}
function pushCompletionGaugeCell(
cells: TodayDashboardCell[],
options: {
id: string;
gauge: TodayCompletionGauge;
title: string;
subtitle: string;
percentLabel: string;
ratioLabel: string;
href: string;
icon: LucideIcon;
},
) {
cells.push({
id: options.id,
layout: TODAY_DASHBOARD_LAYOUT.subscription,
content: (
<TodayCompletionGaugeKpiCard
completed={options.gauge.completed}
total={options.gauge.total}
percent={options.gauge.percent}
title={options.title}
subtitle={options.subtitle}
percentLabel={options.percentLabel}
ratioLabel={options.ratioLabel}
href={options.href}
icon={options.icon}
/>
),
});
}