@burdenoff/microfe-proserviceworld 2026.1026.2 → 2026.1031.1

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.
Files changed (105) hide show
  1. package/dist/proserviceworld/components/CredentialDirectory.js +44 -43
  2. package/dist/proserviceworld/components/CredentialDirectory.js.map +1 -1
  3. package/dist/proserviceworld/components/CredentialReminderPanel.js +5 -4
  4. package/dist/proserviceworld/components/CredentialReminderPanel.js.map +1 -1
  5. package/dist/proserviceworld/components/ui.d.ts +7 -3
  6. package/dist/proserviceworld/components/ui.d.ts.map +1 -1
  7. package/dist/proserviceworld/components/ui.js +135 -121
  8. package/dist/proserviceworld/components/ui.js.map +1 -1
  9. package/dist/proserviceworld/hooks/useProServiceWorldApi.d.ts +28 -0
  10. package/dist/proserviceworld/hooks/useProServiceWorldApi.d.ts.map +1 -1
  11. package/dist/proserviceworld/hooks/useProServiceWorldApi.js +95 -73
  12. package/dist/proserviceworld/hooks/useProServiceWorldApi.js.map +1 -1
  13. package/dist/proserviceworld/hooks/useTr.d.ts +49 -8
  14. package/dist/proserviceworld/hooks/useTr.d.ts.map +1 -1
  15. package/dist/proserviceworld/hooks/useTr.js +45 -21
  16. package/dist/proserviceworld/hooks/useTr.js.map +1 -1
  17. package/dist/proserviceworld/i18n/catalog/analytics.d.ts +5 -0
  18. package/dist/proserviceworld/i18n/catalog/analytics.d.ts.map +1 -0
  19. package/dist/proserviceworld/i18n/catalog/analytics.js +271 -0
  20. package/dist/proserviceworld/i18n/catalog/analytics.js.map +1 -0
  21. package/dist/proserviceworld/i18n/catalog/common.d.ts +9 -0
  22. package/dist/proserviceworld/i18n/catalog/common.d.ts.map +1 -0
  23. package/dist/proserviceworld/i18n/catalog/common.js +163 -0
  24. package/dist/proserviceworld/i18n/catalog/common.js.map +1 -0
  25. package/dist/proserviceworld/i18n/catalog/compliance.d.ts +5 -0
  26. package/dist/proserviceworld/i18n/catalog/compliance.d.ts.map +1 -0
  27. package/dist/proserviceworld/i18n/catalog/compliance.js +157 -0
  28. package/dist/proserviceworld/i18n/catalog/compliance.js.map +1 -0
  29. package/dist/proserviceworld/i18n/catalog/conflicts.d.ts +5 -0
  30. package/dist/proserviceworld/i18n/catalog/conflicts.d.ts.map +1 -0
  31. package/dist/proserviceworld/i18n/catalog/conflicts.js +145 -0
  32. package/dist/proserviceworld/i18n/catalog/conflicts.js.map +1 -0
  33. package/dist/proserviceworld/i18n/catalog/documents.d.ts +5 -0
  34. package/dist/proserviceworld/i18n/catalog/documents.d.ts.map +1 -0
  35. package/dist/proserviceworld/i18n/catalog/documents.js +157 -0
  36. package/dist/proserviceworld/i18n/catalog/documents.js.map +1 -0
  37. package/dist/proserviceworld/i18n/catalog/enums.d.ts +10 -0
  38. package/dist/proserviceworld/i18n/catalog/enums.d.ts.map +1 -0
  39. package/dist/proserviceworld/i18n/catalog/enums.js +481 -0
  40. package/dist/proserviceworld/i18n/catalog/enums.js.map +1 -0
  41. package/dist/proserviceworld/i18n/catalog/index.d.ts +9 -0
  42. package/dist/proserviceworld/i18n/catalog/index.d.ts.map +1 -0
  43. package/dist/proserviceworld/i18n/catalog/index.js +25 -0
  44. package/dist/proserviceworld/i18n/catalog/index.js.map +1 -0
  45. package/dist/proserviceworld/i18n/catalog/invoices.d.ts +5 -0
  46. package/dist/proserviceworld/i18n/catalog/invoices.d.ts.map +1 -0
  47. package/dist/proserviceworld/i18n/catalog/invoices.js +247 -0
  48. package/dist/proserviceworld/i18n/catalog/invoices.js.map +1 -0
  49. package/dist/proserviceworld/i18n/catalog/practiceAreas.d.ts +5 -0
  50. package/dist/proserviceworld/i18n/catalog/practiceAreas.d.ts.map +1 -0
  51. package/dist/proserviceworld/i18n/catalog/practiceAreas.js +97 -0
  52. package/dist/proserviceworld/i18n/catalog/practiceAreas.js.map +1 -0
  53. package/dist/proserviceworld/i18n/catalog/time.d.ts +5 -0
  54. package/dist/proserviceworld/i18n/catalog/time.d.ts.map +1 -0
  55. package/dist/proserviceworld/i18n/catalog/time.js +301 -0
  56. package/dist/proserviceworld/i18n/catalog/time.js.map +1 -0
  57. package/dist/proserviceworld/i18n/index.d.ts +10 -9
  58. package/dist/proserviceworld/i18n/index.d.ts.map +1 -1
  59. package/dist/proserviceworld/i18n/index.js +16 -12
  60. package/dist/proserviceworld/i18n/index.js.map +1 -1
  61. package/dist/proserviceworld/i18n/types.d.ts +13 -0
  62. package/dist/proserviceworld/i18n/types.d.ts.map +1 -0
  63. package/dist/proserviceworld/i18n/types.js +18 -0
  64. package/dist/proserviceworld/i18n/types.js.map +1 -0
  65. package/dist/proserviceworld/pages/AnalyticsPage.d.ts.map +1 -1
  66. package/dist/proserviceworld/pages/AnalyticsPage.js +236 -152
  67. package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
  68. package/dist/proserviceworld/pages/ClientDetailPage.js +99 -98
  69. package/dist/proserviceworld/pages/ClientDetailPage.js.map +1 -1
  70. package/dist/proserviceworld/pages/ClientsPage.d.ts.map +1 -1
  71. package/dist/proserviceworld/pages/ClientsPage.js +181 -181
  72. package/dist/proserviceworld/pages/ClientsPage.js.map +1 -1
  73. package/dist/proserviceworld/pages/CompliancePage.d.ts.map +1 -1
  74. package/dist/proserviceworld/pages/CompliancePage.js +210 -209
  75. package/dist/proserviceworld/pages/CompliancePage.js.map +1 -1
  76. package/dist/proserviceworld/pages/ConflictsPage.d.ts.map +1 -1
  77. package/dist/proserviceworld/pages/ConflictsPage.js +139 -138
  78. package/dist/proserviceworld/pages/ConflictsPage.js.map +1 -1
  79. package/dist/proserviceworld/pages/DocumentsPage.d.ts.map +1 -1
  80. package/dist/proserviceworld/pages/DocumentsPage.js +150 -149
  81. package/dist/proserviceworld/pages/DocumentsPage.js.map +1 -1
  82. package/dist/proserviceworld/pages/EngagementDetailPage.js +105 -104
  83. package/dist/proserviceworld/pages/EngagementDetailPage.js.map +1 -1
  84. package/dist/proserviceworld/pages/EngagementsPage.d.ts.map +1 -1
  85. package/dist/proserviceworld/pages/EngagementsPage.js +137 -137
  86. package/dist/proserviceworld/pages/EngagementsPage.js.map +1 -1
  87. package/dist/proserviceworld/pages/InvoicesPage.d.ts.map +1 -1
  88. package/dist/proserviceworld/pages/InvoicesPage.js +275 -258
  89. package/dist/proserviceworld/pages/InvoicesPage.js.map +1 -1
  90. package/dist/proserviceworld/pages/MattersPage.d.ts.map +1 -1
  91. package/dist/proserviceworld/pages/MattersPage.js +166 -166
  92. package/dist/proserviceworld/pages/MattersPage.js.map +1 -1
  93. package/dist/proserviceworld/pages/PracticeAreasPage.d.ts.map +1 -1
  94. package/dist/proserviceworld/pages/PracticeAreasPage.js +100 -99
  95. package/dist/proserviceworld/pages/PracticeAreasPage.js.map +1 -1
  96. package/dist/proserviceworld/pages/PractitionerCredentialsPage.js +30 -29
  97. package/dist/proserviceworld/pages/PractitionerCredentialsPage.js.map +1 -1
  98. package/dist/proserviceworld/pages/TimePage.d.ts.map +1 -1
  99. package/dist/proserviceworld/pages/TimePage.js +343 -350
  100. package/dist/proserviceworld/pages/TimePage.js.map +1 -1
  101. package/dist/proserviceworld/utils/humanize.d.ts +3 -0
  102. package/dist/proserviceworld/utils/humanize.d.ts.map +1 -0
  103. package/dist/proserviceworld/utils/humanize.js +6 -0
  104. package/dist/proserviceworld/utils/humanize.js.map +1 -0
  105. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/AnalyticsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { BarChart3, TrendingUp, Clock, Wallet } from 'lucide-react';\nimport { useDashboardStats } from '../hooks/useProServiceWorldApi';\nimport { cn } from '../utils/cn';\nimport {\n PageHeader,\n SectionCard,\n MetricCard,\n LoadingBlock,\n ErrorState,\n StatusPill,\n fmtMoney,\n fmtHours,\n} from '../components/ui';\n\nexport const AnalyticsPage: FC = () => {\n const { data, isLoading, error, refetch } = useDashboardStats();\n if (isLoading) return <LoadingBlock label=\"Crunching numbers…\" />;\n if (error) return <ErrorState error={error} onRetry={() => void refetch()} />;\n if (!data) return null;\n\n const totalHours = data.unbilledHours + data.billedHours;\n const utilization = totalHours > 0 ? Math.round((data.billedHours / totalHours) * 100) : 0;\n const billableBase = data.paidInvoiceTotal + data.outstandingInvoiceTotal;\n const collectionRate =\n billableBase > 0 ? Math.round((data.paidInvoiceTotal / billableBase) * 100) : 0;\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Analytics\"\n subtitle=\"Real, live metrics across the practice — no estimates, straight from your records.\"\n icon={<BarChart3 className=\"h-6 w-6\" />}\n />\n\n <div className=\"grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <MetricCard\n label=\"Collected revenue\"\n value={fmtMoney(data.paidInvoiceTotal)}\n hint={`${fmtMoney(billableBase)} invoiced`}\n icon={<Wallet className=\"h-4 w-4\" />}\n tone=\"success\"\n />\n <MetricCard\n label=\"Collection rate\"\n value={`${collectionRate}%`}\n hint={`${fmtMoney(data.outstandingInvoiceTotal)} outstanding`}\n icon={<TrendingUp className=\"h-4 w-4\" />}\n tone=\"accent\"\n />\n <MetricCard\n label=\"Utilization\"\n value={`${utilization}%`}\n hint={`${fmtHours(data.billedHours)} of ${fmtHours(totalHours)}`}\n icon={<Clock className=\"h-4 w-4\" />}\n tone=\"info\"\n />\n <MetricCard\n label=\"Active engagements\"\n value={data.activeEngagements}\n hint={`${data.openMatters} open matters`}\n icon={<BarChart3 className=\"h-4 w-4\" />}\n tone=\"warning\"\n />\n </div>\n\n {/* Practice-health meters — collection & utilization at a glance */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <SectionCard title=\"Collection rate\" description=\"Paid vs. total invoiced.\">\n <GaugeRow\n pct={collectionRate}\n tone=\"accent\"\n primary={fmtMoney(data.paidInvoiceTotal)}\n primaryLabel=\"Collected\"\n secondary={fmtMoney(data.outstandingInvoiceTotal)}\n secondaryLabel=\"Outstanding\"\n />\n </SectionCard>\n <SectionCard title=\"Utilization\" description=\"Billed vs. total logged hours.\">\n <GaugeRow\n pct={utilization}\n tone=\"info\"\n primary={fmtHours(data.billedHours)}\n primaryLabel=\"Billed\"\n secondary={fmtHours(data.unbilledHours)}\n secondaryLabel=\"Unbilled\"\n />\n </SectionCard>\n </div>\n\n <SectionCard title=\"Revenue by month\" description=\"Invoiced totals across recent months.\">\n <RevenueChart data={data.revenueByMonth} />\n </SectionCard>\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <SectionCard title=\"Engagements\" description=\"By status\">\n <Distribution items={data.engagementsByStatus} />\n </SectionCard>\n <SectionCard title=\"Matters\" description=\"By status\">\n <Distribution items={data.mattersByStatus} />\n </SectionCard>\n <SectionCard title=\"Invoices\" description=\"By status\">\n <Distribution items={data.invoicesByStatus} />\n </SectionCard>\n </div>\n\n <SectionCard title=\"Time & billing\" description=\"Billable capacity across the practice.\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <HoursBar\n label=\"Billed hours\"\n value={data.billedHours}\n total={totalHours}\n tone=\"accent\"\n />\n <HoursBar\n label=\"Unbilled hours\"\n value={data.unbilledHours}\n total={totalHours}\n tone=\"warning\"\n />\n </div>\n </SectionCard>\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Radial gauge with a supporting two-number legend\n// ---------------------------------------------------------------------------\nconst GaugeRow: FC<{\n pct: number;\n tone: 'accent' | 'info';\n primary: string;\n primaryLabel: string;\n secondary: string;\n secondaryLabel: string;\n}> = ({ pct, tone, primary, primaryLabel, secondary, secondaryLabel }) => (\n <div className=\"flex items-center gap-5\">\n <Gauge pct={pct} tone={tone} />\n <div className=\"flex flex-1 flex-col gap-2.5\">\n <div className=\"flex items-baseline justify-between gap-3\">\n <span className=\"text-xs font-medium uppercase tracking-wider text-text-tertiary\">\n {primaryLabel}\n </span>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">{primary}</span>\n </div>\n <div className=\"h-px bg-border-seam\" />\n <div className=\"flex items-baseline justify-between gap-3\">\n <span className=\"text-xs font-medium uppercase tracking-wider text-text-tertiary\">\n {secondaryLabel}\n </span>\n <span className=\"text-sm font-semibold tabular-nums text-text-secondary\">{secondary}</span>\n </div>\n </div>\n </div>\n);\n\nconst Gauge: FC<{ pct: number; tone: 'accent' | 'info' }> = ({ pct, tone }) => {\n const clamped = Math.max(0, Math.min(100, pct));\n const r = 34;\n const c = 2 * Math.PI * r;\n const dash = (clamped / 100) * c;\n return (\n <div\n className={cn(\n 'relative h-24 w-24 shrink-0',\n tone === 'accent' ? 'text-accent' : 'text-text-link'\n )}\n >\n <svg viewBox=\"0 0 80 80\" className=\"h-full w-full -rotate-90\">\n <circle\n cx=\"40\"\n cy=\"40\"\n r={r}\n fill=\"none\"\n strokeWidth=\"8\"\n className=\"text-bg-sunken\"\n stroke=\"currentColor\"\n />\n <circle\n cx=\"40\"\n cy=\"40\"\n r={r}\n fill=\"none\"\n strokeWidth=\"8\"\n strokeLinecap=\"round\"\n stroke=\"currentColor\"\n strokeDasharray={`${dash} ${c}`}\n className=\"transition-[stroke-dasharray] duration-700\"\n />\n </svg>\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <span className=\"text-lg font-bold tabular-nums text-text-primary\">{clamped}%</span>\n </div>\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Revenue chart — gradient columns with value labels + month axis\n// ---------------------------------------------------------------------------\nconst RevenueChart: FC<{ data: { month: string; amount: number }[] }> = ({ data }) => {\n if (!data.length) return <p className=\"text-sm text-text-tertiary\">No invoiced revenue yet.</p>;\n const peak = Math.max(...data.map((d) => d.amount), 1);\n return (\n <div className=\"overflow-x-auto\">\n <div className=\"min-w-[520px]\">\n <div className=\"flex h-60 items-end gap-2 border-b border-border-seam\">\n {data.map((d) => (\n <div\n key={d.month}\n className=\"group flex h-full flex-1 flex-col items-center justify-end gap-1.5\"\n >\n <span className=\"whitespace-nowrap text-[10px] font-semibold tabular-nums text-text-secondary\">\n {fmtMoney(d.amount)}\n </span>\n <div\n className=\"w-full max-w-[3rem] rounded-t-lg bg-gradient-to-t from-accent/50 to-accent shadow-sm transition-all duration-200 group-hover:-translate-y-1 group-hover:shadow-md\"\n style={{ height: `${Math.max((d.amount / peak) * 90, 2)}%` }}\n title={fmtMoney(d.amount)}\n />\n </div>\n ))}\n </div>\n <div className=\"mt-2 flex gap-2\">\n {data.map((d) => (\n <span key={d.month} className=\"flex-1 text-center text-xs text-text-tertiary\">\n {monthShort(d.month)}\n </span>\n ))}\n </div>\n </div>\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Distribution — tone-colored gradient bars with count + share\n// ---------------------------------------------------------------------------\nconst Distribution: FC<{ items: { status: string; count: number }[] }> = ({ items }) => {\n if (!items.length) return <p className=\"text-sm text-text-tertiary\">No records yet.</p>;\n const max = Math.max(...items.map((i) => i.count), 1);\n const total = items.reduce((s, i) => s + i.count, 0) || 1;\n return (\n <ul className=\"space-y-3.5\">\n {items.map((i) => (\n <li\n key={i.status}\n className=\"grid grid-cols-[7.5rem_minmax(0,1fr)_auto] items-center gap-3\"\n >\n <StatusPill status={i.status} className=\"justify-self-start\" />\n <div className=\"h-2.5 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className={cn(\n 'h-full rounded-full bg-gradient-to-r transition-all duration-500',\n barGradientFor(i.status)\n )}\n style={{ width: `${Math.max((i.count / max) * 100, 4)}%` }}\n />\n </div>\n <span className=\"flex items-baseline justify-end gap-1.5 tabular-nums\">\n <span className=\"text-sm font-semibold text-text-primary\">{i.count}</span>\n <span className=\"text-xs text-text-tertiary\">\n {Math.round((i.count / total) * 100)}%\n </span>\n </span>\n </li>\n ))}\n </ul>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Hours bar — big gradient progress meter (billed vs unbilled)\n// ---------------------------------------------------------------------------\nconst HoursBar: FC<{ label: string; value: number; total: number; tone: 'accent' | 'warning' }> = ({\n label,\n value,\n total,\n tone,\n}) => {\n const pct = total > 0 ? Math.round((value / total) * 100) : 0;\n const grad =\n tone === 'accent'\n ? 'from-accent/60 to-accent'\n : 'from-status-warning-text/60 to-status-warning-text';\n return (\n <div className=\"rounded-xl border border-border-seam bg-bg-canvas p-4\">\n <div className=\"mb-2.5 flex items-center justify-between gap-3\">\n <span className=\"text-sm font-medium text-text-secondary\">{label}</span>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {fmtHours(value)} · {pct}%\n </span>\n </div>\n <div className=\"h-3 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className={cn('h-full rounded-full bg-gradient-to-r transition-all duration-700', grad)}\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Chart helpers (token-driven; self-contained to this page)\n// ---------------------------------------------------------------------------\ntype BarTone = 'success' | 'warning' | 'danger' | 'info' | 'accent' | 'neutral';\n\nconst BAR_GRADIENT: Record<BarTone, string> = {\n success: 'from-status-success-text/55 to-status-success-text',\n warning: 'from-status-warning-text/55 to-status-warning-text',\n danger: 'from-status-error-text/55 to-status-error-text',\n info: 'from-text-link/55 to-text-link',\n accent: 'from-accent/55 to-accent',\n neutral: 'from-text-tertiary/30 to-text-tertiary/70',\n};\n\n/** Mirror of the shared StatusPill tone mapping, used to tint chart bars. */\nconst STATUS_BAR_TONE: Record<string, BarTone> = {\n ACTIVE: 'success',\n DELIVERED: 'success',\n INVOICED: 'success',\n PAID: 'success',\n APPROVED: 'success',\n PASSED: 'success',\n CLEAR: 'success',\n COMPLETED: 'success',\n FINAL: 'success',\n PROSPECT: 'info',\n PROPOSAL: 'info',\n MEDIUM: 'info',\n INTERNAL_REVIEW: 'info',\n SUBMITTED: 'info',\n SENT: 'info',\n IN_REVIEW: 'info',\n REVIEW: 'warning',\n ON_HOLD: 'warning',\n HIGH: 'warning',\n CLIENT_REVIEW: 'warning',\n PENDING: 'warning',\n POTENTIAL_CONFLICT: 'warning',\n CANCELLED: 'danger',\n CRITICAL: 'danger',\n REJECTED: 'danger',\n OVERDUE: 'danger',\n FAILED: 'danger',\n CONFLICT: 'danger',\n ACCEPTED: 'accent',\n IN_PROGRESS: 'accent',\n OPEN: 'accent',\n BILLED: 'accent',\n INACTIVE: 'neutral',\n ARCHIVED: 'neutral',\n CLOSED: 'neutral',\n LOW: 'neutral',\n DRAFT: 'neutral',\n WAIVED: 'neutral',\n};\n\nfunction barGradientFor(status: string): string {\n return BAR_GRADIENT[STATUS_BAR_TONE[status] ?? 'accent'];\n}\n\n/** \"2026-07\" → \"Jul\" (falls back to the raw value if unparseable). */\nfunction monthShort(m: string): string {\n const [y, mo] = m.split('-').map(Number);\n if (!y || !mo) return m;\n const d = new Date(y, mo - 1, 1);\n return Number.isNaN(d.getTime()) ? m : d.toLocaleDateString(undefined, { month: 'short' });\n}\n"],"mappings":";;;;;;AAeA,IAAa,UAA0B;CACrC,IAAM,EAAE,SAAM,cAAW,UAAO,eAAY,EAAkB;CAC9D,IAAI,GAAW,OAAO,kBAAC,GAAD,EAAc,OAAM,qBAAsB,CAAA;CAChE,IAAI,GAAO,OAAO,kBAAC,GAAD;EAAmB;EAAO,eAAe,KAAK,EAAQ;CAAI,CAAA;CAC5E,IAAI,CAAC,GAAM,OAAO;CAElB,IAAM,IAAa,EAAK,gBAAgB,EAAK,aACvC,IAAc,IAAa,IAAI,KAAK,MAAO,EAAK,cAAc,IAAc,GAAG,IAAI,GACnF,IAAe,EAAK,mBAAmB,EAAK,yBAC5C,IACJ,IAAe,IAAI,KAAK,MAAO,EAAK,mBAAmB,IAAgB,GAAG,IAAI;CAEhF,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,OAAM;IACN,UAAS;IACT,MAAM,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;GACvC,CAAA;GAED,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAS,EAAK,gBAAgB;MACrC,MAAM,GAAG,EAAS,CAAY,EAAE;MAChC,MAAM,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;MACnC,MAAK;KACN,CAAA;KACD,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,GAAG,EAAe;MACzB,MAAM,GAAG,EAAS,EAAK,uBAAuB,EAAE;MAChD,MAAM,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA;MACvC,MAAK;KACN,CAAA;KACD,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,GAAG,EAAY;MACtB,MAAM,GAAG,EAAS,EAAK,WAAW,EAAE,MAAM,EAAS,CAAU;MAC7D,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;MAClC,MAAK;KACN,CAAA;KACD,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAK;MACZ,MAAM,GAAG,EAAK,YAAY;MAC1B,MAAM,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;MACtC,MAAK;KACN,CAAA;IACE;;GAGL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAa,OAAM;KAAkB,aAAY;eAC/C,kBAAC,GAAD;MACE,KAAK;MACL,MAAK;MACL,SAAS,EAAS,EAAK,gBAAgB;MACvC,cAAa;MACb,WAAW,EAAS,EAAK,uBAAuB;MAChD,gBAAe;KAChB,CAAA;IACU,CAAA,GACb,kBAAC,GAAD;KAAa,OAAM;KAAc,aAAY;eAC3C,kBAAC,GAAD;MACE,KAAK;MACL,MAAK;MACL,SAAS,EAAS,EAAK,WAAW;MAClC,cAAa;MACb,WAAW,EAAS,EAAK,aAAa;MACtC,gBAAe;KAChB,CAAA;IACU,CAAA,CACV;;GAEL,kBAAC,GAAD;IAAa,OAAM;IAAmB,aAAY;cAChD,kBAAC,GAAD,EAAc,MAAM,EAAK,eAAiB,CAAA;GAC/B,CAAA;GAEb,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAa,OAAM;MAAc,aAAY;gBAC3C,kBAAC,GAAD,EAAc,OAAO,EAAK,oBAAsB,CAAA;KACrC,CAAA;KACb,kBAAC,GAAD;MAAa,OAAM;MAAU,aAAY;gBACvC,kBAAC,GAAD,EAAc,OAAO,EAAK,gBAAkB,CAAA;KACjC,CAAA;KACb,kBAAC,GAAD;MAAa,OAAM;MAAW,aAAY;gBACxC,kBAAC,GAAD,EAAc,OAAO,EAAK,iBAAmB,CAAA;KAClC,CAAA;IACV;;GAEL,kBAAC,GAAD;IAAa,OAAM;IAAiB,aAAY;cAC9C,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAK;MACZ,OAAO;MACP,MAAK;KACN,CAAA,GACD,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAK;MACZ,OAAO;MACP,MAAK;KACN,CAAA,CACE;;GACM,CAAA;EACV;;AAET,GAKM,KAOA,EAAE,QAAK,SAAM,YAAS,iBAAc,cAAW,wBACnD,kBAAC,OAAD;CAAK,WAAU;WAAf,CACE,kBAAC,GAAD;EAAY;EAAW;CAAO,CAAA,GAC9B,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eACb;IACG,CAAA,GACN,kBAAC,QAAD;KAAM,WAAU;eAAwD;IAAc,CAAA,CACnF;;GACL,kBAAC,OAAD,EAAK,WAAU,sBAAuB,CAAA;GACtC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eACb;IACG,CAAA,GACN,kBAAC,QAAD;KAAM,WAAU;eAA0D;IAAgB,CAAA,CACvF;;EACF;GACF;IAGD,KAAuD,EAAE,QAAK,cAAW;CAC7E,IAAM,IAAU,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,CAAG,CAAC,GAExC,IAAI,IAAI,KAAK,KAAK,IAClB,IAAQ,IAAU,MAAO;CAC/B,OACE,kBAAC,OAAD;EACE,WAAW,EACT,+BACA,MAAS,WAAW,gBAAgB,gBACtC;YAJF,CAME,kBAAC,OAAD;GAAK,SAAQ;GAAY,WAAU;aAAnC,CACE,kBAAC,UAAD;IACE,IAAG;IACH,IAAG;IACA;IACH,MAAK;IACL,aAAY;IACZ,WAAU;IACV,QAAO;GACR,CAAA,GACD,kBAAC,UAAD;IACE,IAAG;IACH,IAAG;IACA;IACH,MAAK;IACL,aAAY;IACZ,eAAc;IACd,QAAO;IACP,iBAAiB,GAAG,EAAK,GAAG;IAC5B,WAAU;GACX,CAAA,CACE;MACL,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,QAAD;IAAM,WAAU;cAAhB,CAAoE,GAAQ,GAAO;;EAChF,CAAA,CACF;;AAET,GAKM,KAAmE,EAAE,cAAW;CACpF,IAAI,CAAC,EAAK,QAAQ,OAAO,kBAAC,KAAD;EAAG,WAAU;YAA6B;CAA2B,CAAA;CAC9F,IAAM,IAAO,KAAK,IAAI,GAAG,EAAK,KAAK,MAAM,EAAE,MAAM,GAAG,CAAC;CACrD,OACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAK,KAAK,MACT,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAS,EAAE,MAAM;KACd,CAAA,GACN,kBAAC,OAAD;MACE,WAAU;MACV,OAAO,EAAE,QAAQ,GAAG,KAAK,IAAK,EAAE,SAAS,IAAQ,IAAI,CAAC,EAAE,GAAG;MAC3D,OAAO,EAAS,EAAE,MAAM;KACzB,CAAA,CACE;OAXE,EAAE,KAWJ,CACN;GACE,CAAA,GACL,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAK,KAAK,MACT,kBAAC,QAAD;KAAoB,WAAU;eAC3B,EAAW,EAAE,KAAK;IACf,GAFK,EAAE,KAEP,CACP;GACE,CAAA,CACF;;CACF,CAAA;AAET,GAKM,KAAoE,EAAE,eAAY;CACtF,IAAI,CAAC,EAAM,QAAQ,OAAO,kBAAC,KAAD;EAAG,WAAU;YAA6B;CAAkB,CAAA;CACtF,IAAM,IAAM,KAAK,IAAI,GAAG,EAAM,KAAK,MAAM,EAAE,KAAK,GAAG,CAAC,GAC9C,IAAQ,EAAM,QAAQ,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC,KAAK;CACxD,OACE,kBAAC,MAAD;EAAI,WAAU;YACX,EAAM,KAAK,MACV,kBAAC,MAAD;GAEE,WAAU;aAFZ;IAIE,kBAAC,GAAD;KAAY,QAAQ,EAAE;KAAQ,WAAU;IAAsB,CAAA;IAC9D,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MACE,WAAW,EACT,oEACA,EAAe,EAAE,MAAM,CACzB;MACA,OAAO,EAAE,OAAO,GAAG,KAAK,IAAK,EAAE,QAAQ,IAAO,KAAK,CAAC,EAAE,GAAG;KAC1D,CAAA;IACE,CAAA;IACL,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,QAAD;MAAM,WAAU;gBAA2C,EAAE;KAAY,CAAA,GACzE,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACG,KAAK,MAAO,EAAE,QAAQ,IAAS,GAAG,GAAE,GACjC;OACF;;GACJ;KAnBG,EAAE,MAmBL,CACL;CACC,CAAA;AAER,GAKM,KAA6F,EACjG,UACA,UACA,UACA,cACI;CACJ,IAAM,IAAM,IAAQ,IAAI,KAAK,MAAO,IAAQ,IAAS,GAAG,IAAI,GACtD,IACJ,MAAS,WACL,6BACA;CACN,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAU;cAA2C;GAAY,CAAA,GACvE,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACG,EAAS,CAAK;KAAE;KAAI;KAAI;IACrB;KACH;MACL,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IACE,WAAW,EAAG,oEAAoE,CAAI;IACtF,OAAO,EAAE,OAAO,GAAG,EAAI,GAAG;GAC3B,CAAA;EACE,CAAA,CACF;;AAET,GAOM,IAAwC;CAC5C,SAAS;CACT,SAAS;CACT,QAAQ;CACR,MAAM;CACN,QAAQ;CACR,SAAS;AACX,GAGM,IAA2C;CAC/C,QAAQ;CACR,WAAW;CACX,UAAU;CACV,MAAM;CACN,UAAU;CACV,QAAQ;CACR,OAAO;CACP,WAAW;CACX,OAAO;CACP,UAAU;CACV,UAAU;CACV,QAAQ;CACR,iBAAiB;CACjB,WAAW;CACX,MAAM;CACN,WAAW;CACX,QAAQ;CACR,SAAS;CACT,MAAM;CACN,eAAe;CACf,SAAS;CACT,oBAAoB;CACpB,WAAW;CACX,UAAU;CACV,UAAU;CACV,SAAS;CACT,QAAQ;CACR,UAAU;CACV,UAAU;CACV,aAAa;CACb,MAAM;CACN,QAAQ;CACR,UAAU;CACV,UAAU;CACV,QAAQ;CACR,KAAK;CACL,OAAO;CACP,QAAQ;AACV;AAEA,SAAS,EAAe,GAAwB;CAC9C,OAAO,EAAa,EAAgB,MAAW;AACjD;AAGA,SAAS,EAAW,GAAmB;CACrC,IAAM,CAAC,GAAG,KAAM,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CACvC,IAAI,CAAC,KAAK,CAAC,GAAI,OAAO;CACtB,IAAM,IAAI,IAAI,KAAK,GAAG,IAAK,GAAG,CAAC;CAC/B,OAAO,OAAO,MAAM,EAAE,QAAQ,CAAC,IAAI,IAAI,EAAE,mBAAmB,KAAA,GAAW,EAAE,OAAO,QAAQ,CAAC;AAC3F"}
