@alfadocs/ui-kit-debug 1.9.2 → 1.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"file":"patient-table-6XnaWimB.js","sources":["../../src/components/patient-table/cell-renderers/balance-badge-cell.tsx","../../src/components/patient-table/cell-renderers/care-plan-status-cell.tsx","../../src/components/patient-table/cell-renderers/next-appointment-cell.tsx","../../src/components/patient-table/columns.ts","../../src/components/patient-table/use-responsive-columns.ts","../../src/components/patient-table/patient-table.tsx"],"sourcesContent":["import type { CustomCellRendererProps } from 'ag-grid-react';\nimport { useTranslation } from 'react-i18next';\nimport { Badge } from '../../badge/badge';\nimport type { PatientRow } from '../types';\n\n/**\n * patient-table — BalanceBadgeCell\n * ----------------------------------------------------------------------------\n * Threshold-coloured unpaid-balance badge for the patient grid.\n *\n * Reads {@link PatientRow.unpaidAmount} (integer cents), formats it to the\n * display currency via `Intl.NumberFormat` (cents → major unit at the render\n * boundary — see types.ts), and wraps the result in a kit {@link Badge} whose\n * semantic variant buckets by outstanding-balance threshold:\n *\n * | cents bucket | meaning | Badge variant |\n * | ----------------------- | ------------------ | ------------- |\n * | `0` | settled | `success` |\n * | `> 0` and `<= 50000` | small balance | `warning` |\n * | `> 50000` | overdue / large | `error` |\n *\n * NOTE on the variant name: the task brief calls the red bucket\n * `destructive`; the kit `Badge` exposes that semantic tier as the `error`\n * variant (backed by the `--destructive` / `--error-foreground` tokens). There\n * is no `destructive` Badge variant, so `error` is the correct destructive-tier\n * mapping — recorded here so the brief↔code discrepancy is intentional.\n *\n * The bucket meaning is colour-coded, so it is also surfaced as an sr-only\n * status word inside the badge (alongside the `withDot` indicator) — the\n * threshold tier is never conveyed by colour alone.\n *\n * Returns `null` when `unpaidAmount` is missing / non-finite (empty cell), per\n * the data-table renderer convention.\n */\n\n/** Display + threshold-classification result for a single balance value. */\ntype BalanceBucket = {\n variant: 'success' | 'warning' | 'error';\n /** i18n key suffix under `ui.patientTable.balance.status.*`. */\n statusKey: 'settled' | 'pending' | 'overdue';\n};\n\n/** Cents at or below which a non-zero balance is treated as a small balance. */\nconst SMALL_BALANCE_CENTS_MAX = 50000;\n\nfunction classifyBalance(cents: number): BalanceBucket {\n if (cents === 0) return { variant: 'success', statusKey: 'settled' };\n if (cents <= SMALL_BALANCE_CENTS_MAX)\n return { variant: 'warning', statusKey: 'pending' };\n return { variant: 'error', statusKey: 'overdue' };\n}\n\nfunction readUnpaidCents(value: unknown): number | null {\n if (value == null || value === '') return null;\n const n = typeof value === 'number' ? value : Number(value);\n return Number.isFinite(n) ? n : null;\n}\n\nexport interface BalanceBadgeCellParams {\n /** ISO 4217 currency code used to format the balance. Default `'EUR'`. */\n currency?: string;\n /**\n * `Intl.NumberFormat` escape hatch, merged on top of the currency defaults.\n * E.g. `{ maximumFractionDigits: 0 }` for whole-currency display.\n */\n options?: Intl.NumberFormatOptions;\n}\n\nexport function BalanceBadgeCell(\n props: CustomCellRendererProps<PatientRow> & BalanceBadgeCellParams,\n) {\n const { value, currency = 'EUR', options } = props;\n const { t, i18n } = useTranslation('ui');\n\n const cents = readUnpaidCents(value);\n if (cents === null) return null;\n\n const locale = i18n.language || 'en';\n const formatted = new Intl.NumberFormat(locale, {\n style: 'currency',\n currency,\n ...options,\n }).format(cents / 100);\n\n const { variant, statusKey } = classifyBalance(cents);\n const status = t(`patientTable.balance.status.${statusKey}`);\n\n return (\n <Badge variant={variant} withDot size=\"sm\" className=\"ds:tabular-nums\">\n {/* Visible: the formatted amount. The threshold meaning is colour-coded,\n so the status word is surfaced to AT as sr-only text — the bucket is\n never conveyed by colour alone. */}\n <span>{formatted}</span>\n <span className=\"ds:sr-only\">{status}</span>\n </Badge>\n );\n}\n","/**\n * care-plan-status-cell — patient-table cell renderer\n * ----------------------------------------------------------------------------\n * A thin domain preset over the data-table `StatusCellRenderer` / kit `Badge`:\n * it bakes the patient care-plan lifecycle `variantMap` so consumers don't have\n * to re-declare it on every column, and localises the label via the `ui`\n * namespace (`ui.patientTable.carePlanStatus.*`).\n *\n * Reads `PatientRow.carePlanStatus` (preferring `data.carePlanStatus`, falling\n * back to AG Grid's resolved `value`), maps it to a Badge variant:\n *\n * accepted → success estimate → info none → neutral (muted)\n *\n * GROUND-TRUTH NOTES (vs. the task brief):\n * • `StatusCellRenderer` is not exported from a barrel and is a tiny wrapper\n * around `Badge`; this preset composes `Badge` directly (same primitive,\n * no extra indirection) rather than wrapping the wrapper.\n * • The brand says the \"muted\" variant is `none`; `Badge`'s neutral variant is\n * already the muted grey chip, so `none → 'neutral'`.\n * • No event handling: a status badge is presentational, so there are no\n * provided callbacks to wire.\n */\nimport type { CustomCellRendererProps } from 'ag-grid-react';\nimport { useTranslation } from 'react-i18next';\nimport { Badge } from '../../badge/badge';\nimport type { PatientCarePlanStatus, PatientRow } from '../types';\n\n/** Badge variant baked per care-plan lifecycle state. */\ntype CarePlanBadgeVariant = 'success' | 'info' | 'neutral';\n\n/**\n * Lifecycle → Badge-variant map. Frozen so it can't be mutated by a consumer\n * holding the reference.\n */\nconst CARE_PLAN_VARIANT_MAP: Readonly<\n Record<PatientCarePlanStatus, CarePlanBadgeVariant>\n> = {\n accepted: 'success',\n estimate: 'info',\n none: 'neutral',\n};\n\n/**\n * `cellRendererParams` for `CarePlanStatusCell`.\n *\n * The lifecycle status, label source and variant map are all baked in, so the\n * only knob is the Badge size — defaults to the compact `sm` chip that fits the\n * standard AG Grid row height.\n */\nexport interface CarePlanStatusCellParams {\n /** Badge size. Defaults to `'sm'` to fit the standard row height. */\n size?: 'sm' | 'md' | 'lg';\n}\n\nfunction isCarePlanStatus(value: unknown): value is PatientCarePlanStatus {\n return value === 'accepted' || value === 'estimate' || value === 'none';\n}\n\n/**\n * Patient care-plan lifecycle badge cell.\n *\n * Pass as `cellRenderer` on the `carePlanStatus` column; the patient\n * `variantMap` and localisation are handled internally.\n */\nexport function CarePlanStatusCell(\n props: CustomCellRendererProps<PatientRow, PatientCarePlanStatus> &\n CarePlanStatusCellParams,\n) {\n const { t } = useTranslation('ui');\n const { data, value, size = 'sm' } = props;\n\n // Prefer the typed row field; fall back to AG Grid's resolved cell value.\n const raw = data?.carePlanStatus ?? value;\n if (!isCarePlanStatus(raw)) return null;\n\n const variant = CARE_PLAN_VARIANT_MAP[raw];\n\n return (\n <Badge variant={variant} withDot size={size}>\n {t(`patientTable.carePlanStatus.${raw}`)}\n </Badge>\n );\n}\n","/**\n * next-appointment-cell — patient-table cell renderer\n * ----------------------------------------------------------------------------\n * Smart upcoming-appointment cell. Renders `PatientRow.nextAppointment` as a\n * locale-aware *relative* phrase (\"in 3 days\", \"tomorrow\") via the kit\n * `Timestamp`, mirroring the `relative` preset of the data-table\n * `DateCellRenderer`. When the appointment falls within a configurable window\n * (default 7 days from now), the cell takes a token-bound emphasis treatment so\n * imminent visits stand out at a glance. When `nextAppointment` is absent it\n * renders a muted \"no upcoming\" empty-state label.\n *\n * Reads `PatientRow.nextAppointment` (preferring the typed `data.nextAppointment`\n * field, falling back to AG Grid's resolved `value`).\n *\n * GROUND-TRUTH NOTES (vs. the task brief):\n * • The brief asked for a highlight \"within 7 days\". That window is exposed as\n * `soonWithinDays` (default 7) so a column can tune it without a new renderer,\n * matching the param-driven shape of the other patient renderers.\n * • The brief named `Avatar`, `Tag`, `Icon`, `IconButton`, `Tooltip` as\n * candidate primitives. This cell is purely presentational (a date + optional\n * emphasis), so it composes only `Timestamp` plus a decorative lucide glyph —\n * the same minimal-primitive approach the analogous `CarePlanStatusCell`\n * takes. There is no `Icon`/`IconButton` standalone component in the kit\n * (renderers use `lucide-react` glyphs directly), and no row callback is\n * provided, so no interactive affordance is wired.\n * • The emphasis is a CVA `tone` variant bound entirely to design tokens\n * (`--warning-foreground`); NO colour is hardcoded and nothing is pushed into\n * AG Grid `cellStyle`/`cellClass`.\n * • PHI rule (PRS 26 §6): no row field reaches a DOM `data-*` attribute here.\n */\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { CalendarClock } from 'lucide-react';\nimport type { CustomCellRendererProps } from 'ag-grid-react';\nimport { useTranslation } from 'react-i18next';\nimport { Timestamp } from '../../timestamp';\nimport type { PatientRow } from '../types';\n\n/* ------------------------------------------------------------------ */\n/* CVA — cell wrapper */\n/* */\n/* `tone` is the only stylistic axis: `soon` lifts the row to a */\n/* token-bound warning emphasis, `default` inherits the cell colour. */\n/* ------------------------------------------------------------------ */\n\nconst nextAppointmentVariants = cva(\n 'ds:inline-flex ds:items-center ds:gap-[var(--spacing-xs)] ds:whitespace-nowrap',\n {\n variants: {\n tone: {\n default: '',\n // Imminent visit: warning foreground token + medium weight. Fully\n // token-bound — no literal colour, no AG Grid cellStyle.\n soon: 'ds:text-[color:var(--warning-foreground)] ds:font-medium',\n // Absent appointment: muted empty state.\n empty: 'ds:text-[color:var(--muted-foreground)]',\n },\n },\n defaultVariants: { tone: 'default' },\n },\n);\n\ntype NextAppointmentVariantProps = VariantProps<typeof nextAppointmentVariants>;\n\n/* ------------------------------------------------------------------ */\n/* Params */\n/* ------------------------------------------------------------------ */\n\n/**\n * `cellRendererParams` for {@link NextAppointmentCell}.\n *\n * Localisation and the relative-date formatting are handled internally; the\n * only knob is the imminence window.\n */\nexport interface NextAppointmentCellParams {\n /**\n * Highlight the cell when the appointment is at most this many days away\n * (and not in the past). Defaults to `7`. A value `<= 0` disables the\n * highlight.\n */\n soonWithinDays?: number;\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\nconst DEFAULT_SOON_WITHIN_DAYS = 7;\n\nfunction toTime(value: unknown): number | null {\n if (value == null || value === '') return null;\n const t =\n value instanceof Date\n ? value.getTime()\n : typeof value === 'number'\n ? value\n : new Date(String(value)).getTime();\n return Number.isNaN(t) ? null : t;\n}\n\n/**\n * Resolve the emphasis tone for a parsed appointment time. `soon` covers any\n * upcoming visit within the window (including today); past appointments fall\n * back to `default`.\n */\nfunction resolveTone(\n time: number,\n soonWithinDays: number,\n): NonNullable<NextAppointmentVariantProps['tone']> {\n if (soonWithinDays <= 0) return 'default';\n const deltaMs = time - Date.now();\n if (deltaMs < 0) return 'default';\n return deltaMs <= soonWithinDays * DAY_MS ? 'soon' : 'default';\n}\n\n/* ------------------------------------------------------------------ */\n/* NextAppointmentCell */\n/* ------------------------------------------------------------------ */\n\n/**\n * Smart upcoming-appointment cell.\n *\n * Pass as `cellRenderer` on the `nextAppointment` column; the relative\n * formatting, imminence emphasis and empty state are handled internally.\n */\nexport function NextAppointmentCell(\n props: CustomCellRendererProps<PatientRow, string> &\n NextAppointmentCellParams,\n) {\n const { t } = useTranslation('ui');\n const { data, value, soonWithinDays = DEFAULT_SOON_WITHIN_DAYS } = props;\n\n // Prefer the typed row field; fall back to AG Grid's resolved cell value.\n const time = toTime(data?.nextAppointment ?? value);\n\n if (time === null) {\n return (\n <span className={nextAppointmentVariants({ tone: 'empty' })}>\n {t('patientTable.nextAppointment.none')}\n </span>\n );\n }\n\n const tone = resolveTone(time, soonWithinDays);\n\n return (\n <span className={nextAppointmentVariants({ tone })}>\n {tone === 'soon' ? (\n <CalendarClock aria-hidden=\"true\" className=\"ds:size-3.5 ds:shrink-0\" />\n ) : null}\n <Timestamp value={time} format=\"relative\" shape=\"bare\" />\n </span>\n );\n}\n","/**\n * patient-table — column definitions\n * ----------------------------------------------------------------------------\n * `buildPatientColumns(t, opts)` returns the ordered `ColDef<PatientRow>[]` for\n * the patient grid. Every patient field is its OWN column — sortable, filterable\n * and individually hideable via ColumnToggle — rather than several fields jumbled\n * into one composite cell. Each column wires the right kit cell renderer + the\n * right kit filter + floatingFilter, a localised `headerName` (from\n * `ui.patientTable.column.*`) and a stable `colId`.\n *\n * Identity is SPLIT across three pinned-start columns (the spec's\n * \"Avatar · First · Last\"):\n * • `avatar` — ImageCellRenderer (photo, initials fallback), sr-only header.\n * • `firstName` — plain text.\n * • `lastName` — LinkCellRenderer (the patient-detail link, `identityHref`).\n * All three are pinned-start P0 anchors and never auto-hide.\n *\n * COLUMN VISIBILITY\n * -----------------\n * `●` default-visible columns carry a `responsiveTier`; the responsive hook hides\n * them by tier as the grid narrows. Every other (grouped) column ships\n * `hide: true` and NO tier, so the hook leaves it alone and a user ColumnToggle\n * override wins. The anchors (avatar / firstName / lastName / actions) omit a\n * tier so they are never auto-hidden.\n *\n * RESPONSIVE TIER MECHANISM\n * -------------------------\n * Each column may carry a custom `responsiveTier` (`P0`–`P3`) property. AG Grid's\n * `ColDef` is an open/extensible shape — extra properties pass through untouched\n * at runtime — but its TS type is closed, so we type each entry as\n * `PatientColDefWithTier` and read the tier back via {@link getResponsiveTier}\n * (no `any`, no reliance on AG Grid's loose `context`).\n *\n * CUSTOM FIELDS\n * -------------\n * `opts.customFields` appends one dynamic column per descriptor after the\n * standard columns: `colId: \\`custom:${fieldName}\\``, a LITERAL `headerName`\n * (the practice-defined label — never i18n-keyed), a `valueGetter` reading\n * `row.customFields?.[fieldName]`, and a renderer + filter chosen by `type`\n * (`text` → plain text · `checkbox` → ✓/— · `dropdown` → Tag chip). All ship\n * `hide: true` and no tier.\n *\n * CONSTRAINTS:\n * • No colour / spacing / radius / font literal in any `cellStyle`/`cellClass`\n * here — only token-bound `ds:` utility classes — so the AG Grid theme bridge\n * stays in control of theming.\n * • CSS logical properties only.\n * • PHI rule (PRS 26 §6): no `PatientRow` field other than `id` is ever placed\n * in a DOM `data-*` attribute. Renderers surface name/contact/DOB as text\n * content only; these defs pass values, not attributes.\n */\nimport type { TFunction } from 'i18next';\nimport { Eye, Mail, MessageSquare, Pencil, Trash2, Users } from 'lucide-react';\nimport { createElement } from 'react';\nimport {\n ActionsCellRenderer,\n actionsColumnWidth,\n CurrencyCellRenderer,\n DateCellRenderer,\n DateRangeFilter,\n DateRangeFloatingFilter,\n ImageCellRenderer,\n LinkCellRenderer,\n NumberFilter,\n NumberFloatingFilter,\n SelectFilter,\n SelectFloatingFilter,\n StatusCellRenderer,\n TagListCellRenderer,\n TextFilter,\n TextFloatingFilter,\n TypeaheadFilter,\n TypeaheadFloatingFilter,\n UserCellRenderer,\n type ActionDef,\n type StatusCellRendererParams,\n type UserCellValue,\n} from '../data-table';\nimport { BalanceBadgeCell } from './cell-renderers/balance-badge-cell';\nimport { CarePlanStatusCell } from './cell-renderers/care-plan-status-cell';\nimport { NextAppointmentCell } from './cell-renderers/next-appointment-cell';\nimport type {\n PatientColDef,\n PatientColumnDef,\n PatientColumnId,\n PatientCustomFieldDef,\n PatientRow,\n PatientRowAction,\n ResponsiveTier,\n} from './types';\n\n/* -------------------------------------------------------------------------- */\n/* Responsive-tier extension */\n/* -------------------------------------------------------------------------- */\n\n/**\n * A patient leaf column carrying its responsive-priority tier. AG Grid passes\n * the extra `responsiveTier` key through untouched; we keep it strongly typed\n * (no `any`, no reliance on AG Grid's `context: any`) so the responsive hook can\n * read it back via {@link getResponsiveTier}.\n */\nexport type PatientColDefWithTier = PatientColDef & {\n /**\n * Responsive priority. Lower numbers survive longer as the grid narrows:\n * `P0` always visible, `P3` first to hide. Consumed by `useResponsiveColumns`.\n */\n responsiveTier?: ResponsiveTier;\n};\n\n/**\n * Read the responsive tier off a column definition. Returns `undefined` for\n * columns that opt out of responsive toggling — the pinned anchors and every\n * `hide: true` grouped column deliberately omit a tier so they are never\n * auto-managed by the responsive hook.\n */\nexport function getResponsiveTier(\n colDef: PatientColumnDef,\n): ResponsiveTier | undefined {\n return (colDef as PatientColDefWithTier).responsiveTier;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Options */\n/* -------------------------------------------------------------------------- */\n\n/** Options threaded into {@link buildPatientColumns}. */\nexport interface BuildPatientColumnsOptions {\n /**\n * Mask sensitive contact details (email) — when `true` the `email` column\n * ships hidden. Forwarded from `PatientTable`'s `hidePatientDetails`. Default\n * `false`.\n */\n hideDetails?: boolean;\n /**\n * Live getter for the actions-column density. When it returns `true`, the\n * `ActionsCellRenderer` collapses its inline buttons into a single \"…\" overflow\n * menu (the spec's 180→88px compact density). Reading via a getter lets a\n * density toggle flip without rebuilding column defs (preserves column state) —\n * call `api.refreshCells({ force: true })` after flipping the backing value.\n * Defaults to a getter that always returns `false`.\n */\n getActionsCompact?: () => boolean;\n /**\n * Invoked when a per-row action button is activated, with the resolved action\n * descriptor and the patient row. Only `row.id` ever leaves this boundary into\n * persistence / agent surfaces — never PHI.\n */\n onRowAction?: (action: PatientRowAction, patient: PatientRow) => void;\n /**\n * Derive the patient-detail href for the `lastName` link column, e.g.\n * `(row) => \\`/patients/${row.id}\\``. When omitted the lastName cell renders a\n * button that calls `onRowAction` with the `show` action instead.\n */\n identityHref?: string | ((row: PatientRow) => string | undefined);\n /**\n * Per-practice custom-field descriptors. Each appends one dynamic `custom:*`\n * column (hidden by default) after the standard columns. See\n * {@link PatientCustomFieldDef}.\n */\n customFields?: PatientCustomFieldDef[];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Row actions */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The fixed number of per-row actions the patient grid builds (show · edit ·\n * email reminder · sms reminder · reassign · delete — see {@link buildRowActions}).\n * Exposed so the component can size the actions column to its EXPANDED footprint\n * (`actionsColumnWidth(PATIENT_ROW_ACTION_COUNT)`) when flipping the runtime\n * compact-actions toggle off, without rebuilding the column defs. Keep in sync\n * with the array {@link buildRowActions} returns.\n */\nexport const PATIENT_ROW_ACTION_COUNT = 6;\n\n/**\n * Build the per-row `ActionDef`s for the actions column. Each is mapped from the\n * spec's closed action set; `hidden` / `disabled` predicates encode the spec's\n * per-row rules (e.g. `sendEmailReminder` hidden when `!emailEnabled`).\n *\n * Bridges the kit `ActionDef<PatientRow>` shape (icon/label/onClick) to the\n * patient `onRowAction(action, patient)` contract by re-deriving a\n * `PatientRowAction` descriptor on click.\n */\nfunction buildRowActions(\n t: TFunction,\n onRowAction: BuildPatientColumnsOptions['onRowAction'],\n): ActionDef<PatientRow>[] {\n const dispatch =\n (action: PatientRowAction) =>\n (patient: PatientRow): void => {\n onRowAction?.(action, patient);\n };\n\n // Each entry pairs the kit ActionDef (what the renderer needs) with the\n // patient-typed descriptor handed back to `onRowAction`.\n const make = (\n action: PatientRowAction,\n overrides: Omit<ActionDef<PatientRow>, 'label' | 'onClick' | 'icon'>,\n ): ActionDef<PatientRow> => ({\n icon: action.icon,\n label: action.label,\n variant: action.variant,\n onClick: dispatch(action),\n ...overrides,\n });\n\n return [\n make(\n {\n id: 'show',\n label: t('patientTable.actions.show'),\n icon: createElement(Eye, { 'aria-hidden': 'true' }),\n },\n {},\n ),\n make(\n {\n id: 'edit',\n label: t('patientTable.actions.edit'),\n icon: createElement(Pencil, { 'aria-hidden': 'true' }),\n },\n {},\n ),\n make(\n {\n id: 'sendEmailReminder',\n label: t('patientTable.actions.sendEmailReminder'),\n icon: createElement(Mail, { 'aria-hidden': 'true' }),\n },\n // Hidden when the patient has email contact disabled (spec).\n { hidden: (patient) => !patient.emailEnabled },\n ),\n make(\n {\n id: 'sendSmsReminder',\n label: t('patientTable.actions.sendSmsReminder'),\n icon: createElement(MessageSquare, { 'aria-hidden': 'true' }),\n },\n // Hidden when no phone number is SMS-enabled (spec).\n {\n hidden: (patient) =>\n !(patient.phoneNumbers ?? []).some((p) => p.smsEnabled),\n },\n ),\n make(\n {\n id: 'reassign',\n label: t('patientTable.actions.reassign'),\n icon: createElement(Users, { 'aria-hidden': 'true' }),\n },\n {},\n ),\n make(\n {\n id: 'delete',\n label: t('patientTable.actions.delete'),\n icon: createElement(Trash2, { 'aria-hidden': 'true' }),\n variant: 'destructive',\n },\n // Disabled (with a reason) for archived patients — the row model's\n // closest \"cannot delete\" signal. Surfaced as the inline tooltip.\n {\n disabled: (patient) => Boolean(patient.isArchived),\n disabledReason: t('patientTable.actions.deleteLocked'),\n },\n ),\n ];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------------- */\n\nconst NO = () => false;\n\n/** Glyph for boolean / eligibility valueFormatter (✓ when true, — otherwise). */\nconst CHECK = '✓';\nconst DASH = '—';\n\n/** Compute integer age (years) from an ISO date-of-birth string. */\nfunction ageFromDob(dob: string | undefined): number | null {\n if (!dob) return null;\n const birth = new Date(dob);\n const ms = birth.getTime();\n if (Number.isNaN(ms)) return null;\n const now = new Date();\n let age = now.getFullYear() - birth.getFullYear();\n const m = now.getMonth() - birth.getMonth();\n if (m < 0 || (m === 0 && now.getDate() < birth.getDate())) age -= 1;\n return age >= 0 ? age : null;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Custom-field columns */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Build the dynamic `custom:*` columns from the practice descriptors. Each is\n * appended hidden (no tier) after the standard columns: a literal `headerName`\n * (the descriptor's `label`, never i18n-keyed), a `valueGetter` reading\n * `row.customFields?.[fieldName]`, and a renderer + filter chosen by `type`.\n */\nfunction buildCustomFieldColumns(\n fields: PatientCustomFieldDef[],\n): PatientColDefWithTier[] {\n return fields.map((field): PatientColDefWithTier => {\n const colId: PatientColumnId = `custom:${field.fieldName}`;\n const base: PatientColDefWithTier = {\n colId,\n headerName: field.label,\n hide: true,\n // No tier — custom fields are opt-in via ColumnToggle only.\n };\n\n if (field.type === 'checkbox') {\n return {\n ...base,\n valueGetter: (params) =>\n params.data?.customFields?.[field.fieldName] === true,\n valueFormatter: (params) => (params.value === true ? CHECK : DASH),\n filter: SelectFilter,\n floatingFilterComponent: SelectFloatingFilter,\n filterParams: {\n options: [\n { value: true, label: CHECK },\n { value: false, label: DASH },\n ],\n },\n minWidth: 110,\n width: 130,\n };\n }\n\n if (field.type === 'dropdown') {\n return {\n ...base,\n // TagListCellRenderer expects an array; wrap the single value.\n valueGetter: (params) => {\n const raw = params.data?.customFields?.[field.fieldName];\n return typeof raw === 'string' && raw ? [raw] : [];\n },\n cellRenderer: TagListCellRenderer,\n filterValueGetter: (params) => {\n const raw = params.data?.customFields?.[field.fieldName];\n return typeof raw === 'string' ? raw : '';\n },\n filter: SelectFilter,\n floatingFilterComponent: SelectFloatingFilter,\n filterParams: {\n // Undefined (not []) when the field declares no options, so the\n // filter derives its choices from the rows. An empty array would\n // mean \"this column has no facets\" and leave the filter unusable.\n options: field.options?.map((opt) => ({\n value: opt,\n label: opt,\n })),\n },\n sortable: false,\n minWidth: 130,\n width: 160,\n };\n }\n\n // text\n return {\n ...base,\n valueGetter: (params) => {\n const raw = params.data?.customFields?.[field.fieldName];\n return typeof raw === 'string' ? raw : '';\n },\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 140,\n width: 170,\n };\n });\n}\n\n/* -------------------------------------------------------------------------- */\n/* buildPatientColumns */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Build the ordered patient-grid column definitions — one field per column.\n *\n * @param t `react-i18next` translation fn (default `ui` namespace).\n * @param opts See {@link BuildPatientColumnsOptions} — density getter, row-action\n * callback, detail masking, identity href, custom fields.\n * @returns Ordered `PatientColumnDef[]`. Default-visible columns carry a\n * `responsiveTier` (read via {@link getResponsiveTier}); the pinned\n * anchors and every grouped (`hide: true`) column omit it so they are\n * never auto-hidden.\n */\nexport function buildPatientColumns(\n t: TFunction,\n opts: BuildPatientColumnsOptions = {},\n): PatientColumnDef[] {\n const {\n hideDetails = false,\n getActionsCompact = NO,\n onRowAction,\n identityHref,\n customFields = [],\n } = opts;\n\n const actions = buildRowActions(t, onRowAction);\n\n const genderLabels: Record<NonNullable<PatientRow['gender']>, string> = {\n m: t('patientTable.gender.m'),\n f: t('patientTable.gender.f'),\n x: t('patientTable.gender.x'),\n };\n\n // Tri-state marketing-consent → label + Badge variant (StatusCellRenderer).\n const consentLabels = {\n yes: t('patientTable.consent.yes'),\n no: t('patientTable.consent.no'),\n notSpecified: t('patientTable.consent.notSpecified'),\n };\n const consentVariantMap: StatusCellRendererParams['variantMap'] = {\n [consentLabels.yes]: 'success',\n [consentLabels.no]: 'error',\n [consentLabels.notSpecified]: 'neutral',\n };\n\n const header = (id: PatientColumnId): string =>\n t(`patientTable.column.${id}`);\n\n const columns: PatientColDefWithTier[] = [\n /* ── Avatar (P0, pinned-start) ─────────────────────────────────── */\n {\n colId: 'avatar',\n // sr-only header — the photo column needs no visible label, but AG Grid\n // and AT still want a name. `headerName` reads \"Photo\"; the visible header\n // TEXT is clipped via the kit's `sr-only-th` convention (clips only\n // `.ag-header-cell-text`, keeping the header-cell box in the grid chrome\n // and the name in the a11y tree — see ag-grid-theme.css).\n headerName: header('avatar'),\n headerClass: 'sr-only-th',\n pinned: 'left',\n cellRenderer: ImageCellRenderer,\n cellRendererParams: {\n srcField: 'avatarUrl',\n fallbackField: 'lastName',\n size: 'sm',\n shape: 'circle',\n },\n sortable: false,\n filter: false,\n resizable: false,\n // Hug the 32px (size=\"sm\") avatar + cell padding — a tight fixed column,\n // not a wide empty one. width === minWidth === maxWidth pins it to size so\n // the pinned group can't stretch it (pinned columns never flex).\n //\n // 48 = 32 (avatar) + 16 (--spacing-sm each side, the reduced padding the\n // theme gives graphic-only cells). The old 52 assumed 20px of padding\n // against a cell that actually had 30px, so the avatar clipped by 10px.\n width: 48,\n minWidth: 48,\n maxWidth: 48,\n // Anchor — never auto-hidden (no tier).\n },\n\n /* ── First name (P0, pinned-start) — plain text ────────────────── */\n {\n colId: 'firstName',\n headerName: header('firstName'),\n pinned: 'left',\n field: 'firstName',\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n // Size to content: width === minWidth so the pinned column sits tight with\n // no dead space before the next pinned column (pinned columns can't flex).\n width: 140,\n minWidth: 120,\n // Anchor — never auto-hidden (no tier).\n },\n\n /* ── Last name (P0, pinned-start) — the patient-detail link ────── */\n {\n colId: 'lastName',\n headerName: header('lastName'),\n pinned: 'left',\n field: 'lastName',\n cellRenderer: LinkCellRenderer,\n cellRendererParams: {\n href: identityHref,\n // When no detail href is supplied the cell renders a button; route its\n // click through the same `show` row action.\n onClick: onRowAction\n ? (row: PatientRow) =>\n onRowAction(\n { id: 'show', label: t('patientTable.actions.show') },\n row,\n )\n : undefined,\n },\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n // Size to content: a modest pinned width with no stretched default, so the\n // avatar + first + last group ends tight against the scrollable columns.\n width: 150,\n minWidth: 130,\n // Anchor — never auto-hidden (no tier).\n },\n\n /* ── Phone (P1) ────────────────────────────────────────────────── */\n {\n colId: 'phone',\n headerName: header('phone'),\n responsiveTier: 'P1',\n // The primary phone number string (prefix + number).\n valueGetter: (params) => params.data?.phoneNumbers?.[0]?.number ?? '',\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 160,\n width: 180,\n },\n\n /* ── Next appointment (P1) ─────────────────────────────────────── */\n {\n colId: 'nextAppointment',\n headerName: header('nextAppointment'),\n responsiveTier: 'P1',\n field: 'nextAppointment',\n cellRenderer: NextAppointmentCell,\n filter: DateRangeFilter,\n floatingFilterComponent: DateRangeFloatingFilter,\n minWidth: 160,\n width: 180,\n },\n\n /* ── Unpaid balance (P1) ───────────────────────────────────────── */\n {\n colId: 'unpaidBalance',\n headerName: header('unpaidBalance'),\n responsiveTier: 'P1',\n field: 'unpaidAmount',\n type: 'numericColumn',\n cellRenderer: BalanceBadgeCell,\n filter: NumberFilter,\n floatingFilterComponent: NumberFloatingFilter,\n // Money is integer cents; the inRange filter compares cents directly.\n filterParams: { defaultOperator: 'inRange', min: 0 },\n minWidth: 150,\n width: 170,\n },\n\n /* ── Email (P2) — hidden when masking ──────────────────────────── */\n {\n colId: 'email',\n headerName: header('email'),\n responsiveTier: 'P2',\n field: 'email',\n // Emit `hide` ONLY when masking (never a literal `hide: false`). A static\n // `hide: false` is re-applied by AG Grid on every `columnDefs` reconcile,\n // clobbering a runtime hide from the ViewSwitcher / responsive hook.\n hide: hideDetails || undefined,\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 200,\n width: 240,\n },\n\n /* ── Care-plan status (P2) ─────────────────────────────────────── */\n {\n colId: 'carePlanStatus',\n headerName: header('carePlanStatus'),\n responsiveTier: 'P2',\n field: 'carePlanStatus',\n cellRenderer: CarePlanStatusCell,\n filter: SelectFilter,\n floatingFilterComponent: SelectFloatingFilter,\n filterParams: {\n options: [\n {\n value: 'accepted',\n label: t('patientTable.carePlanStatus.accepted'),\n },\n {\n value: 'estimate',\n label: t('patientTable.carePlanStatus.estimate'),\n },\n { value: 'none', label: t('patientTable.carePlanStatus.none') },\n ],\n },\n sortable: false,\n minWidth: 130,\n width: 150,\n },\n\n /* ── Fiscal code (P3, text) ────────────────────────────────────── */\n {\n colId: 'fiscalCode',\n headerName: header('fiscalCode'),\n responsiveTier: 'P3',\n field: 'italianFiscalCode',\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 160,\n width: 180,\n },\n\n /* ── Age (P3, derived numeric) ─────────────────────────────────── */\n {\n colId: 'age',\n headerName: header('age'),\n responsiveTier: 'P3',\n // Left-aligned like the text columns — `numericColumn` (which right-aligns\n // header + cell) is for ledger figures, not a 2-digit age. Sort stays\n // numeric because the valueGetter returns a number.\n valueGetter: (params) =>\n ageFromDob(params.data?.dateOfBirth) ?? undefined,\n filter: NumberFilter,\n floatingFilterComponent: NumberFloatingFilter,\n minWidth: 90,\n width: 110,\n },\n\n /* ── Gender (P3) ───────────────────────────────────────────────── */\n {\n colId: 'gender',\n headerName: header('gender'),\n responsiveTier: 'P3',\n // Map the raw code to a localised label for both cell + set filter.\n valueGetter: (params) =>\n params.data?.gender ? genderLabels[params.data.gender] : DASH,\n filter: SelectFilter,\n floatingFilterComponent: SelectFloatingFilter,\n filterParams: {\n options: [\n { value: genderLabels.m, label: genderLabels.m },\n { value: genderLabels.f, label: genderLabels.f },\n { value: genderLabels.x, label: genderLabels.x },\n ],\n },\n minWidth: 110,\n width: 130,\n },\n\n /* ── Source (P3) ───────────────────────────────────────────────── */\n {\n colId: 'source',\n headerName: header('source'),\n responsiveTier: 'P3',\n // TagListCellRenderer expects an array; wrap the resolved source string.\n valueGetter: (params) => {\n const s = params.data?.source ?? params.data?.customSource;\n return s ? [s] : [];\n },\n cellRenderer: TagListCellRenderer,\n filterValueGetter: (params) =>\n params.data?.source ?? params.data?.customSource ?? '',\n filter: SelectFilter,\n floatingFilterComponent: SelectFloatingFilter,\n sortable: false,\n minWidth: 120,\n width: 150,\n },\n\n /* ── Grouped: Identity — hidden by default ─────────────────────── */\n {\n colId: 'dateOfBirth',\n headerName: header('dateOfBirth'),\n hide: true,\n field: 'dateOfBirth',\n cellRenderer: DateCellRenderer,\n cellRendererParams: { format: 'date' },\n filter: DateRangeFilter,\n floatingFilterComponent: DateRangeFloatingFilter,\n minWidth: 140,\n width: 160,\n },\n {\n colId: 'placeOfBirth',\n headerName: header('placeOfBirth'),\n hide: true,\n field: 'placeOfBirth',\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 140,\n width: 160,\n },\n {\n colId: 'countryOfBirth',\n headerName: header('countryOfBirth'),\n hide: true,\n field: 'countryOfBirth',\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 130,\n width: 150,\n },\n\n /* ── Grouped: Contact — hidden by default ──────────────────────── */\n {\n colId: 'smsReminders',\n headerName: header('smsReminders'),\n hide: true,\n valueGetter: (params) => Boolean(params.data?.smsReminderEligible),\n valueFormatter: (params) => (params.value === true ? CHECK : DASH),\n filter: SelectFilter,\n floatingFilterComponent: SelectFloatingFilter,\n filterParams: {\n options: [\n { value: true, label: CHECK },\n { value: false, label: DASH },\n ],\n },\n minWidth: 120,\n width: 140,\n },\n {\n colId: 'emailReminders',\n headerName: header('emailReminders'),\n hide: true,\n valueGetter: (params) => Boolean(params.data?.emailReminderEligible),\n valueFormatter: (params) => (params.value === true ? CHECK : DASH),\n filter: SelectFilter,\n floatingFilterComponent: SelectFloatingFilter,\n filterParams: {\n options: [\n { value: true, label: CHECK },\n { value: false, label: DASH },\n ],\n },\n minWidth: 120,\n width: 140,\n },\n {\n colId: 'pec',\n headerName: header('pec'),\n hide: true,\n field: 'pecAddress',\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 200,\n width: 240,\n },\n\n /* ── Grouped: Appointments — hidden by default ─────────────────── */\n {\n colId: 'lastVisit',\n headerName: header('lastVisit'),\n hide: true,\n field: 'lastAppointment',\n cellRenderer: DateCellRenderer,\n cellRendererParams: { format: 'relative' },\n filter: DateRangeFilter,\n floatingFilterComponent: DateRangeFloatingFilter,\n minWidth: 140,\n width: 160,\n },\n\n /* ── Grouped: Financial — hidden by default ────────────────────── */\n {\n colId: 'billableAmount',\n headerName: header('billableAmount'),\n hide: true,\n type: 'numericColumn',\n cellRenderer: CurrencyCellRenderer,\n // Cents → major unit at the render boundary. No `field` — the valueGetter\n // takes precedence over `field` in AG Grid for both cell + sort, so a\n // `field: 'billableAmount'` would be dead (and misleadingly imply the raw\n // cents drive the renderer).\n valueGetter: (params) =>\n params.data ? params.data.billableAmount / 100 : undefined,\n filter: NumberFilter,\n floatingFilterComponent: NumberFloatingFilter,\n minWidth: 140,\n width: 160,\n },\n {\n colId: 'discount',\n headerName: header('discount'),\n hide: true,\n field: 'defaultDiscount',\n type: 'numericColumn',\n valueFormatter: (params) =>\n params.value == null ? '' : `${params.value}%`,\n filter: NumberFilter,\n floatingFilterComponent: NumberFloatingFilter,\n minWidth: 110,\n width: 130,\n },\n\n /* ── Grouped: Clinical — hidden by default ─────────────────────── */\n {\n colId: 'diagnosis',\n headerName: header('diagnosis'),\n hide: true,\n field: 'diagnosis',\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 180,\n width: 220,\n },\n {\n colId: 'insurance',\n headerName: header('insurance'),\n hide: true,\n field: 'defaultInsurance',\n filter: SelectFilter,\n floatingFilterComponent: SelectFloatingFilter,\n minWidth: 140,\n width: 160,\n },\n {\n colId: 'assignedTo',\n headerName: header('assignedTo'),\n hide: true,\n cellRenderer: UserCellRenderer,\n valueGetter: (params) =>\n params.data?.personalAssistant\n ? ({ name: params.data.personalAssistant } satisfies UserCellValue)\n : null,\n comparator: (a: UserCellValue | null, b: UserCellValue | null) =>\n (a?.name ?? '').localeCompare(b?.name ?? ''),\n filterValueGetter: (params) => params.data?.personalAssistant ?? '',\n filter: TypeaheadFilter,\n floatingFilterComponent: TypeaheadFloatingFilter,\n minWidth: 160,\n width: 180,\n },\n {\n colId: 'job',\n headerName: header('job'),\n hide: true,\n field: 'job',\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 140,\n width: 160,\n },\n\n /* ── Grouped: Admin — hidden by default ────────────────────────── */\n {\n colId: 'marketingConsent',\n headerName: header('marketingConsent'),\n hide: true,\n cellRenderer: StatusCellRenderer,\n cellRendererParams: {\n variantMap: consentVariantMap,\n } satisfies StatusCellRendererParams,\n valueGetter: (params) => {\n switch (params.data?.marketingConsent) {\n case 1:\n return consentLabels.yes;\n case 0:\n return consentLabels.no;\n default:\n return consentLabels.notSpecified;\n }\n },\n filter: SelectFilter,\n floatingFilterComponent: SelectFloatingFilter,\n filterParams: {\n options: [\n { value: consentLabels.yes, label: consentLabels.yes },\n { value: consentLabels.no, label: consentLabels.no },\n {\n value: consentLabels.notSpecified,\n label: consentLabels.notSpecified,\n },\n ],\n },\n sortable: false,\n minWidth: 150,\n width: 170,\n },\n {\n colId: 'patientNumber',\n headerName: header('patientNumber'),\n hide: true,\n // `YYYY/N` yearly numbering.\n valueGetter: (params) => {\n const year = params.data?.yearlyNumberingYear;\n const num = params.data?.yearlyNumberingNumber;\n return year != null && num != null ? `${year}/${num}` : '';\n },\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 120,\n width: 140,\n },\n {\n colId: 'portal',\n headerName: header('portal'),\n hide: true,\n valueGetter: (params) => Boolean(params.data?.mydentista),\n valueFormatter: (params) => (params.value === true ? CHECK : DASH),\n filter: SelectFilter,\n floatingFilterComponent: SelectFloatingFilter,\n filterParams: {\n options: [\n { value: true, label: CHECK },\n { value: false, label: DASH },\n ],\n },\n minWidth: 110,\n width: 130,\n },\n {\n colId: 'createdAt',\n headerName: header('createdAt'),\n hide: true,\n field: 'createdAt',\n cellRenderer: DateCellRenderer,\n cellRendererParams: { format: 'date' },\n filter: DateRangeFilter,\n floatingFilterComponent: DateRangeFloatingFilter,\n minWidth: 140,\n width: 160,\n },\n\n /* ── Grouped: Address — hidden by default ──────────────────────── */\n {\n colId: 'city',\n headerName: header('city'),\n hide: true,\n field: 'city',\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 120,\n width: 150,\n },\n {\n colId: 'province',\n headerName: header('province'),\n hide: true,\n field: 'province',\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 100,\n width: 120,\n },\n {\n colId: 'postcode',\n headerName: header('postcode'),\n hide: true,\n field: 'postcode',\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 110,\n width: 130,\n },\n {\n colId: 'country',\n headerName: header('country'),\n hide: true,\n field: 'countryCode',\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 110,\n width: 130,\n },\n {\n colId: 'street',\n headerName: header('street'),\n hide: true,\n field: 'street',\n filter: TextFilter,\n floatingFilterComponent: TextFloatingFilter,\n minWidth: 180,\n width: 220,\n },\n\n /* ── Custom fields (per-practice, dynamic) — appended, hidden ──── */\n ...buildCustomFieldColumns(customFields),\n\n /* ── Actions (P0, pinned-end) ──────────────────────────────────── */\n {\n colId: 'actions',\n headerName: header('actions'),\n // Pinned to the inline-end edge so actions stay reachable while the field\n // columns scroll. AG Grid `pinned` is physical — the grid's `enableRtl`\n // flips this to the start edge under RTL. Locked: neither a header drag\n // nor a restored column-state snapshot may move it off the edge.\n pinned: 'right',\n lockPinned: true,\n suppressMovable: true,\n // Header + action group left-aligned (justify-start), consistent with every\n // other column — no right-aligned header (the actions still sit at the\n // pinned inline-end edge, the group just starts at the cell's leading edge).\n cellClass: 'ds:justify-start',\n cellRenderer: ActionsCellRenderer,\n cellRendererParams: {\n actions,\n intent: 'ghost',\n group: true,\n label: t('patientTable.actions.group'),\n collapsed: getActionsCompact,\n },\n sortable: false,\n filter: false,\n resizable: false,\n // The renderer collapses to a single overflow trigger via\n // `getActionsCompact`; the expanded width is DERIVED from the action count\n // so a row showing every action can't clip — 88px collapsed → fits-all.\n width: actionsColumnWidth(actions.length),\n minWidth: 88,\n // Anchor — never auto-hidden (no tier).\n },\n ];\n\n return columns;\n}\n","/**\n * patient-table — responsive column hook\n * ----------------------------------------------------------------------------\n * `useResponsiveColumns(handleRef, opts)` toggles column visibility by the\n * `responsiveTier` (`P0`–`P3`) baked onto each `ColDef` by `buildPatientColumns`,\n * driven by the *grid container's* width — measured with a `ResizeObserver`, NOT\n * the viewport — so the table responds correctly when embedded in a narrow panel,\n * split pane or sidebar rather than only at browser-window breakpoints.\n *\n * BREAKPOINTS (container inline-size → highest tier kept visible):\n *\n * < 640px → P0 only (pinned avatar / firstName / lastName / actions)\n * < 900px → P0 + P1 (+ phone, nextAppointment, balance)\n * < 1200px → P0 + P1 + P2 (+ email, carePlanStatus)\n * ≥ 1200px → all tiers (P0–P3) (+ fiscalCode, age, gender, source)\n *\n * Columns without a tier (the pinned avatar / firstName / lastName / actions\n * anchors, and every grouped `hide: true` column) are never touched — the\n * anchors stay visible at every width; the grouped columns are owned by\n * ColumnToggle only.\n *\n * RESPONSIVE ↔ PERSISTENCE RECONCILIATION\n * ---------------------------------------\n * `data-table.tsx` persists the full AG Grid column state to\n * `localStorage[\"data-table-col-state:<gridId>\"]` (load on grid-ready, save on\n * unmount). A user's explicit `ColumnToggle` choice lives in that persisted state\n * and MUST win over — and survive — the responsive baseline. Reconciliation:\n *\n * 1. The responsive baseline is exactly that — a *baseline*. We only auto\n * show/hide a column the user has NOT manually toggled this session.\n * 2. We track a `Set<colId>` of user-touched columns. We learn of a manual\n * toggle from the grid's `columnVisible` event: any such event whose\n * `source` is NOT `'api'` is user-driven (ColumnToggle tool panel, column\n * menu, context menu, drag), so we add those colIds to the set. Our own\n * responsive calls go through `setColumnsVisible(..., 'api')`, so they never\n * pollute the set (and we additionally guard with a re-entrancy flag).\n * 3. Once a column is user-touched, the hook leaves it alone forever this\n * session — its visibility is whatever the user chose, which is what\n * `data-table.tsx` then persists on unmount. The persisted choice therefore\n * wins on the next mount too: we seed the user-touched set from the saved\n * column state so a previously hidden/shown column is treated as a manual\n * override and not clobbered by the baseline on re-entry.\n *\n * CONSTRAINTS: no `any` (AG Grid's `ColumnEventType` is read off the typed event,\n * tier read via the typed `getResponsiveTier` helper). No DOM `data-*` attribute\n * is written here (PHI rule is moot — this hook only reads colIds and widths).\n */\nimport { useCallback, useEffect, useRef, type RefObject } from 'react';\nimport type { Column, ColumnVisibleEvent } from 'ag-grid-community';\nimport { getResponsiveTier } from './columns';\nimport type { PatientRow, PatientTableHandle, ResponsiveTier } from './types';\n\n/* -------------------------------------------------------------------------- */\n/* Breakpoint model */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Container inline-size breakpoints. Each entry is the *minimum* container width\n * (px) at which the named tier becomes visible. Below the first threshold only\n * `P0` shows; at or above the last, everything shows.\n */\ninterface Breakpoint {\n /** Minimum container inline-size (px) for this tier to be shown. */\n minWidth: number;\n /** The tier this breakpoint reveals. */\n tier: ResponsiveTier;\n}\n\nconst DEFAULT_BREAKPOINTS: readonly Breakpoint[] = [\n { minWidth: 0, tier: 'P0' },\n { minWidth: 640, tier: 'P1' },\n { minWidth: 900, tier: 'P2' },\n { minWidth: 1200, tier: 'P3' },\n];\n\n/** Resolve the set of tiers that should be visible at a given container width. */\nfunction visibleTiersForWidth(\n width: number,\n breakpoints: readonly Breakpoint[],\n): Set<ResponsiveTier> {\n const visible = new Set<ResponsiveTier>();\n for (const bp of breakpoints) {\n if (width >= bp.minWidth) visible.add(bp.tier);\n }\n // P0 is always visible regardless of width.\n visible.add('P0');\n return visible;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Options */\n/* -------------------------------------------------------------------------- */\n\nexport interface UseResponsiveColumnsOptions {\n /**\n * The grid container element to observe. The hook measures THIS element's\n * inline-size (not the viewport) so it works embedded. When the ref is unset\n * the hook is inert.\n */\n containerRef: RefObject<HTMLElement | null>;\n /**\n * Disable responsive toggling entirely (the `responsive={false}` escape hatch\n * on `PatientTable`). When `false` the hook unobserves and makes no visibility\n * changes — column visibility is left entirely to persistence / ColumnToggle.\n * Default `true`.\n */\n enabled?: boolean;\n /**\n * Override the container-width breakpoints. Ordered low→high `minWidth`; each\n * reveals its `tier`. Defaults to 640 / 900 / 1200 (P1 / P2 / P3).\n */\n breakpoints?: readonly Breakpoint[];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Return shape */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Imperative surface returned by {@link useResponsiveColumns}. The responsive\n * effect runs internally; `lockColumns` lets a caller (the view switcher) mark\n * columns as user-touched so the responsive baseline stops auto-managing them.\n */\nexport interface UseResponsiveColumnsResult {\n /**\n * Mark the given colIds as \"user-touched\" so the responsive baseline leaves\n * their visibility alone from here on — exactly as if the user had toggled\n * them via `ColumnToggle`. The view switcher calls this when it applies a\n * view's column visibility, so its programmatic (`source: 'api'`) visibility\n * writes are not immediately re-hidden/re-shown by the next responsive\n * reconcile pass. No-op (with the colIds still recorded for a later mount of\n * the effect) when the hook is disabled.\n */\n lockColumns: (colIds: readonly string[]) => void;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Hook */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Wire responsive, tier-driven column visibility to the patient grid.\n *\n * @param handleRef The `PatientTable` imperative handle ref — the hook reaches\n * the raw AG Grid API via `handleRef.current.getRawApi()`.\n * @param opts Container ref to observe, plus enable flag / breakpoint\n * overrides. See {@link UseResponsiveColumnsOptions}.\n * @returns {@link UseResponsiveColumnsResult} — `lockColumns(colIds)`\n * to mark columns user-touched (see the view-switcher\n * coordination note in patient-table.tsx).\n */\nexport function useResponsiveColumns(\n handleRef: RefObject<PatientTableHandle | null>,\n opts: UseResponsiveColumnsOptions,\n): UseResponsiveColumnsResult {\n const {\n containerRef,\n enabled = true,\n breakpoints = DEFAULT_BREAKPOINTS,\n } = opts;\n\n // colIds the user has explicitly toggled this session — responsive auto-toggle\n // must never override these (their persisted visibility wins). A ref, not\n // state: mutating it must not re-run the effect or re-render. Shared between\n // the responsive effect (which seeds + grows it from `columnVisible` events)\n // and the exported `lockColumns` (which the view switcher uses to register a\n // view-driven visibility change as a user override).\n const userTouchedRef = useRef<Set<string>>(new Set());\n // Re-entrancy guard: true while WE are applying a responsive change, so the\n // resulting `columnVisible` event is not misread as a user toggle even if a\n // future AG Grid version reports a non-`api` source for it.\n const applyingRef = useRef(false);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!enabled || !container) return;\n\n // Resolve the live API lazily on each tick — the grid may not be ready on\n // the first observer callback.\n const getApi = () => handleRef.current?.getRawApi();\n\n /**\n * Record a user-driven visibility change so the column is left alone from\n * here on. `source: 'api'` is the only programmatic source the hook itself\n * uses; everything else (tool panel, column menu, context menu, drag) is the\n * user. The re-entrancy flag covers our own calls defensively.\n */\n const onColumnVisible = (event: ColumnVisibleEvent<PatientRow>) => {\n if (applyingRef.current) return;\n if (event.source === 'api') return;\n const touched = event.columns ?? (event.column ? [event.column] : []);\n for (const col of touched) {\n userTouchedRef.current.add(col.getColId());\n }\n };\n\n // The grid API is frequently not ready on the first effect tick (AG Grid\n // mounts asynchronously). `wired` ensures the one-time seed + listener\n // attach happens the first time we actually have an API, no matter whether\n // that is here on mount or later from a ResizeObserver tick.\n let wired = false;\n\n /**\n * One-time wiring once the API is live: seed the user-touched set from the\n * persisted/current column state, then subscribe to `columnVisible` so later\n * manual toggles are recorded. Seeding treats an already-hidden tiered\n * column that *would* be visible at the current width as a deliberate user\n * hide (restored from localStorage by data-table.tsx) so the baseline does\n * not clobber it. We only positively detect *hidden* overrides — auto-show\n * would otherwise fight a deliberate hide.\n */\n const ensureWired = (api: NonNullable<ReturnType<typeof getApi>>) => {\n if (wired) return;\n wired = true;\n const cols = api.getColumns();\n const width = container.getBoundingClientRect().width;\n const visibleTiers = visibleTiersForWidth(width, breakpoints);\n if (cols) {\n for (const col of cols) {\n const tier = getResponsiveTier(col.getColDef());\n if (tier && !col.isVisible() && visibleTiers.has(tier)) {\n userTouchedRef.current.add(col.getColId());\n }\n }\n }\n api.addEventListener('columnVisible', onColumnVisible);\n };\n\n /** Apply the responsive baseline for the current container width. */\n const reconcile = () => {\n const api = getApi();\n if (!api) return;\n ensureWired(api);\n const columns = api.getColumns();\n if (!columns) return;\n\n const width = container.getBoundingClientRect().width;\n const visibleTiers = visibleTiersForWidth(width, breakpoints);\n\n // Compute the show / hide buckets, skipping user-touched and untiered\n // columns. Batch the calls so AG Grid fires one event per direction.\n const toShow: Column[] = [];\n const toHide: Column[] = [];\n\n for (const col of columns) {\n const colId = col.getColId();\n // The user owns this column's visibility — never auto-manage it.\n if (userTouchedRef.current.has(colId)) continue;\n\n const tier = getResponsiveTier(col.getColDef());\n // No tier ⇒ anchor column (Identity / Actions): always left visible.\n if (!tier) continue;\n\n const shouldShow = visibleTiers.has(tier);\n if (shouldShow && !col.isVisible()) toShow.push(col);\n else if (!shouldShow && col.isVisible()) toHide.push(col);\n }\n\n if (toShow.length === 0 && toHide.length === 0) return;\n\n applyingRef.current = true;\n try {\n // AG Grid tags API-driven changes with `source: 'api'`, so the\n // resulting `columnVisible` events are ignored by `onColumnVisible`\n // (they are the baseline, not user intent). `applyingRef` is the belt\n // to that braces in case a future version reports a different source.\n if (toShow.length > 0) api.setColumnsVisible(toShow, true);\n if (toHide.length > 0) api.setColumnsVisible(toHide, false);\n } finally {\n applyingRef.current = false;\n }\n };\n\n // Observe the GRID CONTAINER (not the viewport) so embedded tables respond\n // to their own box. The first observer tick fires synchronously on observe,\n // and `reconcile` lazily wires + seeds the moment the grid API is live.\n const observer = new ResizeObserver(() => reconcile());\n observer.observe(container);\n reconcile();\n\n return () => {\n observer.disconnect();\n // The API may have torn down already on unmount; guard the removal.\n getApi()?.removeEventListener('columnVisible', onColumnVisible);\n };\n }, [containerRef, enabled, breakpoints, handleRef]);\n\n // Stable across renders — `userTouchedRef` is a ref, so adding to it never\n // re-runs the responsive effect. Recording a colId here makes the next\n // `reconcile` pass skip it (it `continue`s on `userTouchedRef.has(colId)`),\n // which is exactly how a manual ColumnToggle override is honoured. The view\n // switcher calls this for every column it shows/hides so the responsive\n // baseline doesn't immediately fight the applied view.\n const lockColumns = useCallback((colIds: readonly string[]) => {\n for (const id of colIds) userTouchedRef.current.add(id);\n }, []);\n\n return { lockColumns };\n}\n","/**\n * patient-table — Domain-Specific component\n * ----------------------------------------------------------------------------\n * `PatientTable` is a thin composition over the kit's `data-table` engine\n * (AG Grid Community) specialised for the AlfaDocs patient list. It bakes in:\n *\n * • `buildPatientColumns(t, …)` (one field per column — split identity,\n * grouped hidden columns, dynamic custom fields) merged with a\n * consumer-supplied `columns` override.\n * • the kit data-table built-in renderers (Image / Link / Date / Currency /\n * Status / TagList / User) plus the three patient renderers kept for the\n * domain columns (NextAppointment / BalanceBadge / CarePlanStatus).\n * • a default toolbar — `QuickSearch` + `FilterChips` + `ColumnToggle` +\n * `ExportMenu` + a `BulkAction` per `props.bulkActions` — overridable\n * wholesale via `props.toolbar`.\n * • `useResponsiveColumns` (container-width column-priority tiers) when\n * `props.responsive` (default `true`).\n * • multiple row selection, pagination, archived-row tint, loading / empty\n * overlays.\n * • `useAgentRegistration(dataTableAgent, handle, gridId)` — the SAME agent\n * adapter `data-table` registers, keyed by `gridId`.\n *\n * The `forwardRef` exposes the inner `DataTableHandle<PatientRow>` so consumers\n * (e.g. the `patients` pattern's WarningStack \"View\" actions) can drive the\n * grid imperatively — `setFilter`, `setSort`, `getSelection`, etc.\n *\n * CONSTRAINTS (CI-blocking — src/docs/23-constraints.mdx, 26-agent-readiness.mdx):\n * • No hardcoded colour / spacing / radius / shadow / font — token-bound `ds:`\n * utilities only. Row tint goes through the token-bound\n * `data-table-row-archived` class on the AG Grid theme bridge, never an\n * inline `cellStyle`.\n * • CSS logical properties only (`ms/me/ps/pe`, `text-start/text-end`).\n * • All user-visible strings via `useTranslation('ui')` → `ui.patientTable.*`.\n * • TypeScript strict — no `any`. `forwardRef` exposes the curated handle.\n * • PHI rule (PRS 26 §6): only `row.id` reaches DOM `data-*`. This component\n * writes no PHI attribute; AG Grid surfaces ids via its native `row-id`.\n */\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n type RefObject,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport type { GetRowIdParams, RowClassParams } from 'ag-grid-community';\nimport {\n actionsColumnWidth,\n DataTable,\n dataTableAgent,\n type DataTableHandle,\n} from '../data-table';\nimport { Select } from '../select';\nimport { useAgentRegistration } from '../../agent';\nimport {\n buildPatientColumns,\n getResponsiveTier,\n PATIENT_ROW_ACTION_COUNT,\n type BuildPatientColumnsOptions,\n} from './columns';\nimport {\n useResponsiveColumns,\n type UseResponsiveColumnsResult,\n} from './use-responsive-columns';\nimport type {\n PatientBulkAction,\n PatientColumnDef,\n PatientCustomFieldDef,\n PatientRow,\n PatientRowAction,\n PatientTableHandle,\n PatientView,\n} from './types';\n\n/* -------------------------------------------------------------------------- */\n/* Props */\n/* -------------------------------------------------------------------------- */\n\nexport interface PatientTableProps {\n /**\n * Row data. `undefined` ⇒ the grid renders its loading overlay (the\n * data-table contract: `rowData === undefined` is the loading sentinel). Pass\n * `[]` for an explicit empty state.\n */\n rowData?: PatientRow[];\n /**\n * Override / extend the baked patient columns. When provided, these column\n * definitions REPLACE the kit defaults entirely (the consumer is expected to\n * compose `buildPatientColumns(t, …)` — re-exported from the barrel — with\n * their own additions). When omitted, the kit's `buildPatientColumns` output\n * is used.\n */\n columns?: PatientColumnDef[];\n /**\n * REQUIRED. Persistence key (column state → `localStorage`) AND the agent\n * instance address (`data-component-id`). Two tables on one page need\n * distinct `gridId`s.\n */\n gridId: string;\n /** Row-height preset. Defaults to the engine default (`'default'`). */\n density?: 'compact' | 'default' | 'expanded';\n /**\n * Collapse the per-row actions column into a single \"…\" overflow trigger\n * independently of row `density`. When `true` the actions cells render the\n * collapsed menu and the column shrinks to its 88px compact footprint; when\n * `false` they render the inline button group at the full expanded width.\n * When `undefined` (the default) compactness FOLLOWS `density` — `compact`\n * density collapses the actions, every other density keeps them inline — so\n * existing consumers are unaffected. Flipping this at runtime re-reads the\n * actions cells via a stable ref-backed getter (no `columnDefs` rebuild, so\n * column state is preserved) and resizes the actions column to match.\n */\n actionsCompact?: boolean;\n /**\n * Grid height — forwarded to the underlying `DataTable.heightClass` (a Tailwind\n * height class). Pass `\"ds:h-full\"` to fill a sized parent, or an explicit\n * value like `\"ds:h-[75vh]\"`. Omit to use the engine default (`ds:h-[500px]`).\n */\n heightClass?: string;\n /**\n * Enable container-width column-priority responsiveness (lower-priority\n * columns hide as the grid narrows). Default `true`. Set `false` to leave\n * column visibility entirely to persistence / `ColumnToggle`.\n */\n responsive?: boolean;\n /**\n * Saved column/filter/sort presets. When non-empty AND no custom `toolbar`\n * is supplied, the default toolbar renders a `Select`-based view switcher in\n * the FilterBar; choosing a view applies its column visibility (via the\n * responsive-hook lock so the choice is honoured like a manual ColumnToggle),\n * `filterModel` (`setFilter`) and `sortModel` (`setSort`). Omitted entirely\n * when empty / undefined or when `toolbar` is supplied (a custom toolbar owns\n * its own view UI).\n */\n views?: PatientView[];\n /**\n * Invoked when a per-row action button is activated, with the resolved action\n * descriptor and the patient row. Only `row.id` ever leaves this boundary into\n * persistence / agent surfaces — never PHI.\n */\n onRowAction?: (action: PatientRowAction, patient: PatientRow) => void;\n /**\n * Toolbar bulk actions (operate on the current selection). Each renders a\n * `DataTable.Toolbar.BulkAction` that surfaces only while rows are selected;\n * its `onSelect` receives the selected row ids (never PHI).\n */\n bulkActions?: PatientBulkAction[];\n /**\n * Force the loading overlay independently of `rowData`. When `true` the grid\n * shows the loading skeleton even if `rowData` is an array (e.g. a background\n * refresh). `rowData === undefined` also triggers loading.\n */\n loading?: boolean;\n /**\n * Replace the default toolbar wholesale. Pass a `<DataTable.Toolbar>…</…>`\n * tree (it has access to the same toolbar context). When omitted the default\n * QuickSearch + FilterChips + ColumnToggle + ExportMenu + BulkActions toolbar\n * is rendered.\n */\n toolbar?: ReactNode;\n /**\n * Omit the `QuickSearch` field from the DEFAULT toolbar. The rest of the\n * default toolbar (view switcher when `views` is supplied, FilterChips,\n * ColumnToggle, ExportMenu, BulkActions) is unaffected. Ignored when a custom\n * `toolbar` is supplied (that toolbar owns its own composition). Default\n * `false` (search shown). Use for surfaces that provide their own search\n * affordance elsewhere on the page.\n */\n hideQuickSearch?: boolean;\n /**\n * Enable the grid's client-side pagination. When `false` (the default), every\n * row loads into a single virtualised, scrollable view and filter / sort act\n * over the WHOLE set (no pager) — the right model for a \"load and filter all\"\n * list, which is what a patient list is. Pass `true` for a surface that really\n * wants a pager.\n */\n pagination?: boolean;\n /**\n * Page-size dropdown options shown in the pagination panel — forwarded to the\n * underlying `DataTable.paginationPageSizeSelector`. Pass `false` to hide the\n * page-size chooser entirely (pagination navigation is unaffected). Default\n * `[10, 25, 50, 100]` (the engine default).\n */\n paginationPageSizeSelector?: number[] | false;\n /**\n * Mask sensitive contact details (email) in the Contact / Email columns.\n * Forwarded to `buildPatientColumns`' `hideDetails`. Ignored when `columns`\n * is supplied (the consumer owns their columns then). Default `false`.\n */\n hideDetails?: boolean;\n /**\n * Derive the patient-detail href for the Identity cell, e.g.\n * `(row) => \\`/patients/${row.id}\\``. Forwarded to `buildPatientColumns`.\n * Ignored when `columns` is supplied. When omitted the Identity cell routes\n * its click through `onRowAction` with the `show` action.\n */\n identityHref?: BuildPatientColumnsOptions['identityHref'];\n /**\n * Per-practice custom-field descriptors. Each appends one dynamic `custom:*`\n * column (hidden by default, toggleable via ColumnToggle) after the standard\n * columns. Forwarded to `buildPatientColumns`. Ignored when `columns` is\n * supplied (the consumer owns their columns then).\n */\n customFields?: PatientCustomFieldDef[];\n /** Override the default region aria-label (`ui.patientTable.regionLabel`). */\n 'aria-label'?: string;\n /** Extra class names merged onto the inner grid wrapper. */\n className?: string;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Row class — archived tint */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Token-bound archived-row tint. The class is defined on the AG Grid theme\n * bridge (`src/tokens/ag-grid-theme.css` → `.data-table-row-archived`) so the\n * recessive muted fill stays under token control — no colour ever enters a\n * `cellStyle` / `cellClass` literal here.\n */\nfunction getPatientRowClass(\n params: RowClassParams<PatientRow>,\n): string | undefined {\n return params.data?.isArchived ? 'data-table-row-archived' : undefined;\n}\n\n/* -------------------------------------------------------------------------- */\n/* Row id — key selection / bulk actions on the real patient id */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Use the stable `PatientRow.id` as AG Grid's node id. Without this AG Grid\n * keys nodes by row index, so `getSelectedNodes().map(n => n.id)`,\n * `setSelection(ids)`, the bulk-action bridge and the agent adapter would all\n * receive index strings (`\"0\"`, `\"1\"`) instead of patient ids. Forwarded to the\n * engine via `gridOptions.getRowId` (the path `data-table.tsx` spreads through\n * untouched). `id` is the ONLY field permitted on a DOM hook (PHI rule).\n */\nfunction getPatientRowId(params: GetRowIdParams<PatientRow>): string {\n return params.data.id;\n}\n\n/* -------------------------------------------------------------------------- */\n/* View switcher — default-toolbar control for the `views` prop */\n/* -------------------------------------------------------------------------- */\n\ninterface ViewSwitcherProps {\n /** Saved views to choose from (already known non-empty by the caller). */\n views: PatientView[];\n /** The live patient-table handle ref (filter / sort / raw API access). */\n handleRef: RefObject<PatientTableHandle | null>;\n /**\n * Mark a view's visibility writes as user-touched so `useResponsiveColumns`\n * stops auto-managing them — see the coordination note at the call site.\n */\n lockColumns: UseResponsiveColumnsResult['lockColumns'];\n /** Accessible label for the control (`ui.patientTable.views.label`). */\n label: string;\n}\n\n/**\n * A minimal, accessible saved-view switcher rendered in the DEFAULT toolbar's\n * FilterBar. Uses the kit `Select` (convenience `options` form) — a small,\n * labelled single-choice control that fits the toolbar's compact density and\n * carries Radix's listbox a11y for free.\n *\n * On selection it applies the chosen {@link PatientView} through the patient\n * handle + raw AG Grid API:\n * • column visibility — show the view's listed toggleable columns, hide the\n * rest of the toggleable (tiered) ones. Untiered columns (the pinned\n * Identity / Actions anchors) are NEVER hidden.\n * • `filterModel` via `handle.setFilter`.\n * • `sortModel` via `handle.setSort`.\n *\n * RESPONSIVE COORDINATION (critical): a view-applied visibility change must not\n * be immediately re-hidden/re-shown by `useResponsiveColumns`. We route every\n * colId we touch through `lockColumns(colIds)`, which adds them to the same\n * `userTouchedRef` set the hook grows from non-`api` `columnVisible` events —\n * so from then on the responsive baseline treats them as a user override and\n * leaves them alone, exactly as a manual ColumnToggle would.\n */\nfunction ViewSwitcher({\n views,\n handleRef,\n lockColumns,\n label,\n}: ViewSwitcherProps) {\n const [activeViewId, setActiveViewId] = useState<string>('');\n\n const options = useMemo(\n () => views.map((view) => ({ value: view.id, label: view.label })),\n [views],\n );\n\n const applyView = useCallback(\n (viewId: string) => {\n const view = views.find((v) => v.id === viewId);\n const handle = handleRef.current;\n if (!view || !handle) return;\n\n const api = handle.getRawApi();\n if (api) {\n const wanted = new Set<string>(view.columns);\n const toShow: string[] = [];\n const toHide: string[] = [];\n for (const col of api.getColumns() ?? []) {\n // Only the tiered (auto-hideable) field columns participate; the\n // pinned Identity / Actions anchors carry no tier and are never\n // hidden by a view.\n if (getResponsiveTier(col.getColDef()) === undefined) continue;\n const colId = col.getColId();\n if (wanted.has(colId)) toShow.push(colId);\n else toHide.push(colId);\n }\n // Lock BEFORE the visibility write so a synchronous responsive\n // reconcile (e.g. a ResizeObserver tick triggered by the layout shift)\n // already sees these colIds as user-touched.\n lockColumns([...toShow, ...toHide]);\n if (toShow.length > 0) api.setColumnsVisible(toShow, true);\n if (toHide.length > 0) api.setColumnsVisible(toHide, false);\n }\n\n handle.setFilter(view.filterModel);\n handle.setSort(view.sortModel);\n api?.onFilterChanged();\n },\n [views, handleRef, lockColumns],\n );\n\n const handleValueChange = useCallback(\n (next: string) => {\n setActiveViewId(next);\n if (next) applyView(next);\n },\n [applyView],\n );\n\n return (\n // Width-constraining wrapper (same pattern as the toolbar's QuickSearch) so\n // the Select's intrinsic `w-full` trigger sizes to a compact, legible width\n // instead of absorbing the whole FilterBar row.\n <div className=\"ds:w-[12rem] ds:max-w-full ds:min-w-0 ds:shrink-0\">\n <Select\n options={options}\n value={activeViewId}\n onValueChange={handleValueChange}\n size=\"sm\"\n aria-label={label}\n />\n </div>\n );\n}\n\n/* -------------------------------------------------------------------------- */\n/* PatientTable */\n/* -------------------------------------------------------------------------- */\n\nexport const PatientTable = forwardRef<PatientTableHandle, PatientTableProps>(\n function PatientTable(\n {\n rowData,\n columns,\n gridId,\n density,\n actionsCompact,\n heightClass,\n responsive = true,\n views,\n onRowAction,\n bulkActions,\n loading = false,\n toolbar,\n hideQuickSearch = false,\n pagination = false,\n paginationPageSizeSelector,\n hideDetails = false,\n identityHref,\n customFields,\n 'aria-label': ariaLabel,\n className,\n },\n ref,\n ) {\n const { t } = useTranslation('ui');\n\n // Internal handle ref forwarded BOTH to the consumer's ref (below) and to\n // `useResponsiveColumns`, which reaches the live AG Grid API via\n // `handle.getRawApi()`.\n const handleRef = useRef<PatientTableHandle | null>(null);\n\n // Container the responsive hook observes (its inline-size drives the\n // column-priority tiers). Observing the grid's own box — not the viewport —\n // is what makes the table responsive when embedded in a narrow panel.\n const containerRef = useRef<HTMLDivElement | null>(null);\n\n // Mirror the handle into state once the inner DataTable mounts so the\n // agent-registration effect (which keys off `handle` reactively) fires with\n // a live handle. A bare ref mutation would not re-run the effect.\n const [registrableHandle, setRegistrableHandle] =\n useState<PatientTableHandle | null>(null);\n\n // Expose the inner DataTable handle to the consumer ref. Re-runs when the\n // mirrored handle becomes available so a consumer reading the ref in an\n // effect sees the live handle, not the initial `null`.\n useImperativeHandle(\n ref,\n () => (registrableHandle ?? handleRef.current) as PatientTableHandle,\n [registrableHandle],\n );\n\n // Responsive column-priority tiers, driven by the grid container's width.\n // Inert when `responsive` is false (the hook unobserves and makes no calls).\n // The container ref is widened to the hook's `HTMLElement` param at the call\n // site (RefObject is invariant on its element type) — same cast pattern as\n // `useDirection` in tooltip.tsx. `lockColumns` lets the ViewSwitcher mark a\n // view's visibility writes as user-touched (see ViewSwitcher's coordination\n // note) — called before the hook value is consumed in the default toolbar.\n const { lockColumns } = useResponsiveColumns(handleRef, {\n containerRef: containerRef as RefObject<HTMLElement | null>,\n enabled: responsive,\n });\n\n // Actions-column compactness. Explicit `actionsCompact` wins; otherwise it\n // follows `density` (`compact` collapses, every other density stays inline)\n // — the legacy coupling, preserved as the default so existing consumers are\n // unaffected.\n const compact = actionsCompact ?? density === 'compact';\n\n // Ref-backed live getter for the compactness flag. The actions cell renderer\n // calls this every render, so flipping `compact` need only update the ref +\n // refresh the cells — it never rebuilds `columnDefs` (which would discard the\n // user's column state). The getter identity is stable, so it is NOT a\n // `columnDefs` dependency.\n const actionsCompactRef = useRef(compact);\n const getActionsCompact = useCallback(() => actionsCompactRef.current, []);\n\n // On a `compact` change — OR once the grid handle first becomes live — sync\n // the ref the renderer reads, then drive the LIVE grid: set the actions\n // column to its collapsed (88px) or expanded (derived from the row-action\n // count) width and force the actions cells to re-read the getter. Depending\n // on `registrableHandle` makes the initial compact state apply as soon as the\n // DataTable mirrors its handle in (the api is null on first paint), so a\n // page mounting already-compact lands at 88px without a column-defs rebuild.\n useEffect(() => {\n actionsCompactRef.current = compact;\n const api = handleRef.current?.getRawApi();\n if (!api) return;\n api.setColumnWidths([\n {\n key: 'actions',\n newWidth: compact ? 88 : actionsColumnWidth(PATIENT_ROW_ACTION_COUNT),\n },\n ]);\n api.refreshCells({ force: true });\n }, [compact, registrableHandle]);\n\n // Columns: consumer override wins wholesale; otherwise the baked patient\n // columns. Memoised on the inputs the builder reads — `density` is NO LONGER\n // among them: the actions column reads its compactness through the stable\n // `getActionsCompact` getter, so a density (or `actionsCompact`) flip resizes\n // + refreshes the live grid without rebuilding the defs (preserving column\n // state). `density` still drives row height via the DataTable prop below.\n const columnDefs = useMemo<PatientColumnDef[]>(\n () =>\n columns ??\n buildPatientColumns(t, {\n hideDetails,\n onRowAction,\n identityHref,\n customFields,\n getActionsCompact,\n }),\n [\n columns,\n t,\n hideDetails,\n onRowAction,\n identityHref,\n customFields,\n getActionsCompact,\n ],\n );\n\n // Default toolbar — an optional ViewSwitcher + QuickSearch + FilterChips\n // (FilterBar slot) and ColumnToggle + ExportMenu + one BulkAction per\n // `bulkActions` (Actions slot). Overridable wholesale via `props.toolbar`;\n // when a custom `toolbar` is supplied the ViewSwitcher is NOT injected — a\n // custom toolbar owns its own view UI. The switcher is also omitted when\n // `views` is empty / undefined. All toolbar parts translate their own labels\n // through the `dataTable.*` namespace; the BulkAction labels come from the\n // consumer-supplied descriptors.\n const hasViews = (views?.length ?? 0) > 0;\n const defaultToolbar = (\n <DataTable.Toolbar>\n <DataTable.Toolbar.FilterBar>\n {hasViews && views ? (\n <ViewSwitcher\n views={views}\n handleRef={handleRef}\n lockColumns={lockColumns}\n label={t('patientTable.views.label')}\n />\n ) : null}\n {hideQuickSearch ? null : <DataTable.Toolbar.QuickSearch />}\n <DataTable.Toolbar.FilterChips />\n </DataTable.Toolbar.FilterBar>\n <DataTable.Toolbar.Actions>\n {bulkActions?.map((action) => (\n <DataTable.Toolbar.BulkAction\n key={action.id}\n label={action.label}\n icon={action.icon}\n variant={action.variant}\n onClick={(api) => {\n // Resolve the selection via the live grid API → row ids only\n // (never PHI). Falls back to an empty list if the API is unset.\n const ids =\n api\n ?.getSelectedNodes()\n .map((node) => node.id)\n .filter((id): id is string => id != null) ?? [];\n action.onSelect(ids);\n }}\n />\n ))}\n <DataTable.Toolbar.ColumnToggle />\n <DataTable.Toolbar.ExportMenu />\n </DataTable.Toolbar.Actions>\n </DataTable.Toolbar>\n );\n\n const toolbarContent = toolbar ?? defaultToolbar;\n\n // `rowData === undefined` is the data-table loading sentinel; `loading`\n // forces it independently (e.g. a background refresh over existing rows).\n const effectiveRowData = loading ? undefined : rowData;\n\n // Stable `gridOptions` forwarding `getRowId` so AG Grid keys nodes by the\n // real `PatientRow.id` (see getPatientRowId) — selection, bulk actions and\n // the agent adapter all then return patient ids, not row-index strings.\n const gridOptions = useMemo(() => ({ getRowId: getPatientRowId }), []);\n\n // Register the data-table agent adapter keyed by `gridId` — the same\n // adapter `data-table` registers internally. Idempotent overwrite with the\n // identical handle under the same `(adapterId, gridId)` key; both share the\n // unmount lifecycle so the dedup is harmless and the registration stays\n // addressable as `patient-table`'s `gridId` for the duration.\n useAgentRegistration(\n dataTableAgent,\n registrableHandle as DataTableHandle | null,\n gridId,\n );\n\n return (\n <div\n ref={containerRef}\n role=\"region\"\n aria-label={ariaLabel ?? t('patientTable.regionLabel')}\n data-component=\"patient-table\"\n data-component-id={gridId}\n className=\"ds:flex ds:w-full ds:min-h-0 ds:flex-1 ds:flex-col\"\n >\n <DataTable<PatientRow>\n ref={(instance) => {\n handleRef.current = instance;\n // Mirror into state once (or when the instance identity changes) so\n // the registration effect picks up the live handle.\n setRegistrableHandle((prev) =>\n prev === instance ? prev : instance,\n );\n }}\n gridId={gridId}\n columnDefs={columnDefs}\n rowData={effectiveRowData}\n rowSelection=\"multiple\"\n pagination={pagination}\n {...(heightClass !== undefined ? { heightClass } : {})}\n {...(paginationPageSizeSelector !== undefined\n ? { paginationPageSizeSelector }\n : {})}\n density={density}\n getRowClass={getPatientRowClass}\n gridOptions={gridOptions}\n className={className}\n >\n {toolbarContent}\n </DataTable>\n </div>\n );\n },\n);\n\nPatientTable.displayName = 'PatientTable';\n"],"names":["SMALL_BALANCE_CENTS_MAX","classifyBalance","cents","readUnpaidCents","value","n","BalanceBadgeCell","props","currency","options","t","i18n","useTranslation","locale","formatted","variant","statusKey","status","jsxs","Badge","jsx","CARE_PLAN_VARIANT_MAP","isCarePlanStatus","CarePlanStatusCell","data","size","raw","nextAppointmentVariants","cva","DAY_MS","DEFAULT_SOON_WITHIN_DAYS","toTime","resolveTone","time","soonWithinDays","deltaMs","NextAppointmentCell","tone","CalendarClock","Timestamp","getResponsiveTier","colDef","PATIENT_ROW_ACTION_COUNT","buildRowActions","onRowAction","dispatch","action","patient","make","overrides","createElement","Eye","Pencil","Mail","MessageSquare","p","Users","Trash2","NO","CHECK","DASH","ageFromDob","dob","birth","ms","now","age","m","buildCustomFieldColumns","fields","field","base","params","_b","_a","SelectFilter","SelectFloatingFilter","TagListCellRenderer","opt","TextFilter","TextFloatingFilter","buildPatientColumns","opts","hideDetails","getActionsCompact","identityHref","customFields","actions","genderLabels","consentLabels","consentVariantMap","header","id","ImageCellRenderer","LinkCellRenderer","row","_c","DateRangeFilter","DateRangeFloatingFilter","NumberFilter","NumberFloatingFilter","s","DateCellRenderer","CurrencyCellRenderer","UserCellRenderer","a","b","TypeaheadFilter","TypeaheadFloatingFilter","StatusCellRenderer","year","num","ActionsCellRenderer","actionsColumnWidth","DEFAULT_BREAKPOINTS","visibleTiersForWidth","width","breakpoints","visible","bp","useResponsiveColumns","handleRef","containerRef","enabled","userTouchedRef","useRef","applyingRef","useEffect","container","getApi","onColumnVisible","event","touched","col","wired","ensureWired","api","cols","visibleTiers","tier","reconcile","columns","toShow","toHide","colId","shouldShow","observer","useCallback","colIds","getPatientRowClass","getPatientRowId","ViewSwitcher","views","lockColumns","label","activeViewId","setActiveViewId","useState","useMemo","view","applyView","viewId","v","handle","wanted","handleValueChange","next","Select","PatientTable","forwardRef","rowData","gridId","density","actionsCompact","heightClass","responsive","bulkActions","loading","toolbar","hideQuickSearch","pagination","paginationPageSizeSelector","ariaLabel","className","ref","registrableHandle","setRegistrableHandle","useImperativeHandle","compact","actionsCompactRef","columnDefs","hasViews","defaultToolbar","DataTable","ids","node","toolbarContent","effectiveRowData","gridOptions","useAgentRegistration","dataTableAgent","instance","prev"],"mappings":"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"}