/** * Staff route only: tab matrix + checkbox state ↔ TAB_* permission names. * Add presentational pieces under ./components/ as the UI grows. */ import type { OrgTypeName } from '@/components/shared/permissions'; export const STAFF_FEATURE_GROUPS = [ { labelKey: 'featureToday', read: 'TAB_TODAY_READ', edit: 'TAB_TODAY_EDIT', orgTypes: ['CLINIC', 'LAB'] as const }, { labelKey: 'featureStaff', read: 'TAB_STAFF_READ', edit: 'TAB_STAFF_EDIT', orgTypes: ['CLINIC', 'LAB'] as const }, { labelKey: 'featureOrganizations', read: 'TAB_ORGANIZATIONS_READ', edit: 'TAB_ORGANIZATIONS_EDIT', orgTypes: ['CLINIC', 'LAB'] as const }, { labelKey: 'featurePatients', read: 'TAB_PATIENTS_READ', edit: 'TAB_PATIENTS_EDIT', orgTypes: ['CLINIC'] as const }, { labelKey: 'featureAppointment', read: 'TAB_APPOINTMENTS_READ', edit: 'TAB_APPOINTMENTS_EDIT', orgTypes: ['CLINIC'] as const }, { labelKey: 'featureTreatment', read: 'TAB_TREATMENT_READ', edit: 'TAB_TREATMENT_EDIT', orgTypes: ['CLINIC'] as const }, { labelKey: 'featureCases', read: 'TAB_CASES_READ', edit: 'TAB_CASES_EDIT', orgTypes: ['LAB'] as const }, { labelKey: 'featureTasks', read: 'TAB_TASKS_READ', edit: 'TAB_TASKS_EDIT', orgTypes: ['LAB'] as const }, { labelKey: 'featureBilling', read: 'TAB_BILLING_READ', edit: 'TAB_BILLING_EDIT', orgTypes: ['CLINIC', 'LAB'] as const }, { labelKey: 'featureReports', read: 'TAB_REPORTS_READ', edit: 'TAB_REPORTS_EDIT', orgTypes: ['CLINIC', 'LAB'] as const }, ] as const; export type FeaturePermState = Record; export type OrgType = OrgTypeName | null | undefined; type StaffFeaturesTranslate = (key: string) => string; export function staffFeatureGroupsForOrgType(organizationType: OrgType) { if (!organizationType) return [...STAFF_FEATURE_GROUPS]; return STAFF_FEATURE_GROUPS.filter((g) => (g.orgTypes as readonly OrgTypeName[]).includes(organizationType), ); } export function resolveStaffFeatureLabel( group: (typeof STAFF_FEATURE_GROUPS)[number], organizationType: OrgType, t: StaffFeaturesTranslate, ): string { if (group.read === 'TAB_ORGANIZATIONS_READ') { return organizationType === 'LAB' ? t('featureClinics') : t('featureLabs'); } return t(group.labelKey); } export function emptyFeaturePermissionState(organizationType?: OrgType): FeaturePermState { const s: FeaturePermState = {}; for (const g of staffFeatureGroupsForOrgType(organizationType)) { s[g.edit] = { read: false, edit: false }; } return s; } export function featureStateFromPermissionNames( names: string[], organizationType?: OrgType, ): FeaturePermState { const set = new Set(names); const s = emptyFeaturePermissionState(organizationType); for (const g of staffFeatureGroupsForOrgType(organizationType)) { const hasEdit = set.has(g.edit); const hasRead = set.has(g.read) || hasEdit; s[g.edit] = { read: hasRead, edit: hasEdit }; } return s; } export function permissionNamesFromFeatureState(state: FeaturePermState): string[] { const out: string[] = []; for (const g of STAFF_FEATURE_GROUPS) { const cell = state[g.edit]; if (!cell) continue; if (cell.edit) out.push(g.edit); else if (cell.read) out.push(g.read); } return out; } export function featureStateHasTreatmentEdit(state: FeaturePermState): boolean { return Boolean(state.TAB_TREATMENT_EDIT?.edit); } /** Human-readable access for the team table — feature name, or "Feature (Read only)" */ export function formatAccessSummary( permissionNames: string[] | null | undefined, organizationType: OrgType, t: StaffFeaturesTranslate, ): string { if (!permissionNames?.length) return t('noTabAccess'); const set = new Set(permissionNames); const parts: string[] = []; for (const g of staffFeatureGroupsForOrgType(organizationType)) { const hasEdit = set.has(g.edit); const hasRead = set.has(g.read) || hasEdit; if (!hasRead) continue; const label = resolveStaffFeatureLabel(g, organizationType, t); parts.push(hasEdit ? label : `${label} ${t('readOnlySuffix')}`); } return parts.length ? parts.join(' · ') : t('noTabAccess'); }