1
+ {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/AnalyticsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { BarChart3, TrendingUp, Clock, Wallet, Scale, AlertTriangle } from 'lucide-react';\nimport { useDashboardStats, useWriteOffs } from '../hooks/useProServiceWorldApi';\nimport { cn } from '../utils/cn';\nimport { plural, useTr } from '../hooks/useTr';\nimport {\n PageHeader,\n SectionCard,\n MetricCard,\n LoadingBlock,\n ErrorState,\n StatusPill,\n Empty,\n TableWrap,\n Th,\n Td,\n fmtMoney,\n fmtHours,\n fmtDate,\n} from '../components/ui';\n\nexport const AnalyticsPage: FC = () => {\n const tr = useTr();\n const { data, isLoading, error, refetch } = useDashboardStats();\n const writeOffs = useWriteOffs({ pagination: { limit: 10 } });\n if (isLoading) return <LoadingBlock label={tr('psw.analytics.loading', 'Crunching numbers…')} />;\n if (error) return <ErrorState error={error} onRetry={() => void refetch()} />;\n if (!data) return null;\n\n const totalHours = data.unbilledHours + data.billedHours;\n const utilization = totalHours > 0 ? Math.round((data.billedHours / totalHours) * 100) : 0;\n const billableBase = data.paidInvoiceTotal + data.outstandingInvoiceTotal;\n const collectionRate =\n billableBase > 0 ? Math.round((data.paidInvoiceTotal / billableBase) * 100) : 0;\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title={tr('psw.analytics.title', 'Analytics')}\n subtitle={tr(\n 'psw.analytics.subtitle',\n 'Real, live metrics across the practice — no estimates, straight from your records.'\n )}\n icon={<BarChart3 className=\"h-6 w-6\" />}\n />\n\n <div className=\"grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <MetricCard\n label={tr('psw.analytics.collectedRevenue', 'Collected revenue')}\n value={fmtMoney(data.paidInvoiceTotal)}\n hint={tr('psw.analytics.invoicedHint', '{{amount}} invoiced', {\n amount: fmtMoney(billableBase),\n })}\n icon={<Wallet className=\"h-4 w-4\" />}\n tone=\"success\"\n />\n <MetricCard\n label={tr('psw.analytics.collectionRate', 'Collection rate')}\n value={`${collectionRate}%`}\n hint={tr('psw.analytics.outstandingHint', '{{amount}} outstanding', {\n amount: fmtMoney(data.outstandingInvoiceTotal),\n })}\n icon={<TrendingUp className=\"h-4 w-4\" />}\n tone=\"accent\"\n />\n <MetricCard\n label={tr('psw.analytics.utilization', 'Utilization')}\n value={`${utilization}%`}\n hint={tr('psw.analytics.hoursOfTotal', '{{billed}} of {{total}}', {\n billed: fmtHours(data.billedHours),\n total: fmtHours(totalHours),\n })}\n icon={<Clock className=\"h-4 w-4\" />}\n tone=\"info\"\n />\n <MetricCard\n label={tr('psw.analytics.activeEngagements', 'Active engagements')}\n value={data.activeEngagements}\n hint={tr('psw.analytics.openMattersHint', '{{count}} open matters', {\n count: data.openMatters,\n })}\n icon={<BarChart3 className=\"h-4 w-4\" />}\n tone=\"warning\"\n />\n </div>\n\n {/* Practice-health meters — collection & utilization at a glance */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <SectionCard\n title={tr('psw.analytics.collectionRate', 'Collection rate')}\n description={tr('psw.analytics.collectionRateDescription', 'Paid vs. total invoiced.')}\n >\n <GaugeRow\n pct={collectionRate}\n tone=\"accent\"\n primary={fmtMoney(data.paidInvoiceTotal)}\n primaryLabel={tr('psw.analytics.collected', 'Collected')}\n secondary={fmtMoney(data.outstandingInvoiceTotal)}\n secondaryLabel={tr('psw.analytics.outstanding', 'Outstanding')}\n />\n </SectionCard>\n <SectionCard\n title={tr('psw.analytics.utilization', 'Utilization')}\n description={tr('psw.analytics.utilizationDescription', 'Billed vs. total logged hours.')}\n >\n <GaugeRow\n pct={utilization}\n tone=\"info\"\n primary={fmtHours(data.billedHours)}\n primaryLabel={tr('psw.analytics.billed', 'Billed')}\n secondary={fmtHours(data.unbilledHours)}\n secondaryLabel={tr('psw.analytics.unbilled', 'Unbilled')}\n />\n </SectionCard>\n </div>\n\n <SectionCard\n title={tr('psw.analytics.revenueByMonth', 'Revenue by month')}\n description={tr(\n 'psw.analytics.revenueByMonthDescription',\n 'Invoiced totals across recent months.'\n )}\n >\n <RevenueChart data={data.revenueByMonth} />\n </SectionCard>\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <SectionCard\n title={tr('psw.analytics.engagements', 'Engagements')}\n description={tr('psw.analytics.byStatus', 'By status')}\n >\n <Distribution items={data.engagementsByStatus} />\n </SectionCard>\n <SectionCard\n title={tr('psw.analytics.matters', 'Matters')}\n description={tr('psw.analytics.byStatus', 'By status')}\n >\n <Distribution items={data.mattersByStatus} />\n </SectionCard>\n <SectionCard\n title={tr('psw.analytics.invoices', 'Invoices')}\n description={tr('psw.analytics.byStatus', 'By status')}\n >\n <Distribution items={data.invoicesByStatus} />\n </SectionCard>\n </div>\n\n <SectionCard\n title={tr('psw.analytics.timeBilling', 'Time & billing')}\n description={tr(\n 'psw.analytics.timeBillingDescription',\n 'Billable capacity across the practice.'\n )}\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <HoursBar\n label={tr('psw.analytics.billedHours', 'Billed hours')}\n value={data.billedHours}\n total={totalHours}\n tone=\"accent\"\n />\n <HoursBar\n label={tr('psw.analytics.unbilledHours', 'Unbilled hours')}\n value={data.unbilledHours}\n total={totalHours}\n tone=\"warning\"\n />\n </div>\n </SectionCard>\n\n <div className=\"grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <MetricCard\n label={tr('psw.analytics.realization', 'Realization')}\n value={`${Math.round(data.realizationPct)}%`}\n hint={tr('psw.analytics.realizationHint', '{{billed}} of {{worked}} worked', {\n billed: fmtMoney(data.billedValue),\n worked: fmtMoney(data.workedValue),\n })}\n icon={<Scale className=\"h-4 w-4\" />}\n tone=\"accent\"\n />\n <MetricCard\n label={tr('psw.analytics.writeOffs', 'Write-offs')}\n value={fmtMoney(data.writeOffTotal)}\n hint={tr('psw.analytics.writeOffsHint', 'Total written off')}\n icon={<AlertTriangle className=\"h-4 w-4\" />}\n tone=\"warning\"\n />\n </div>\n\n <SectionCard\n title={tr('psw.analytics.agedReceivables', 'Aged receivables')}\n description={tr(\n 'psw.analytics.agedReceivablesDescription',\n 'Outstanding invoice value by age — the margin-leak early warning.'\n )}\n >\n <div className=\"grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <AgingBucket\n label={tr('psw.analytics.agingCurrent', 'Current')}\n bucket={data.agedReceivables.current}\n tone=\"success\"\n />\n <AgingBucket\n label={tr('psw.analytics.aging30', '30 days')}\n bucket={data.agedReceivables.thirty}\n tone=\"info\"\n />\n <AgingBucket\n label={tr('psw.analytics.aging60', '60 days')}\n bucket={data.agedReceivables.sixty}\n tone=\"warning\"\n />\n <AgingBucket\n label={tr('psw.analytics.aging90', '90+ days')}\n bucket={data.agedReceivables.ninetyPlus}\n tone=\"danger\"\n />\n </div>\n </SectionCard>\n\n <SectionCard\n title={tr('psw.analytics.writeOffs', 'Write-offs')}\n description={tr(\n 'psw.analytics.writeOffsDescription',\n 'Append-only audit of written-off amounts.'\n )}\n >\n {writeOffs.isLoading ? (\n <LoadingBlock label={tr('psw.analytics.writeOffsLoading', 'Loading write-offs…')} />\n ) : !writeOffs.data?.length ? (\n <Empty\n title={tr('psw.analytics.writeOffsEmptyTitle', 'No write-offs')}\n description={tr(\n 'psw.analytics.writeOffsEmptyDescription',\n 'Nothing has been written off yet.'\n )}\n />\n ) : (\n <TableWrap>\n <thead>\n <tr>\n <Th>{tr('psw.common.date', 'Date')}</Th>\n <Th>{tr('psw.analytics.reason', 'Reason')}</Th>\n <Th className=\"text-end\">{tr('psw.common.amount', 'Amount')}</Th>\n </tr>\n </thead>\n <tbody>\n {writeOffs.data.map((w) => (\n <tr key={w.id}>\n <Td>{fmtDate(w.createdAt)}</Td>\n <Td>{w.reason}</Td>\n <Td className=\"text-end\">{fmtMoney(w.amount)}</Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n )}\n </SectionCard>\n </div>\n );\n};\n\nconst AgingBucket: FC<{\n label: string;\n bucket: { amount: number; count: number };\n tone: 'success' | 'info' | 'warning' | 'danger';\n}> = ({ label, bucket, tone }) => {\n const tr = useTr();\n return (\n <MetricCard\n label={label}\n value={fmtMoney(bucket.amount)}\n hint={plural(\n tr,\n 'psw.invoices.count',\n bucket.count,\n '{{count}} invoice',\n '{{count}} invoices'\n )}\n tone={tone}\n />\n );\n};\n\n// ---------------------------------------------------------------------------\n// Radial gauge with a supporting two-number legend\n// ---------------------------------------------------------------------------\nconst GaugeRow: FC<{\n pct: number;\n tone: 'accent' | 'info';\n primary: string;\n primaryLabel: string;\n secondary: string;\n secondaryLabel: string;\n}> = ({ pct, tone, primary, primaryLabel, secondary, secondaryLabel }) => (\n <div className=\"flex items-center gap-5\">\n <Gauge pct={pct} tone={tone} />\n <div className=\"flex flex-1 flex-col gap-2.5\">\n <div className=\"flex items-baseline justify-between gap-3\">\n <span className=\"text-xs font-medium uppercase tracking-wider text-text-tertiary\">\n {primaryLabel}\n </span>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">{primary}</span>\n </div>\n <div className=\"h-px bg-border-seam\" />\n <div className=\"flex items-baseline justify-between gap-3\">\n <span className=\"text-xs font-medium uppercase tracking-wider text-text-tertiary\">\n {secondaryLabel}\n </span>\n <span className=\"text-sm font-semibold tabular-nums text-text-secondary\">{secondary}</span>\n </div>\n </div>\n </div>\n);\n\nconst Gauge: FC<{ pct: number; tone: 'accent' | 'info' }> = ({ pct, tone }) => {\n const clamped = Math.max(0, Math.min(100, pct));\n const r = 34;\n const c = 2 * Math.PI * r;\n const dash = (clamped / 100) * c;\n return (\n <div\n className={cn(\n 'relative h-24 w-24 shrink-0',\n tone === 'accent' ? 'text-accent' : 'text-text-link'\n )}\n >\n <svg viewBox=\"0 0 80 80\" className=\"h-full w-full -rotate-90\">\n <circle\n cx=\"40\"\n cy=\"40\"\n r={r}\n fill=\"none\"\n strokeWidth=\"8\"\n className=\"text-bg-sunken\"\n stroke=\"currentColor\"\n />\n <circle\n cx=\"40\"\n cy=\"40\"\n r={r}\n fill=\"none\"\n strokeWidth=\"8\"\n strokeLinecap=\"round\"\n stroke=\"currentColor\"\n strokeDasharray={`${dash} ${c}`}\n className=\"transition-[stroke-dasharray] duration-700\"\n />\n </svg>\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <span className=\"text-lg font-bold tabular-nums text-text-primary\">{clamped}%</span>\n </div>\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Revenue chart — gradient columns with value labels + month axis\n// ---------------------------------------------------------------------------\nconst RevenueChart: FC<{ data: { month: string; amount: number }[] }> = ({ data }) => {\n const tr = useTr();\n if (!data.length)\n return (\n <p className=\"text-sm text-text-tertiary\">\n {tr('psw.dashboard.noRevenue', 'No invoiced revenue yet.')}\n </p>\n );\n const peak = Math.max(...data.map((d) => d.amount), 1);\n return (\n <div className=\"overflow-x-auto\">\n <div className=\"min-w-[520px]\">\n <div className=\"flex h-60 items-end gap-2 border-b border-border-seam\">\n {data.map((d) => (\n <div\n key={d.month}\n className=\"group flex h-full flex-1 flex-col items-center justify-end gap-1.5\"\n >\n <span className=\"whitespace-nowrap text-[10px] font-semibold tabular-nums text-text-secondary\">\n {fmtMoney(d.amount)}\n </span>\n <div\n className=\"w-full max-w-[3rem] rounded-t-lg bg-gradient-to-t from-accent/50 to-accent shadow-sm transition-all duration-200 group-hover:-translate-y-1 group-hover:shadow-md\"\n style={{ height: `${Math.max((d.amount / peak) * 90, 2)}%` }}\n title={fmtMoney(d.amount)}\n />\n </div>\n ))}\n </div>\n <div className=\"mt-2 flex gap-2\">\n {data.map((d) => (\n <span key={d.month} className=\"flex-1 text-center text-xs text-text-tertiary\">\n {monthShort(d.month)}\n </span>\n ))}\n </div>\n </div>\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Distribution — tone-colored gradient bars with count + share\n// ---------------------------------------------------------------------------\nconst Distribution: FC<{ items: { status: string; count: number }[] }> = ({ items }) => {\n const tr = useTr();\n if (!items.length)\n return (\n <p className=\"text-sm text-text-tertiary\">\n {tr('psw.dashboard.noRecords', 'No records yet.')}\n </p>\n );\n const max = Math.max(...items.map((i) => i.count), 1);\n const total = items.reduce((s, i) => s + i.count, 0) || 1;\n return (\n <ul className=\"space-y-3.5\">\n {items.map((i) => (\n <li\n key={i.status}\n className=\"grid grid-cols-[7.5rem_minmax(0,1fr)_auto] items-center gap-3\"\n >\n <StatusPill status={i.status} className=\"justify-self-start\" />\n <div className=\"h-2.5 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className={cn(\n 'h-full rounded-full bg-gradient-to-r transition-all duration-500',\n barGradientFor(i.status)\n )}\n style={{ width: `${Math.max((i.count / max) * 100, 4)}%` }}\n />\n </div>\n <span className=\"flex items-baseline justify-end gap-1.5 tabular-nums\">\n <span className=\"text-sm font-semibold text-text-primary\">{i.count}</span>\n <span className=\"text-xs text-text-tertiary\">\n {Math.round((i.count / total) * 100)}%\n </span>\n </span>\n </li>\n ))}\n </ul>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Hours bar — big gradient progress meter (billed vs unbilled)\n// ---------------------------------------------------------------------------\nconst HoursBar: FC<{ label: string; value: number; total: number; tone: 'accent' | 'warning' }> = ({\n label,\n value,\n total,\n tone,\n}) => {\n const pct = total > 0 ? Math.round((value / total) * 100) : 0;\n const grad =\n tone === 'accent'\n ? 'from-accent/60 to-accent'\n : 'from-status-warning-text/60 to-status-warning-text';\n return (\n <div className=\"rounded-xl border border-border-seam bg-bg-canvas p-4\">\n <div className=\"mb-2.5 flex items-center justify-between gap-3\">\n <span className=\"text-sm font-medium text-text-secondary\">{label}</span>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary\">\n {fmtHours(value)} · {pct}%\n </span>\n </div>\n <div className=\"h-3 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className={cn('h-full rounded-full bg-gradient-to-r transition-all duration-700', grad)}\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Chart helpers (token-driven; self-contained to this page)\n// ---------------------------------------------------------------------------\ntype BarTone = 'success' | 'warning' | 'danger' | 'info' | 'accent' | 'neutral';\n\nconst BAR_GRADIENT: Record<BarTone, string> = {\n success: 'from-status-success-text/55 to-status-success-text',\n warning: 'from-status-warning-text/55 to-status-warning-text',\n danger: 'from-status-error-text/55 to-status-error-text',\n info: 'from-text-link/55 to-text-link',\n accent: 'from-accent/55 to-accent',\n neutral: 'from-text-tertiary/30 to-text-tertiary/70',\n};\n\n/** Mirror of the shared StatusPill tone mapping, used to tint chart bars. */\nconst STATUS_BAR_TONE: Record<string, BarTone> = {\n ACTIVE: 'success',\n DELIVERED: 'success',\n INVOICED: 'success',\n PAID: 'success',\n APPROVED: 'success',\n PASSED: 'success',\n CLEAR: 'success',\n COMPLETED: 'success',\n FINAL: 'success',\n PROSPECT: 'info',\n PROPOSAL: 'info',\n MEDIUM: 'info',\n INTERNAL_REVIEW: 'info',\n SUBMITTED: 'info',\n SENT: 'info',\n IN_REVIEW: 'info',\n REVIEW: 'warning',\n ON_HOLD: 'warning',\n HIGH: 'warning',\n CLIENT_REVIEW: 'warning',\n PENDING: 'warning',\n POTENTIAL_CONFLICT: 'warning',\n CANCELLED: 'danger',\n CRITICAL: 'danger',\n REJECTED: 'danger',\n OVERDUE: 'danger',\n FAILED: 'danger',\n CONFLICT: 'danger',\n ACCEPTED: 'accent',\n IN_PROGRESS: 'accent',\n OPEN: 'accent',\n BILLED: 'accent',\n INACTIVE: 'neutral',\n ARCHIVED: 'neutral',\n CLOSED: 'neutral',\n LOW: 'neutral',\n DRAFT: 'neutral',\n WAIVED: 'neutral',\n};\n\nfunction barGradientFor(status: string): string {\n return BAR_GRADIENT[STATUS_BAR_TONE[status] ?? 'accent'];\n}\n\n/** \"2026-07\" → \"Jul\" (falls back to the raw value if unparseable). */\nfunction monthShort(m: string): string {\n const [y, mo] = m.split('-').map(Number);\n if (!y || !mo) return m;\n const d = new Date(y, mo - 1, 1);\n return Number.isNaN(d.getTime()) ? m : d.toLocaleDateString(undefined, { month: 'short' });\n}\n"],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import { useClient as e, useClientTeamAssignments as t, useConflictChecks as n, useCreateConflictCheck as r } from "../hooks/useProServiceWorldApi.js";
2
2
  import { useNav as i } from "../utils/nav.js";
3
3
  import { cn as a } from "../utils/cn.js";
4
- import { Banner as o, Empty as s, ErrorState as c, Field as l, LoadingBlock as u, Modal as d, PageHeader as f, SectionCard as p, StatusPill as m, TableWrap as h, Td as g, TextArea as _, TextInput as v, Th as y, fmtDate as b, fmtMoney as x, humanize as S } from "../components/ui.js";
4
+ import { humanize as o } from "../utils/humanize.js";
5
+ import { Banner as s, Empty as c, ErrorState as l, Field as u, LoadingBlock as d, Modal as f, PageHeader as p, SectionCard as m, StatusPill as h, TableWrap as g, Td as _, TextArea as v, TextInput as y, Th as b, fmtDate as x, fmtMoney as S } from "../components/ui.js";
5
6
  import { useActorNames as C } from "../hooks/useActorNames.js";
6
7
  import { DiscussionThread as w } from "../components/DiscussionThread.js";
7
8
  import { useMemo as T, useState as E } from "react";
@@ -11,30 +12,30 @@ import { Fragment as B, jsx as V, jsxs as H } from "react/jsx-runtime";
11
12
  import { Button as U } from "@burdenoff/fe-libs/ui";
12
13
  //#region src/proserviceworld/pages/ClientDetailPage.tsx
13
14
  var W = (e) => e.split(/\s+/).filter(Boolean).slice(0, 2).map((e) => e[0]?.toUpperCase() ?? "").join("") || "?", G = () => {
14
- let { id: r = "" } = z(), a = i(), { data: o, isLoading: l, error: d, refetch: _ } = e(r), v = n({ clientId: r }), [R, B] = E(!1), G = T(() => [...(o?.engagements ?? []).map((e) => ({
15
+ let { id: r = "" } = z(), a = i(), { data: s, isLoading: u, error: f, refetch: v } = e(r), y = n({ clientId: r }), [R, B] = E(!1), G = T(() => [...(s?.engagements ?? []).map((e) => ({
15
16
  type: "engagement",
16
17
  id: e.id,
17
18
  name: e.name
18
- })), ...(o?.matters ?? []).map((e) => ({
19
+ })), ...(s?.matters ?? []).map((e) => ({
19
20
  type: "matter",
20
21
  id: e.id,
21
22
  name: e.reference || e.title
22
- }))], [o]), K = t(G), X = C([
23
- o?.relationshipOwnerId,
24
- ...(o?.engagements ?? []).map((e) => e.responsiblePartnerId),
25
- ...(o?.matters ?? []).map((e) => e.leadPractitionerId),
23
+ }))], [s]), K = t(G), X = C([
24
+ s?.relationshipOwnerId,
25
+ ...(s?.engagements ?? []).map((e) => e.responsiblePartnerId),
26
+ ...(s?.matters ?? []).map((e) => e.leadPractitionerId),
26
27
  ...(K.data ?? []).map((e) => e.practitionerId)
27
28
  ]);
28
- if (l) return /* @__PURE__ */ V(u, {});
29
- if (d) return /* @__PURE__ */ V(c, {
30
- error: d,
31
- onRetry: () => void _()
29
+ if (u) return /* @__PURE__ */ V(d, {});
30
+ if (f) return /* @__PURE__ */ V(l, {
31
+ error: f,
32
+ onRetry: () => void v()
32
33
  });
33
- if (!o) return /* @__PURE__ */ V(s, {
34
+ if (!s) return /* @__PURE__ */ V(c, {
34
35
  title: "Client not found",
35
36
  description: "It may have been archived or belongs to another workspace."
36
37
  });
37
- let Z = o.contactName || o.name;
38
+ let Z = s.contactName || s.name;
38
39
  return /* @__PURE__ */ H("div", {
39
40
  className: "space-y-6",
40
41
  children: [
@@ -44,21 +45,21 @@ var W = (e) => e.split(/\s+/).filter(Boolean).slice(0, 2).map((e) => e[0]?.toUpp
44
45
  className: "flex items-center gap-1.5 text-sm text-text-secondary transition-colors hover:text-accent",
45
46
  children: [/* @__PURE__ */ V(D, { className: "h-4 w-4" }), " Back to clients"]
46
47
  }),
47
- /* @__PURE__ */ V(f, {
48
- title: o.name,
49
- subtitle: [o.type === "ORGANIZATION" ? "Organization" : "Individual", o.industry].filter(Boolean).join(" · "),
50
- actions: /* @__PURE__ */ V(m, { status: o.status })
48
+ /* @__PURE__ */ V(p, {
49
+ title: s.name,
50
+ subtitle: [s.type === "ORGANIZATION" ? "Organization" : "Individual", s.industry].filter(Boolean).join(" · "),
51
+ actions: /* @__PURE__ */ V(h, { status: s.status })
51
52
  }),
52
- o.tags.length > 0 && /* @__PURE__ */ V("div", {
53
+ s.tags.length > 0 && /* @__PURE__ */ V("div", {
53
54
  className: "flex flex-wrap gap-2",
54
- children: o.tags.map((e) => /* @__PURE__ */ V("span", {
55
+ children: s.tags.map((e) => /* @__PURE__ */ V("span", {
55
56
  className: "rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary",
56
57
  children: e
57
58
  }, e))
58
59
  }),
59
60
  /* @__PURE__ */ H("div", {
60
61
  className: "grid grid-cols-1 gap-4 lg:grid-cols-3",
61
- children: [/* @__PURE__ */ V(p, {
62
+ children: [/* @__PURE__ */ V(m, {
62
63
  title: "Contact",
63
64
  className: "lg:col-span-1",
64
65
  children: /* @__PURE__ */ H("div", {
@@ -72,44 +73,44 @@ var W = (e) => e.split(/\s+/).filter(Boolean).slice(0, 2).map((e) => e[0]?.toUpp
72
73
  className: "min-w-0",
73
74
  children: [/* @__PURE__ */ V("div", {
74
75
  className: "truncate text-sm font-semibold text-text-primary",
75
- children: o.contactName || o.name
76
+ children: s.contactName || s.name
76
77
  }), /* @__PURE__ */ V("div", {
77
78
  className: "truncate text-xs text-text-tertiary",
78
- children: o.contactName ? "Primary contact" : "Client record"
79
+ children: s.contactName ? "Primary contact" : "Client record"
79
80
  })]
80
81
  })]
81
82
  }), /* @__PURE__ */ H("dl", {
82
83
  className: "space-y-2.5 text-sm",
83
84
  children: [
84
- o.contactEmail && /* @__PURE__ */ V(J, {
85
+ s.contactEmail && /* @__PURE__ */ V(J, {
85
86
  icon: /* @__PURE__ */ V(M, { className: "h-4 w-4" }),
86
- value: o.contactEmail
87
+ value: s.contactEmail
87
88
  }),
88
- o.contactPhone && /* @__PURE__ */ V(J, {
89
+ s.contactPhone && /* @__PURE__ */ V(J, {
89
90
  icon: /* @__PURE__ */ V(P, { className: "h-4 w-4" }),
90
- value: o.contactPhone
91
+ value: s.contactPhone
91
92
  }),
92
- o.address && /* @__PURE__ */ V(J, {
93
+ s.address && /* @__PURE__ */ V(J, {
93
94
  icon: /* @__PURE__ */ V(N, { className: "h-4 w-4" }),
94
- value: o.address
95
+ value: s.address
95
96
  }),
96
- o.notes && /* @__PURE__ */ V(J, {
97
+ s.notes && /* @__PURE__ */ V(J, {
97
98
  icon: /* @__PURE__ */ V(I, { className: "h-4 w-4" }),
98
- value: o.notes,
99
+ value: s.notes,
99
100
  multiline: !0
100
101
  }),
101
- o.relationshipOwnerId && /* @__PURE__ */ V(J, {
102
+ s.relationshipOwnerId && /* @__PURE__ */ V(J, {
102
103
  icon: /* @__PURE__ */ V(L, { className: "h-4 w-4" }),
103
- value: `${X.nameOf(o.relationshipOwnerId)} · relationship owner`
104
+ value: `${X.nameOf(s.relationshipOwnerId)} · relationship owner`
104
105
  }),
105
- !o.contactEmail && !o.contactPhone && !o.address && !o.notes && !o.relationshipOwnerId && /* @__PURE__ */ V("p", {
106
+ !s.contactEmail && !s.contactPhone && !s.address && !s.notes && !s.relationshipOwnerId && /* @__PURE__ */ V("p", {
106
107
  className: "text-sm text-text-tertiary",
107
108
  children: "No contact details recorded yet."
108
109
  })
109
110
  ]
110
111
  })]
111
112
  })
112
- }), /* @__PURE__ */ V(p, {
113
+ }), /* @__PURE__ */ V(m, {
113
114
  title: "Conflict checks",
114
115
  className: "lg:col-span-2",
115
116
  action: /* @__PURE__ */ H(U, {
@@ -118,34 +119,34 @@ var W = (e) => e.split(/\s+/).filter(Boolean).slice(0, 2).map((e) => e[0]?.toUpp
118
119
  onClick: () => B(!0),
119
120
  children: [/* @__PURE__ */ V(j, { className: "mr-1.5 h-4 w-4" }), " Run check"]
120
121
  }),
121
- children: v.isLoading ? /* @__PURE__ */ V(u, { label: "Loading…" }) : !v.data || v.data.items.length === 0 ? /* @__PURE__ */ V(s, {
122
+ children: y.isLoading ? /* @__PURE__ */ V(d, { label: "Loading…" }) : !y.data || y.data.items.length === 0 ? /* @__PURE__ */ V(c, {
122
123
  title: "No conflict checks",
123
124
  description: "Run a conflict check before accepting new work for this client.",
124
125
  icon: /* @__PURE__ */ V(j, { className: "h-7 w-7" })
125
- }) : /* @__PURE__ */ H(h, { children: [/* @__PURE__ */ V("thead", { children: /* @__PURE__ */ H("tr", { children: [
126
- /* @__PURE__ */ V(y, { children: "Parties searched" }),
127
- /* @__PURE__ */ V(y, { children: "Date" }),
128
- /* @__PURE__ */ V(y, {
126
+ }) : /* @__PURE__ */ H(g, { children: [/* @__PURE__ */ V("thead", { children: /* @__PURE__ */ H("tr", { children: [
127
+ /* @__PURE__ */ V(b, { children: "Parties searched" }),
128
+ /* @__PURE__ */ V(b, { children: "Date" }),
129
+ /* @__PURE__ */ V(b, {
129
130
  className: "text-right",
130
131
  children: "Status"
131
132
  })
132
- ] }) }), /* @__PURE__ */ V("tbody", { children: v.data.items.map((e) => /* @__PURE__ */ H("tr", {
133
+ ] }) }), /* @__PURE__ */ V("tbody", { children: y.data.items.map((e) => /* @__PURE__ */ H("tr", {
133
134
  className: "transition-colors hover:bg-bg-sunken/60",
134
135
  children: [
135
- /* @__PURE__ */ H(g, { children: [/* @__PURE__ */ V("div", {
136
+ /* @__PURE__ */ H(_, { children: [/* @__PURE__ */ V("div", {
136
137
  className: "font-medium text-text-primary",
137
138
  children: e.searchedParties.join(", ") || "Conflict search"
138
139
  }), e.findings ? /* @__PURE__ */ V("div", {
139
140
  className: "text-xs text-text-tertiary",
140
141
  children: e.findings
141
142
  }) : null] }),
142
- /* @__PURE__ */ V(g, {
143
+ /* @__PURE__ */ V(_, {
143
144
  className: "whitespace-nowrap",
144
- children: b(e.createdAt)
145
+ children: x(e.createdAt)
145
146
  }),
146
- /* @__PURE__ */ V(g, {
147
+ /* @__PURE__ */ V(_, {
147
148
  className: "text-right",
148
- children: /* @__PURE__ */ V(m, { status: e.status })
149
+ children: /* @__PURE__ */ V(h, { status: e.status })
149
150
  })
150
151
  ]
151
152
  }, e.id)) })] })
@@ -158,111 +159,111 @@ var W = (e) => e.split(/\s+/).filter(Boolean).slice(0, 2).map((e) => e[0]?.toUpp
158
159
  hasScopes: G.length > 0,
159
160
  actors: X
160
161
  }),
161
- /* @__PURE__ */ V(p, {
162
+ /* @__PURE__ */ V(m, {
162
163
  title: "Engagements",
163
- description: `${o.engagements.length} scoped for this client`,
164
+ description: `${s.engagements.length} scoped for this client`,
164
165
  action: /* @__PURE__ */ H(U, {
165
166
  size: "sm",
166
167
  variant: "outline",
167
168
  onClick: () => a("engagements"),
168
169
  children: [/* @__PURE__ */ V(F, { className: "mr-1.5 h-4 w-4" }), "Manage"]
169
170
  }),
170
- children: o.engagements.length === 0 ? /* @__PURE__ */ V(s, {
171
+ children: s.engagements.length === 0 ? /* @__PURE__ */ V(c, {
171
172
  title: "No engagements",
172
173
  description: "Scope the first engagement for this client.",
173
174
  icon: /* @__PURE__ */ V(O, { className: "h-7 w-7" })
174
- }) : /* @__PURE__ */ H(h, { children: [/* @__PURE__ */ V("thead", { children: /* @__PURE__ */ H("tr", { children: [
175
- /* @__PURE__ */ V(y, { children: "Engagement" }),
176
- /* @__PURE__ */ V(y, { children: "Partner" }),
177
- /* @__PURE__ */ V(y, { children: "Fee" }),
178
- /* @__PURE__ */ V(y, { children: "Started" }),
179
- /* @__PURE__ */ V(y, {
175
+ }) : /* @__PURE__ */ H(g, { children: [/* @__PURE__ */ V("thead", { children: /* @__PURE__ */ H("tr", { children: [
176
+ /* @__PURE__ */ V(b, { children: "Engagement" }),
177
+ /* @__PURE__ */ V(b, { children: "Partner" }),
178
+ /* @__PURE__ */ V(b, { children: "Fee" }),
179
+ /* @__PURE__ */ V(b, { children: "Started" }),
180
+ /* @__PURE__ */ V(b, {
180
181
  className: "text-right",
181
182
  children: "Status"
182
183
  })
183
- ] }) }), /* @__PURE__ */ V("tbody", { children: o.engagements.map((e) => /* @__PURE__ */ H("tr", {
184
+ ] }) }), /* @__PURE__ */ V("tbody", { children: s.engagements.map((e) => /* @__PURE__ */ H("tr", {
184
185
  className: "group cursor-pointer transition-colors hover:bg-bg-sunken/60",
185
186
  onClick: () => a(`engagements/${e.id}`),
186
187
  children: [
187
- /* @__PURE__ */ V(g, { children: /* @__PURE__ */ H("div", {
188
+ /* @__PURE__ */ V(_, { children: /* @__PURE__ */ H("div", {
188
189
  className: "flex items-center gap-1 font-medium text-text-primary transition-colors group-hover:text-accent",
189
190
  children: [/* @__PURE__ */ V("span", { children: e.name }), /* @__PURE__ */ V(k, { className: "h-3.5 w-3.5 shrink-0 text-text-tertiary opacity-0 transition-opacity group-hover:opacity-100" })]
190
191
  }) }),
191
- /* @__PURE__ */ V(g, {
192
+ /* @__PURE__ */ V(_, {
192
193
  className: "whitespace-nowrap",
193
194
  children: e.responsiblePartnerId ? X.nameOf(e.responsiblePartnerId) : /* @__PURE__ */ V("span", {
194
195
  className: "text-text-tertiary",
195
196
  children: "Unassigned"
196
197
  })
197
198
  }),
198
- /* @__PURE__ */ H(g, {
199
+ /* @__PURE__ */ H(_, {
199
200
  className: "whitespace-nowrap",
200
201
  children: [
201
- x(e.feeAmount, e.currency),
202
+ S(e.feeAmount, e.currency),
202
203
  " ",
203
204
  /* @__PURE__ */ H("span", {
204
205
  className: "text-xs text-text-tertiary",
205
- children: ["/ ", S(e.feeArrangement)]
206
+ children: ["/ ", o(e.feeArrangement)]
206
207
  })
207
208
  ]
208
209
  }),
209
- /* @__PURE__ */ V(g, {
210
+ /* @__PURE__ */ V(_, {
210
211
  className: "whitespace-nowrap",
211
- children: b(e.startDate)
212
+ children: x(e.startDate)
212
213
  }),
213
- /* @__PURE__ */ V(g, {
214
+ /* @__PURE__ */ V(_, {
214
215
  className: "text-right",
215
- children: /* @__PURE__ */ V(m, { status: e.status })
216
+ children: /* @__PURE__ */ V(h, { status: e.status })
216
217
  })
217
218
  ]
218
219
  }, e.id)) })] })
219
220
  }),
220
- /* @__PURE__ */ V(p, {
221
+ /* @__PURE__ */ V(m, {
221
222
  title: "Matters",
222
- description: `${o.matters.length} tracked for this client`,
223
+ description: `${s.matters.length} tracked for this client`,
223
224
  action: /* @__PURE__ */ H(U, {
224
225
  size: "sm",
225
226
  variant: "outline",
226
227
  onClick: () => a("matters"),
227
228
  children: [/* @__PURE__ */ V(F, { className: "mr-1.5 h-4 w-4" }), "Manage"]
228
229
  }),
229
- children: o.matters.length === 0 ? /* @__PURE__ */ V(s, {
230
+ children: s.matters.length === 0 ? /* @__PURE__ */ V(c, {
230
231
  title: "No matters",
231
232
  description: "Open a matter to track regulated work for this client.",
232
233
  icon: /* @__PURE__ */ V(A, { className: "h-7 w-7" })
233
- }) : /* @__PURE__ */ H(h, { children: [/* @__PURE__ */ V("thead", { children: /* @__PURE__ */ H("tr", { children: [
234
- /* @__PURE__ */ V(y, { children: "Reference" }),
235
- /* @__PURE__ */ V(y, { children: "Title" }),
236
- /* @__PURE__ */ V(y, { children: "Lead" }),
237
- /* @__PURE__ */ V(y, { children: "Due" }),
238
- /* @__PURE__ */ V(y, { children: "Priority" }),
239
- /* @__PURE__ */ V(y, {
234
+ }) : /* @__PURE__ */ H(g, { children: [/* @__PURE__ */ V("thead", { children: /* @__PURE__ */ H("tr", { children: [
235
+ /* @__PURE__ */ V(b, { children: "Reference" }),
236
+ /* @__PURE__ */ V(b, { children: "Title" }),
237
+ /* @__PURE__ */ V(b, { children: "Lead" }),
238
+ /* @__PURE__ */ V(b, { children: "Due" }),
239
+ /* @__PURE__ */ V(b, { children: "Priority" }),
240
+ /* @__PURE__ */ V(b, {
240
241
  className: "text-right",
241
242
  children: "Status"
242
243
  })
243
- ] }) }), /* @__PURE__ */ V("tbody", { children: o.matters.map((e) => /* @__PURE__ */ H("tr", {
244
+ ] }) }), /* @__PURE__ */ V("tbody", { children: s.matters.map((e) => /* @__PURE__ */ H("tr", {
244
245
  className: "transition-colors hover:bg-bg-sunken/60",
245
246
  children: [
246
- /* @__PURE__ */ V(g, {
247
+ /* @__PURE__ */ V(_, {
247
248
  className: "whitespace-nowrap font-medium text-text-primary",
248
249
  children: e.reference || "—"
249
250
  }),
250
- /* @__PURE__ */ V(g, { children: e.title }),
251
- /* @__PURE__ */ V(g, {
251
+ /* @__PURE__ */ V(_, { children: e.title }),
252
+ /* @__PURE__ */ V(_, {
252
253
  className: "whitespace-nowrap",
253
254
  children: e.leadPractitionerId ? X.nameOf(e.leadPractitionerId) : /* @__PURE__ */ V("span", {
254
255
  className: "text-text-tertiary",
255
256
  children: "Unassigned"
256
257
  })
257
258
  }),
258
- /* @__PURE__ */ V(g, {
259
+ /* @__PURE__ */ V(_, {
259
260
  className: "whitespace-nowrap",
260
- children: b(e.dueDate)
261
+ children: x(e.dueDate)
261
262
  }),
262
- /* @__PURE__ */ V(g, { children: /* @__PURE__ */ V(m, { status: e.priority }) }),
263
- /* @__PURE__ */ V(g, {
263
+ /* @__PURE__ */ V(_, { children: /* @__PURE__ */ V(h, { status: e.priority }) }),
264
+ /* @__PURE__ */ V(_, {
264
265
  className: "text-right",
265
- children: /* @__PURE__ */ V(m, { status: e.status })
266
+ children: /* @__PURE__ */ V(h, { status: e.status })
266
267
  })
267
268
  ]
268
269
  }, e.id)) })] })
@@ -274,7 +275,7 @@ var W = (e) => e.split(/\s+/).filter(Boolean).slice(0, 2).map((e) => e[0]?.toUpp
274
275
  R && /* @__PURE__ */ V(Y, {
275
276
  clientId: r,
276
277
  onClose: () => B(!1),
277
- onDone: () => void v.refetch()
278
+ onDone: () => void y.refetch()
278
279
  })
279
280
  ]
280
281
  });
@@ -287,7 +288,7 @@ function K(e) {
287
288
  roles: [],
288
289
  isLead: !1,
289
290
  scopes: []
290
- }, r = S(n.role);
291
+ }, r = o(n.role);
291
292
  e.roles.includes(r) || e.roles.push(r), e.isLead = e.isLead || n.isLead, e.scopes.some((e) => e.id === n.scopeId) || e.scopes.push({
292
293
  id: n.scopeId,
293
294
  name: n.scopeName,
@@ -298,10 +299,10 @@ function K(e) {
298
299
  }
299
300
  var q = ({ rows: e, loading: t, error: n, hasScopes: r, actors: i }) => {
300
301
  let a = K(e);
301
- return /* @__PURE__ */ V(p, {
302
+ return /* @__PURE__ */ V(m, {
302
303
  title: "Client team",
303
304
  description: "Everyone staffed on this client's engagements and matters.",
304
- children: t ? /* @__PURE__ */ V(u, { label: "Loading…" }) : n ? /* @__PURE__ */ V(c, { error: n }) : r ? a.length === 0 ? /* @__PURE__ */ V(s, {
305
+ children: t ? /* @__PURE__ */ V(d, { label: "Loading…" }) : n ? /* @__PURE__ */ V(l, { error: n }) : r ? a.length === 0 ? /* @__PURE__ */ V(c, {
305
306
  title: "No one assigned",
306
307
  description: "Staff practitioners from an engagement's Team section to see them here.",
307
308
  icon: /* @__PURE__ */ V(R, { className: "h-7 w-7" })
@@ -355,7 +356,7 @@ var q = ({ rows: e, loading: t, error: n, hasScopes: r, actors: i }) => {
355
356
  ]
356
357
  }, e.practitionerId);
357
358
  })
358
- }) : /* @__PURE__ */ V(s, {
359
+ }) : /* @__PURE__ */ V(c, {
359
360
  title: "Nothing to staff yet",
360
361
  description: "Scope an engagement or open a matter, then assign the team that will deliver it.",
361
362
  icon: /* @__PURE__ */ V(R, { className: "h-7 w-7" })
@@ -371,8 +372,8 @@ var q = ({ rows: e, loading: t, error: n, hasScopes: r, actors: i }) => {
371
372
  children: t
372
373
  })]
373
374
  }), Y = ({ clientId: e, onClose: t, onDone: n }) => {
374
- let i = r(), [a, s] = E(""), [c, u] = E(""), [f, p] = E(null);
375
- return /* @__PURE__ */ V(d, {
375
+ let i = r(), [a, o] = E(""), [c, l] = E(""), [d, p] = E(null);
376
+ return /* @__PURE__ */ V(f, {
376
377
  open: !0,
377
378
  onClose: t,
378
379
  title: "Run conflict check",
@@ -403,25 +404,25 @@ var q = ({ rows: e, loading: t, error: n, hasScopes: r, actors: i }) => {
403
404
  children: /* @__PURE__ */ H("div", {
404
405
  className: "space-y-3",
405
406
  children: [
406
- f && /* @__PURE__ */ V(o, {
407
+ d && /* @__PURE__ */ V(s, {
407
408
  tone: "danger",
408
- children: f
409
+ children: d
409
410
  }),
410
- /* @__PURE__ */ V(l, {
411
+ /* @__PURE__ */ V(u, {
411
412
  label: "Parties searched",
412
413
  required: !0,
413
414
  hint: "Comma-separated — counterparties, related entities, principals.",
414
- children: /* @__PURE__ */ V(v, {
415
+ children: /* @__PURE__ */ V(y, {
415
416
  value: a,
416
- onChange: (e) => s(e.target.value),
417
+ onChange: (e) => o(e.target.value),
417
418
  placeholder: "Beta Corp, John Roe, Gamma LLP"
418
419
  })
419
420
  }),
420
- /* @__PURE__ */ V(l, {
421
+ /* @__PURE__ */ V(u, {
421
422
  label: "Findings",
422
- children: /* @__PURE__ */ V(_, {
423
+ children: /* @__PURE__ */ V(v, {
423
424
  value: c,
424
- onChange: (e) => u(e.target.value),
425
+ onChange: (e) => l(e.target.value),
425
426
  placeholder: "Notes on any potential conflicts…"
426
427
  })
427
428
  })