Files
dyolink/frontend/src/components/today/widget-registry.ts

220 lines
6.1 KiB
TypeScript
Raw Normal View History

import type { LucideIcon } from 'lucide-react';
import {
AlertCircle,
CalendarDays,
ClipboardList,
FlaskConical,
Link2,
Stethoscope,
UserCog,
Users,
} from 'lucide-react';
import type { Organization } from '@/types/organization';
import {
canAccessAppointmentsSection,
canEditStaff,
canViewCases,
canViewStaff,
canViewTasks,
canViewTreatment,
hasPermission,
type OrgTypeName,
} from '@/components/shared/permissions';
import type { TodaySummaryWidgets, TodayWidgetKey } from '@/types/today';
export type KpiCardColor = 'blue' | 'yellow' | 'green' | 'red' | 'purple' | 'default';
export interface TodayKpiDefinition {
key: TodayWidgetKey;
titleKey: string;
icon: LucideIcon;
color: KpiCardColor;
orgTypes: OrgTypeName[];
isVisible: (org: Organization | null) => boolean;
formatValue: (widgets: TodaySummaryWidgets) => string | null;
formatSubtitle?: (widgets: TodaySummaryWidgets) => string | null;
}
function countWidget(
widgets: TodaySummaryWidgets,
key: TodayWidgetKey,
): number | null {
const value = widgets[key];
if (!value || !('count' in value)) return null;
return value.count;
}
function canManageOrganizations(org: Organization | null): boolean {
if (!org) return false;
if (org.isOwner) return true;
return hasPermission(org, 'TAB_ORGANIZATIONS_EDIT');
}
function canViewPatients(org: Organization | null): boolean {
if (!org) return false;
return (
hasPermission(org, 'TAB_PATIENTS_READ') ||
hasPermission(org, 'TAB_PATIENTS_EDIT')
);
}
export const TODAY_KPI_DEFINITIONS: TodayKpiDefinition[] = [
{
key: 'appointmentsToday',
titleKey: 'widgetAppointmentsToday',
icon: CalendarDays,
color: 'blue',
orgTypes: ['CLINIC'],
isVisible: (org) => canAccessAppointmentsSection(org),
formatValue: (widgets) => {
const count = countWidget(widgets, 'appointmentsToday');
return count === null ? null : String(count);
},
},
{
key: 'patientsToday',
titleKey: 'widgetPatientsToday',
icon: Users,
color: 'green',
orgTypes: ['CLINIC'],
isVisible: (org) => canViewPatients(org) || canAccessAppointmentsSection(org),
formatValue: (widgets) => {
const count = countWidget(widgets, 'patientsToday');
return count === null ? null : String(count);
},
},
{
key: 'treatmentsToday',
titleKey: 'widgetTreatmentsToday',
icon: Stethoscope,
color: 'purple',
orgTypes: ['CLINIC'],
isVisible: (org) => canViewTreatment(org),
formatValue: (widgets) => {
const count = countWidget(widgets, 'treatmentsToday');
return count === null ? null : String(count);
},
},
{
key: 'draftTreatments',
titleKey: 'widgetDraftTreatments',
icon: ClipboardList,
color: 'yellow',
orgTypes: ['CLINIC'],
isVisible: (org) => canViewTreatment(org),
formatValue: (widgets) => {
const count = countWidget(widgets, 'draftTreatments');
return count === null ? null : String(count);
},
},
{
key: 'labCasesPendingSend',
titleKey: 'widgetLabCasesPendingSend',
icon: FlaskConical,
color: 'red',
orgTypes: ['CLINIC'],
isVisible: (org) => canViewTreatment(org),
formatValue: (widgets) => {
const count = countWidget(widgets, 'labCasesPendingSend');
return count === null ? null : String(count);
},
},
{
key: 'casesReceivedToday',
titleKey: 'widgetCasesReceivedToday',
icon: FlaskConical,
color: 'blue',
orgTypes: ['LAB'],
isVisible: (org) => canViewCases(org),
formatValue: (widgets) => {
const count = countWidget(widgets, 'casesReceivedToday');
return count === null ? null : String(count);
},
},
{
key: 'tasksInProgress',
titleKey: 'widgetTasksInProgress',
icon: ClipboardList,
color: 'yellow',
orgTypes: ['LAB'],
isVisible: (org) => canViewTasks(org),
formatValue: (widgets) => {
const count = countWidget(widgets, 'tasksInProgress');
return count === null ? null : String(count);
},
},
{
key: 'importantTasks',
titleKey: 'widgetImportantTasks',
icon: AlertCircle,
color: 'red',
orgTypes: ['LAB'],
isVisible: (org) => canViewTasks(org),
formatValue: (widgets) => {
const count = countWidget(widgets, 'importantTasks');
return count === null ? null : String(count);
},
},
{
key: 'pendingConnections',
titleKey: 'widgetPendingConnections',
icon: Link2,
color: 'yellow',
orgTypes: ['CLINIC', 'LAB'],
isVisible: (org) => canManageOrganizations(org),
formatValue: (widgets) => {
const count = countWidget(widgets, 'pendingConnections');
return count === null ? null : String(count);
},
},
{
key: 'seats',
titleKey: 'widgetSeats',
icon: UserCog,
color: 'default',
orgTypes: ['CLINIC', 'LAB'],
isVisible: (org) => canViewStaff(org),
formatValue: (widgets) => {
const seats = widgets.seats;
if (!seats || !('used' in seats)) return null;
if (seats.unlimited) return String(seats.used);
return `${seats.used}/${seats.limit ?? 0}`;
},
formatSubtitle: (widgets) => {
const seats = widgets.seats;
if (!seats || !('used' in seats)) return null;
return seats.unlimited ? 'unlimited' : null;
},
},
{
key: 'pendingStaffInvites',
titleKey: 'widgetPendingStaffInvites',
icon: UserCog,
color: 'purple',
orgTypes: ['CLINIC', 'LAB'],
isVisible: (org) => canEditStaff(org) || Boolean(org?.isOwner),
formatValue: (widgets) => {
const count = countWidget(widgets, 'pendingStaffInvites');
return count === null ? null : String(count);
},
},
];
export function getEligibleTodayKpis(org: Organization | null): TodayKpiDefinition[] {
if (!org) return [];
return TODAY_KPI_DEFINITIONS.filter((definition) => {
if (!definition.orgTypes.includes(org.type)) return false;
return definition.isVisible(org);
});
}
export function getVisibleTodayKpis(
org: Organization | null,
widgets: TodaySummaryWidgets,
): TodayKpiDefinition[] {
return getEligibleTodayKpis(org).filter(
(definition) => definition.formatValue(widgets) !== null,
);
}