687 lines
21 KiB
TypeScript
687 lines
21 KiB
TypeScript
'use client';
|
|
|
|
import { useMemo } from 'react';
|
|
import { useTranslations } from 'next-intl';
|
|
import { useAuth } from '@/lib/hooks/useAuth';
|
|
import {
|
|
canEditCases,
|
|
canEditTreatment,
|
|
canViewAppointmentsTab,
|
|
canViewCases,
|
|
canViewLabCasesOrTasks,
|
|
canViewMyAppointmentsWeekChart,
|
|
canViewTasks,
|
|
canViewTreatment,
|
|
} from '@/components/shared/permissions';
|
|
import { KpiCard } from '@/components/today/KpiCard';
|
|
import { ChartCard } from '@/components/today/ChartCard';
|
|
import { TodayAreaChart } from '@/components/today/TodayAreaChart';
|
|
import { TodayBarChart } from '@/components/today/TodayBarChart';
|
|
import {
|
|
mapWeekChartBuckets,
|
|
useTodayDayLabelFormatter,
|
|
} from '@/components/today/chart-day-labels';
|
|
import { TodayDashboardGrid } from '@/components/today/TodayDashboardGrid';
|
|
import { TodayDonutChart, TodayDonutChartLegend } from '@/components/today/TodayDonutChart';
|
|
import { TodayHorizontalBarChart } from '@/components/today/TodayHorizontalBarChart';
|
|
import { TodayPartnerCasesStackedBarChart } from '@/components/today/TodayPartnerCasesStackedBarChart';
|
|
import { Package, Stethoscope, type LucideIcon } from 'lucide-react';
|
|
import { TodayCompletionGaugeKpiCard } from '@/components/today/TodayCompletionGaugeKpiCard';
|
|
import {
|
|
mapLabTaskActivityChartData,
|
|
TodayLabTaskActivityChart,
|
|
} from '@/components/today/TodayLabTaskActivityChart';
|
|
import { TodaySubscriptionKpiCard } from '@/components/today/TodaySubscriptionKpiCard';
|
|
import { TodayUpcomingAppointments } from '@/components/today/TodayUpcomingAppointments';
|
|
import {
|
|
ChartCardSkeleton,
|
|
KpiCardSkeleton,
|
|
ListRowSkeleton,
|
|
} from '@/components/today/TodaySkeleton';
|
|
import {
|
|
TODAY_DASHBOARD_LAYOUT,
|
|
type TodayDashboardCell,
|
|
} from '@/components/today/today-dashboard-layout';
|
|
import { getEligibleTodayKpis, getVisibleTodayKpis } from '@/components/today/widget-registry';
|
|
import { prosthesisTypeColor } from '@/components/ui/treatment/prosthesisTypeDisplay';
|
|
import { treatmentTypeColor } from '@/components/ui/treatment/treatmentTypeDisplay';
|
|
import type {
|
|
TodayCompletionGauge,
|
|
TodaySubscriptionSnapshot,
|
|
TodaySummaryActions,
|
|
TodaySummaryCharts,
|
|
TodaySummaryWidgets,
|
|
} from '@/types/today';
|
|
|
|
interface TodayDashboardProps {
|
|
widgets: TodaySummaryWidgets;
|
|
charts: TodaySummaryCharts;
|
|
actions: TodaySummaryActions;
|
|
subscription?: TodaySubscriptionSnapshot;
|
|
loading?: boolean;
|
|
isInitialLoad?: boolean;
|
|
hasError?: boolean;
|
|
}
|
|
|
|
export function TodayDashboard({
|
|
widgets,
|
|
charts,
|
|
actions,
|
|
subscription,
|
|
loading = false,
|
|
isInitialLoad = false,
|
|
hasError = false,
|
|
}: TodayDashboardProps) {
|
|
const t = useTranslations('today');
|
|
const dayLabelFormatter = useTodayDayLabelFormatter();
|
|
const { currentOrganization } = useAuth();
|
|
const orgType = currentOrganization?.type;
|
|
const isOwner = Boolean(currentOrganization?.isOwner);
|
|
|
|
const showUpcoming =
|
|
orgType === 'CLINIC' &&
|
|
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') {
|
|
return (
|
|
canViewAppointmentsTab(currentOrganization) ||
|
|
canViewTreatment(currentOrganization)
|
|
);
|
|
}
|
|
return (
|
|
canViewCases(currentOrganization) ||
|
|
canViewTasks(currentOrganization) ||
|
|
canViewLabCasesOrTasks(currentOrganization)
|
|
);
|
|
}, [currentOrganization, orgType]);
|
|
|
|
const kpiDefinitions = isInitialLoad
|
|
? getEligibleTodayKpis(currentOrganization)
|
|
: getVisibleTodayKpis(currentOrganization, widgets);
|
|
|
|
const showSubscriptionCard = isOwner && (isInitialLoad || Boolean(subscription));
|
|
|
|
const showCaseCompletionCard =
|
|
orgType === 'LAB' &&
|
|
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,
|
|
isOwner,
|
|
showMyAppointmentsWeekChart: Boolean(
|
|
currentOrganization &&
|
|
canViewMyAppointmentsWeekChart(currentOrganization),
|
|
),
|
|
showCasePartnersChart,
|
|
charts,
|
|
});
|
|
}
|
|
|
|
return buildDashboardCells({
|
|
t,
|
|
dayLabelFormatter,
|
|
widgets,
|
|
charts,
|
|
actions,
|
|
subscription,
|
|
kpiDefinitions,
|
|
showSubscriptionCard: showSubscriptionCard && Boolean(subscription),
|
|
showCaseCompletionCard:
|
|
showCaseCompletionCard && charts.caseCompletion !== undefined,
|
|
showTreatmentPlanCompletionCard:
|
|
showTreatmentPlanCompletionCard &&
|
|
charts.treatmentPlanCompletion !== undefined,
|
|
showUpcoming: Boolean(showUpcoming),
|
|
showCharts,
|
|
orgType,
|
|
isOwner,
|
|
currentOrganization,
|
|
});
|
|
}, [
|
|
isInitialLoad,
|
|
kpiDefinitions,
|
|
showSubscriptionCard,
|
|
showCaseCompletionCard,
|
|
showTreatmentPlanCompletionCard,
|
|
showUpcoming,
|
|
showCharts,
|
|
orgType,
|
|
isOwner,
|
|
charts,
|
|
t,
|
|
dayLabelFormatter,
|
|
widgets,
|
|
actions,
|
|
subscription,
|
|
currentOrganization,
|
|
]);
|
|
|
|
if (hasError && !loading && cells.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
if (!loading && !hasError && cells.length === 0) {
|
|
return (
|
|
<div className="rounded-[var(--radius-md)] border border-border/60 bg-background-secondary/30 px-4 py-6 text-center">
|
|
<p className="text-sm text-text-muted">{t('noWidgets')}</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return <TodayDashboardGrid cells={cells} loading={loading} />;
|
|
}
|
|
|
|
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[] = [];
|
|
|
|
if (options.showCharts) {
|
|
const chartCount = countVisibleCharts(
|
|
options.charts,
|
|
options.orgType,
|
|
options.isOwner,
|
|
options.showMyAppointmentsWeekChart,
|
|
options.showCasePartnersChart,
|
|
);
|
|
for (let index = 0; index < Math.min(chartCount, 4); index += 1) {
|
|
cells.push({
|
|
id: `chart-skeleton-${index}`,
|
|
layout: TODAY_DASHBOARD_LAYOUT.chart,
|
|
content: <ChartCardSkeleton />,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (options.showUpcoming) {
|
|
cells.push({
|
|
id: 'upcoming-skeleton',
|
|
layout: TODAY_DASHBOARD_LAYOUT.upcoming,
|
|
content: (
|
|
<div className="flex h-full min-h-0 flex-col rounded-[var(--radius-lg)] border border-card-border bg-card p-3">
|
|
<div className="mb-2 space-y-1.5">
|
|
<div className="h-3.5 w-32 animate-pulse rounded bg-background-secondary/60" />
|
|
<div className="h-3 w-44 animate-pulse rounded bg-background-secondary/60" />
|
|
</div>
|
|
<div className="space-y-2">
|
|
{[0, 1].map((key) => (
|
|
<ListRowSkeleton key={key} compact />
|
|
))}
|
|
</div>
|
|
</div>
|
|
),
|
|
});
|
|
}
|
|
|
|
if (options.showSubscriptionCard) {
|
|
cells.push({
|
|
id: 'subscription-skeleton',
|
|
layout: TODAY_DASHBOARD_LAYOUT.subscription,
|
|
content: <KpiCardSkeleton tall />,
|
|
});
|
|
}
|
|
|
|
if (options.showCaseCompletionCard) {
|
|
cells.push({
|
|
id: 'case-completion-skeleton',
|
|
layout: TODAY_DASHBOARD_LAYOUT.subscription,
|
|
content: <KpiCardSkeleton tall />,
|
|
});
|
|
}
|
|
|
|
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}`,
|
|
layout: TODAY_DASHBOARD_LAYOUT.kpi,
|
|
content: <KpiCardSkeleton />,
|
|
});
|
|
}
|
|
|
|
return cells;
|
|
}
|
|
|
|
function buildDashboardCells(options: {
|
|
t: ReturnType<typeof useTranslations<'today'>>;
|
|
dayLabelFormatter: ReturnType<typeof useTodayDayLabelFormatter>;
|
|
widgets: TodaySummaryWidgets;
|
|
charts: TodaySummaryCharts;
|
|
actions: TodaySummaryActions;
|
|
subscription?: TodaySubscriptionSnapshot;
|
|
kpiDefinitions: ReturnType<typeof getVisibleTodayKpis>;
|
|
showSubscriptionCard: boolean;
|
|
showCaseCompletionCard: boolean;
|
|
showTreatmentPlanCompletionCard: boolean;
|
|
showUpcoming: boolean;
|
|
showCharts: boolean;
|
|
orgType?: 'CLINIC' | 'LAB';
|
|
isOwner: boolean;
|
|
currentOrganization: ReturnType<typeof useAuth>['currentOrganization'];
|
|
}): TodayDashboardCell[] {
|
|
const cells: TodayDashboardCell[] = [];
|
|
|
|
if (options.showCharts) {
|
|
cells.push(
|
|
...buildChartCells({
|
|
t: options.t,
|
|
charts: options.charts,
|
|
orgType: options.orgType,
|
|
isOwner: options.isOwner,
|
|
showMyAppointmentsWeekChart: Boolean(
|
|
options.currentOrganization &&
|
|
canViewMyAppointmentsWeekChart(options.currentOrganization),
|
|
),
|
|
showCasePartnersChart:
|
|
Boolean(options.currentOrganization) &&
|
|
((options.orgType === 'CLINIC' &&
|
|
canEditTreatment(options.currentOrganization)) ||
|
|
(options.orgType === 'LAB' &&
|
|
canEditCases(options.currentOrganization))),
|
|
dayLabelFormatter: options.dayLabelFormatter,
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (options.showUpcoming) {
|
|
cells.push({
|
|
id: 'upcoming-appointments',
|
|
layout: TODAY_DASHBOARD_LAYOUT.upcoming,
|
|
content: (
|
|
<TodayUpcomingAppointments actions={options.actions} loading={false} isInitialLoad={false} />
|
|
),
|
|
});
|
|
}
|
|
|
|
if (options.showSubscriptionCard && options.subscription) {
|
|
cells.push({
|
|
id: 'subscription',
|
|
layout: TODAY_DASHBOARD_LAYOUT.subscription,
|
|
content: <TodaySubscriptionKpiCard subscription={options.subscription} />,
|
|
});
|
|
}
|
|
|
|
if (options.showCaseCompletionCard && options.charts.caseCompletion !== undefined) {
|
|
pushCompletionGaugeCell(cells, {
|
|
id: 'case-completion',
|
|
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,
|
|
});
|
|
}
|
|
|
|
for (const definition of options.kpiDefinitions) {
|
|
const value = definition.formatValue(options.widgets) ?? '—';
|
|
const subtitle = definition.formatSubtitle?.(options.widgets);
|
|
|
|
cells.push({
|
|
id: `kpi-${definition.key}`,
|
|
layout: TODAY_DASHBOARD_LAYOUT.kpi,
|
|
content: (
|
|
<KpiCard
|
|
title={options.t(definition.titleKey)}
|
|
value={value}
|
|
subtitle={subtitle}
|
|
icon={definition.icon}
|
|
color={definition.color}
|
|
href={definition.href}
|
|
className="h-full"
|
|
/>
|
|
),
|
|
});
|
|
}
|
|
|
|
return cells;
|
|
}
|
|
|
|
function buildChartCells(options: {
|
|
t: ReturnType<typeof useTranslations<'today'>>;
|
|
charts: TodaySummaryCharts;
|
|
orgType?: 'CLINIC' | 'LAB';
|
|
isOwner: boolean;
|
|
showMyAppointmentsWeekChart: boolean;
|
|
showCasePartnersChart: boolean;
|
|
dayLabelFormatter: ReturnType<typeof useTodayDayLabelFormatter>;
|
|
}): TodayDashboardCell[] {
|
|
const { t, charts, orgType, isOwner, showMyAppointmentsWeekChart } = options;
|
|
const cells: TodayDashboardCell[] = [];
|
|
const areaChart = TODAY_DASHBOARD_LAYOUT.chartArea;
|
|
const barChart = TODAY_DASHBOARD_LAYOUT.chartBar;
|
|
|
|
const appointmentsWeekAllData = mapWeekChartBuckets(
|
|
charts.appointmentsWeekAll ?? [],
|
|
options.dayLabelFormatter,
|
|
);
|
|
const appointmentsWeekMineData = mapWeekChartBuckets(
|
|
charts.appointmentsWeekMine ?? [],
|
|
options.dayLabelFormatter,
|
|
);
|
|
const labTaskActivityData = mapWeekChartBuckets(
|
|
charts.labTaskActivityWeek ?? [],
|
|
options.dayLabelFormatter,
|
|
);
|
|
const labTaskActivityChartData = mapLabTaskActivityChartData(labTaskActivityData);
|
|
|
|
if (orgType === 'CLINIC' && charts.appointmentsWeekAll !== undefined) {
|
|
cells.push({
|
|
id: 'chart-appointments-week-all',
|
|
layout: areaChart,
|
|
content: (
|
|
<ChartCard
|
|
title={t('chartAppointmentsWeekAllTitle')}
|
|
subtitle={t('chartAppointmentsWeekAllSubtitle')}
|
|
isEmpty={appointmentsWeekAllData.every((row) => row.count === 0)}
|
|
emptyMessage={t('chartEmpty')}
|
|
>
|
|
<TodayAreaChart data={appointmentsWeekAllData} />
|
|
</ChartCard>
|
|
),
|
|
});
|
|
}
|
|
|
|
if (
|
|
orgType === 'CLINIC' &&
|
|
showMyAppointmentsWeekChart &&
|
|
charts.appointmentsWeekMine !== undefined
|
|
) {
|
|
cells.push({
|
|
id: 'chart-appointments-week-mine',
|
|
layout: areaChart,
|
|
content: (
|
|
<ChartCard
|
|
title={t('chartAppointmentsWeekMineTitle')}
|
|
subtitle={t('chartAppointmentsWeekMineSubtitle')}
|
|
isEmpty={appointmentsWeekMineData.every((row) => row.count === 0)}
|
|
emptyMessage={t('chartEmpty')}
|
|
>
|
|
<TodayAreaChart data={appointmentsWeekMineData} />
|
|
</ChartCard>
|
|
),
|
|
});
|
|
}
|
|
|
|
if (orgType === 'LAB' && charts.labTaskActivityWeek !== undefined) {
|
|
cells.push({
|
|
id: 'chart-lab-task-activity',
|
|
layout: areaChart,
|
|
content: (
|
|
<ChartCard
|
|
title={t('chartLabTaskActivityTitle')}
|
|
subtitle={t('chartLabTaskActivitySubtitle')}
|
|
isEmpty={labTaskActivityData.every(
|
|
(row) => row.completed === 0 && row.received === 0,
|
|
)}
|
|
emptyMessage={t('chartEmpty')}
|
|
>
|
|
<TodayLabTaskActivityChart
|
|
data={labTaskActivityChartData}
|
|
completedLabel={t('chartLabTaskCompletedLegend')}
|
|
receivedLabel={t('chartLabTaskReceivedLegend')}
|
|
/>
|
|
</ChartCard>
|
|
),
|
|
});
|
|
}
|
|
|
|
const efficiencyReportData = charts.efficiencyReport ?? [];
|
|
if (
|
|
isOwner &&
|
|
charts.efficiencyReport !== undefined &&
|
|
efficiencyReportData.length >= 2
|
|
) {
|
|
cells.push({
|
|
id: 'chart-efficiency-report',
|
|
layout: areaChart,
|
|
content: (
|
|
<ChartCard
|
|
title={t('chartEfficiencyReportTitle')}
|
|
subtitle={
|
|
orgType === 'CLINIC'
|
|
? t('chartEfficiencyReportSubtitleClinic')
|
|
: t('chartEfficiencyReportSubtitleLab')
|
|
}
|
|
isEmpty={efficiencyReportData.every((row) => row.count === 0)}
|
|
emptyMessage={t('chartEmpty')}
|
|
sidePanelLayout
|
|
chartPanel={
|
|
<TodayDonutChart
|
|
data={efficiencyReportData}
|
|
labelForCode={(code) =>
|
|
efficiencyReportData.find((row) => row.code === code)?.label ?? code
|
|
}
|
|
variant="pie"
|
|
/>
|
|
}
|
|
>
|
|
<TodayDonutChartLegend
|
|
data={efficiencyReportData}
|
|
labelForCode={(code) =>
|
|
efficiencyReportData.find((row) => row.code === code)?.label ?? code
|
|
}
|
|
/>
|
|
</ChartCard>
|
|
),
|
|
});
|
|
}
|
|
|
|
const appointmentsByProviderData = charts.appointmentsByProvider ?? [];
|
|
if (orgType === 'CLINIC' && charts.appointmentsByProvider !== undefined) {
|
|
cells.push({
|
|
id: 'chart-appointments-by-provider',
|
|
layout: barChart,
|
|
content: (
|
|
<ChartCard
|
|
title={t('chartAppointmentsByProviderTitle')}
|
|
subtitle={t('chartAppointmentsByProviderSubtitle')}
|
|
isEmpty={appointmentsByProviderData.length === 0}
|
|
emptyMessage={t('chartEmpty')}
|
|
>
|
|
<TodayHorizontalBarChart data={appointmentsByProviderData} />
|
|
</ChartCard>
|
|
),
|
|
});
|
|
}
|
|
|
|
const treatmentData = charts.treatmentMixWeek ?? [];
|
|
if (orgType === 'CLINIC' && charts.treatmentMixWeek !== undefined) {
|
|
cells.push({
|
|
id: 'chart-treatment-mix',
|
|
layout: barChart,
|
|
content: (
|
|
<ChartCard
|
|
title={t('chartTreatmentMixTitle')}
|
|
subtitle={t('chartTreatmentMixSubtitle')}
|
|
isEmpty={treatmentData.length === 0}
|
|
emptyMessage={t('chartEmpty')}
|
|
>
|
|
<TodayBarChart
|
|
data={treatmentData}
|
|
colorForCode={(code, index) => treatmentTypeColor(code, index)}
|
|
/>
|
|
</ChartCard>
|
|
),
|
|
});
|
|
}
|
|
|
|
const tasksByProsthesisData = charts.tasksByProsthesis ?? [];
|
|
if (orgType === 'LAB' && charts.tasksByProsthesis !== undefined) {
|
|
cells.push({
|
|
id: 'chart-tasks-by-prosthesis',
|
|
layout: barChart,
|
|
content: (
|
|
<ChartCard
|
|
title={t('chartTasksByProsthesisTitle')}
|
|
subtitle={t('chartTasksByProsthesisSubtitle')}
|
|
isEmpty={tasksByProsthesisData.length === 0}
|
|
emptyMessage={t('chartEmpty')}
|
|
>
|
|
<TodayBarChart
|
|
data={tasksByProsthesisData}
|
|
colorForCode={(code, index) => prosthesisTypeColor(code, index)}
|
|
/>
|
|
</ChartCard>
|
|
),
|
|
});
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
function countVisibleCharts(
|
|
charts: TodaySummaryCharts,
|
|
orgType?: 'CLINIC' | 'LAB',
|
|
isOwner = false,
|
|
showMyAppointmentsWeekChart = false,
|
|
showCasePartnersChart = false,
|
|
): number {
|
|
let count = 0;
|
|
if (orgType === 'CLINIC') {
|
|
count += charts.appointmentsWeekAll !== undefined ? 1 : 0;
|
|
count +=
|
|
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 &&
|
|
charts.efficiencyReport !== undefined &&
|
|
(charts.efficiencyReport?.length ?? 0) >= 2
|
|
) {
|
|
count += 1;
|
|
}
|
|
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}
|
|
/>
|
|
),
|
|
});
|
|
}
|