@burdenoff/microfe-proserviceworld 2026.1026.1 → 2026.1028.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.
@@ -1,169 +1,241 @@
1
- import { useDashboardStats as e } from "../hooks/useProServiceWorldApi.js";
2
- import { cn as t } from "../utils/cn.js";
3
- import { ErrorState as n, LoadingBlock as r, MetricCard as i, PageHeader as a, SectionCard as o, StatusPill as s, fmtHours as c, fmtMoney as l } from "../components/ui.js";
4
- import { BarChart3 as u, Clock as d, TrendingUp as f, Wallet as p } from "lucide-react";
5
- import { jsx as m, jsxs as h } from "react/jsx-runtime";
1
+ import { useDashboardStats as e, useWriteOffs as t } from "../hooks/useProServiceWorldApi.js";
2
+ import { cn as n } from "../utils/cn.js";
3
+ import { Empty as r, ErrorState as i, LoadingBlock as a, MetricCard as o, PageHeader as s, SectionCard as c, StatusPill as l, TableWrap as u, Td as d, Th as f, fmtDate as p, fmtHours as m, fmtMoney as h } from "../components/ui.js";
4
+ import { AlertTriangle as g, BarChart3 as _, Clock as v, Scale as y, TrendingUp as b, Wallet as x } from "lucide-react";
5
+ import { jsx as S, jsxs as C } from "react/jsx-runtime";
6
6
  //#region src/proserviceworld/pages/AnalyticsPage.tsx
7
- var g = () => {
8
- let { data: t, isLoading: s, error: g, refetch: v } = e();
9
- if (s) return /* @__PURE__ */ m(r, { label: "Crunching numbers…" });
10
- if (g) return /* @__PURE__ */ m(n, {
11
- error: g,
12
- onRetry: () => void v()
7
+ var w = () => {
8
+ let { data: n, isLoading: l, error: w, refetch: D } = e(), j = t({ pagination: { limit: 10 } });
9
+ if (l) return /* @__PURE__ */ S(a, { label: "Crunching numbers…" });
10
+ if (w) return /* @__PURE__ */ S(i, {
11
+ error: w,
12
+ onRetry: () => void D()
13
13
  });
14
- if (!t) return null;
15
- let S = t.unbilledHours + t.billedHours, C = S > 0 ? Math.round(t.billedHours / S * 100) : 0, w = t.paidInvoiceTotal + t.outstandingInvoiceTotal, T = w > 0 ? Math.round(t.paidInvoiceTotal / w * 100) : 0;
16
- return /* @__PURE__ */ h("div", {
14
+ if (!n) return null;
15
+ let M = n.unbilledHours + n.billedHours, N = M > 0 ? Math.round(n.billedHours / M * 100) : 0, P = n.paidInvoiceTotal + n.outstandingInvoiceTotal, F = P > 0 ? Math.round(n.paidInvoiceTotal / P * 100) : 0;
16
+ return /* @__PURE__ */ C("div", {
17
17
  className: "space-y-6",
18
18
  children: [
19
- /* @__PURE__ */ m(a, {
19
+ /* @__PURE__ */ S(s, {
20
20
  title: "Analytics",
21
21
  subtitle: "Real, live metrics across the practice — no estimates, straight from your records.",
22
- icon: /* @__PURE__ */ m(u, { className: "h-6 w-6" })
22
+ icon: /* @__PURE__ */ S(_, { className: "h-6 w-6" })
23
23
  }),
24
- /* @__PURE__ */ h("div", {
24
+ /* @__PURE__ */ C("div", {
25
25
  className: "grid grid-cols-2 gap-3 lg:grid-cols-4",
26
26
  children: [
27
- /* @__PURE__ */ m(i, {
27
+ /* @__PURE__ */ S(o, {
28
28
  label: "Collected revenue",
29
- value: l(t.paidInvoiceTotal),
30
- hint: `${l(w)} invoiced`,
31
- icon: /* @__PURE__ */ m(p, { className: "h-4 w-4" }),
29
+ value: h(n.paidInvoiceTotal),
30
+ hint: `${h(P)} invoiced`,
31
+ icon: /* @__PURE__ */ S(x, { className: "h-4 w-4" }),
32
32
  tone: "success"
33
33
  }),
34
- /* @__PURE__ */ m(i, {
34
+ /* @__PURE__ */ S(o, {
35
35
  label: "Collection rate",
36
- value: `${T}%`,
37
- hint: `${l(t.outstandingInvoiceTotal)} outstanding`,
38
- icon: /* @__PURE__ */ m(f, { className: "h-4 w-4" }),
36
+ value: `${F}%`,
37
+ hint: `${h(n.outstandingInvoiceTotal)} outstanding`,
38
+ icon: /* @__PURE__ */ S(b, { className: "h-4 w-4" }),
39
39
  tone: "accent"
40
40
  }),
41
- /* @__PURE__ */ m(i, {
41
+ /* @__PURE__ */ S(o, {
42
42
  label: "Utilization",
43
- value: `${C}%`,
44
- hint: `${c(t.billedHours)} of ${c(S)}`,
45
- icon: /* @__PURE__ */ m(d, { className: "h-4 w-4" }),
43
+ value: `${N}%`,
44
+ hint: `${m(n.billedHours)} of ${m(M)}`,
45
+ icon: /* @__PURE__ */ S(v, { className: "h-4 w-4" }),
46
46
  tone: "info"
47
47
  }),
48
- /* @__PURE__ */ m(i, {
48
+ /* @__PURE__ */ S(o, {
49
49
  label: "Active engagements",
50
- value: t.activeEngagements,
51
- hint: `${t.openMatters} open matters`,
52
- icon: /* @__PURE__ */ m(u, { className: "h-4 w-4" }),
50
+ value: n.activeEngagements,
51
+ hint: `${n.openMatters} open matters`,
52
+ icon: /* @__PURE__ */ S(_, { className: "h-4 w-4" }),
53
53
  tone: "warning"
54
54
  })
55
55
  ]
56
56
  }),
57
- /* @__PURE__ */ h("div", {
57
+ /* @__PURE__ */ C("div", {
58
58
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
59
- children: [/* @__PURE__ */ m(o, {
59
+ children: [/* @__PURE__ */ S(c, {
60
60
  title: "Collection rate",
61
61
  description: "Paid vs. total invoiced.",
62
- children: /* @__PURE__ */ m(_, {
63
- pct: T,
62
+ children: /* @__PURE__ */ S(E, {
63
+ pct: F,
64
64
  tone: "accent",
65
- primary: l(t.paidInvoiceTotal),
65
+ primary: h(n.paidInvoiceTotal),
66
66
  primaryLabel: "Collected",
67
- secondary: l(t.outstandingInvoiceTotal),
67
+ secondary: h(n.outstandingInvoiceTotal),
68
68
  secondaryLabel: "Outstanding"
69
69
  })
70
- }), /* @__PURE__ */ m(o, {
70
+ }), /* @__PURE__ */ S(c, {
71
71
  title: "Utilization",
72
72
  description: "Billed vs. total logged hours.",
73
- children: /* @__PURE__ */ m(_, {
74
- pct: C,
73
+ children: /* @__PURE__ */ S(E, {
74
+ pct: N,
75
75
  tone: "info",
76
- primary: c(t.billedHours),
76
+ primary: m(n.billedHours),
77
77
  primaryLabel: "Billed",
78
- secondary: c(t.unbilledHours),
78
+ secondary: m(n.unbilledHours),
79
79
  secondaryLabel: "Unbilled"
80
80
  })
81
81
  })]
82
82
  }),
83
- /* @__PURE__ */ m(o, {
83
+ /* @__PURE__ */ S(c, {
84
84
  title: "Revenue by month",
85
85
  description: "Invoiced totals across recent months.",
86
- children: /* @__PURE__ */ m(y, { data: t.revenueByMonth })
86
+ children: /* @__PURE__ */ S(O, { data: n.revenueByMonth })
87
87
  }),
88
- /* @__PURE__ */ h("div", {
88
+ /* @__PURE__ */ C("div", {
89
89
  className: "grid grid-cols-1 gap-4 lg:grid-cols-3",
90
90
  children: [
91
- /* @__PURE__ */ m(o, {
91
+ /* @__PURE__ */ S(c, {
92
92
  title: "Engagements",
93
93
  description: "By status",
94
- children: /* @__PURE__ */ m(b, { items: t.engagementsByStatus })
94
+ children: /* @__PURE__ */ S(k, { items: n.engagementsByStatus })
95
95
  }),
96
- /* @__PURE__ */ m(o, {
96
+ /* @__PURE__ */ S(c, {
97
97
  title: "Matters",
98
98
  description: "By status",
99
- children: /* @__PURE__ */ m(b, { items: t.mattersByStatus })
99
+ children: /* @__PURE__ */ S(k, { items: n.mattersByStatus })
100
100
  }),
101
- /* @__PURE__ */ m(o, {
101
+ /* @__PURE__ */ S(c, {
102
102
  title: "Invoices",
103
103
  description: "By status",
104
- children: /* @__PURE__ */ m(b, { items: t.invoicesByStatus })
104
+ children: /* @__PURE__ */ S(k, { items: n.invoicesByStatus })
105
105
  })
106
106
  ]
107
107
  }),
108
- /* @__PURE__ */ m(o, {
108
+ /* @__PURE__ */ S(c, {
109
109
  title: "Time & billing",
110
110
  description: "Billable capacity across the practice.",
111
- children: /* @__PURE__ */ h("div", {
111
+ children: /* @__PURE__ */ C("div", {
112
112
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
113
- children: [/* @__PURE__ */ m(x, {
113
+ children: [/* @__PURE__ */ S(A, {
114
114
  label: "Billed hours",
115
- value: t.billedHours,
116
- total: S,
115
+ value: n.billedHours,
116
+ total: M,
117
117
  tone: "accent"
118
- }), /* @__PURE__ */ m(x, {
118
+ }), /* @__PURE__ */ S(A, {
119
119
  label: "Unbilled hours",
120
- value: t.unbilledHours,
121
- total: S,
120
+ value: n.unbilledHours,
121
+ total: M,
122
122
  tone: "warning"
123
123
  })]
124
124
  })
125
+ }),
126
+ /* @__PURE__ */ C("div", {
127
+ className: "grid grid-cols-2 gap-3 lg:grid-cols-4",
128
+ children: [/* @__PURE__ */ S(o, {
129
+ label: "Realization",
130
+ value: `${Math.round(n.realizationPct)}%`,
131
+ hint: `${h(n.billedValue)} of ${h(n.workedValue)} worked`,
132
+ icon: /* @__PURE__ */ S(y, { className: "h-4 w-4" }),
133
+ tone: "accent"
134
+ }), /* @__PURE__ */ S(o, {
135
+ label: "Write-offs",
136
+ value: h(n.writeOffTotal),
137
+ hint: "Total written off",
138
+ icon: /* @__PURE__ */ S(g, { className: "h-4 w-4" }),
139
+ tone: "warning"
140
+ })]
141
+ }),
142
+ /* @__PURE__ */ S(c, {
143
+ title: "Aged receivables",
144
+ description: "Outstanding invoice value by age — the margin-leak early warning.",
145
+ children: /* @__PURE__ */ C("div", {
146
+ className: "grid grid-cols-2 gap-3 lg:grid-cols-4",
147
+ children: [
148
+ /* @__PURE__ */ S(T, {
149
+ label: "Current",
150
+ bucket: n.agedReceivables.current,
151
+ tone: "success"
152
+ }),
153
+ /* @__PURE__ */ S(T, {
154
+ label: "30 days",
155
+ bucket: n.agedReceivables.thirty,
156
+ tone: "info"
157
+ }),
158
+ /* @__PURE__ */ S(T, {
159
+ label: "60 days",
160
+ bucket: n.agedReceivables.sixty,
161
+ tone: "warning"
162
+ }),
163
+ /* @__PURE__ */ S(T, {
164
+ label: "90+ days",
165
+ bucket: n.agedReceivables.ninetyPlus,
166
+ tone: "danger"
167
+ })
168
+ ]
169
+ })
170
+ }),
171
+ /* @__PURE__ */ S(c, {
172
+ title: "Write-offs",
173
+ description: "Append-only audit of written-off amounts.",
174
+ children: j.isLoading ? /* @__PURE__ */ S(a, { label: "Loading write-offs…" }) : j.data?.length ? /* @__PURE__ */ C(u, { children: [/* @__PURE__ */ S("thead", { children: /* @__PURE__ */ C("tr", { children: [
175
+ /* @__PURE__ */ S(f, { children: "Date" }),
176
+ /* @__PURE__ */ S(f, { children: "Reason" }),
177
+ /* @__PURE__ */ S(f, {
178
+ className: "text-right",
179
+ children: "Amount"
180
+ })
181
+ ] }) }), /* @__PURE__ */ S("tbody", { children: j.data.map((e) => /* @__PURE__ */ C("tr", { children: [
182
+ /* @__PURE__ */ S(d, { children: p(e.createdAt) }),
183
+ /* @__PURE__ */ S(d, { children: e.reason }),
184
+ /* @__PURE__ */ S(d, {
185
+ className: "text-right",
186
+ children: h(e.amount)
187
+ })
188
+ ] }, e.id)) })] }) : /* @__PURE__ */ S(r, {
189
+ title: "No write-offs",
190
+ description: "Nothing has been written off yet."
191
+ })
125
192
  })
126
193
  ]
127
194
  });
128
- }, _ = ({ pct: e, tone: t, primary: n, primaryLabel: r, secondary: i, secondaryLabel: a }) => /* @__PURE__ */ h("div", {
195
+ }, T = ({ label: e, bucket: t, tone: n }) => /* @__PURE__ */ S(o, {
196
+ label: e,
197
+ value: h(t.amount),
198
+ hint: `${t.count} invoice${t.count === 1 ? "" : "s"}`,
199
+ tone: n
200
+ }), E = ({ pct: e, tone: t, primary: n, primaryLabel: r, secondary: i, secondaryLabel: a }) => /* @__PURE__ */ C("div", {
129
201
  className: "flex items-center gap-5",
130
- children: [/* @__PURE__ */ m(v, {
202
+ children: [/* @__PURE__ */ S(D, {
131
203
  pct: e,
132
204
  tone: t
133
- }), /* @__PURE__ */ h("div", {
205
+ }), /* @__PURE__ */ C("div", {
134
206
  className: "flex flex-1 flex-col gap-2.5",
135
207
  children: [
136
- /* @__PURE__ */ h("div", {
208
+ /* @__PURE__ */ C("div", {
137
209
  className: "flex items-baseline justify-between gap-3",
138
- children: [/* @__PURE__ */ m("span", {
210
+ children: [/* @__PURE__ */ S("span", {
139
211
  className: "text-xs font-medium uppercase tracking-wider text-text-tertiary",
140
212
  children: r
141
- }), /* @__PURE__ */ m("span", {
213
+ }), /* @__PURE__ */ S("span", {
142
214
  className: "text-sm font-semibold tabular-nums text-text-primary",
143
215
  children: n
144
216
  })]
145
217
  }),
146
- /* @__PURE__ */ m("div", { className: "h-px bg-border-seam" }),
147
- /* @__PURE__ */ h("div", {
218
+ /* @__PURE__ */ S("div", { className: "h-px bg-border-seam" }),
219
+ /* @__PURE__ */ C("div", {
148
220
  className: "flex items-baseline justify-between gap-3",
149
- children: [/* @__PURE__ */ m("span", {
221
+ children: [/* @__PURE__ */ S("span", {
150
222
  className: "text-xs font-medium uppercase tracking-wider text-text-tertiary",
151
223
  children: a
152
- }), /* @__PURE__ */ m("span", {
224
+ }), /* @__PURE__ */ S("span", {
153
225
  className: "text-sm font-semibold tabular-nums text-text-secondary",
154
226
  children: i
155
227
  })]
156
228
  })
157
229
  ]
158
230
  })]
159
- }), v = ({ pct: e, tone: n }) => {
231
+ }), D = ({ pct: e, tone: t }) => {
160
232
  let r = Math.max(0, Math.min(100, e)), i = 2 * Math.PI * 34, a = r / 100 * i;
161
- return /* @__PURE__ */ h("div", {
162
- className: t("relative h-24 w-24 shrink-0", n === "accent" ? "text-accent" : "text-text-link"),
163
- children: [/* @__PURE__ */ h("svg", {
233
+ return /* @__PURE__ */ C("div", {
234
+ className: n("relative h-24 w-24 shrink-0", t === "accent" ? "text-accent" : "text-text-link"),
235
+ children: [/* @__PURE__ */ C("svg", {
164
236
  viewBox: "0 0 80 80",
165
237
  className: "h-full w-full -rotate-90",
166
- children: [/* @__PURE__ */ m("circle", {
238
+ children: [/* @__PURE__ */ S("circle", {
167
239
  cx: "40",
168
240
  cy: "40",
169
241
  r: 34,
@@ -171,7 +243,7 @@ var g = () => {
171
243
  strokeWidth: "8",
172
244
  className: "text-bg-sunken",
173
245
  stroke: "currentColor"
174
- }), /* @__PURE__ */ m("circle", {
246
+ }), /* @__PURE__ */ S("circle", {
175
247
  cx: "40",
176
248
  cy: "40",
177
249
  r: 34,
@@ -182,74 +254,74 @@ var g = () => {
182
254
  strokeDasharray: `${a} ${i}`,
183
255
  className: "transition-[stroke-dasharray] duration-700"
184
256
  })]
185
- }), /* @__PURE__ */ m("div", {
257
+ }), /* @__PURE__ */ S("div", {
186
258
  className: "absolute inset-0 flex items-center justify-center",
187
- children: /* @__PURE__ */ h("span", {
259
+ children: /* @__PURE__ */ C("span", {
188
260
  className: "text-lg font-bold tabular-nums text-text-primary",
189
261
  children: [r, "%"]
190
262
  })
191
263
  })]
192
264
  });
193
- }, y = ({ data: e }) => {
194
- if (!e.length) return /* @__PURE__ */ m("p", {
265
+ }, O = ({ data: e }) => {
266
+ if (!e.length) return /* @__PURE__ */ S("p", {
195
267
  className: "text-sm text-text-tertiary",
196
268
  children: "No invoiced revenue yet."
197
269
  });
198
270
  let t = Math.max(...e.map((e) => e.amount), 1);
199
- return /* @__PURE__ */ m("div", {
271
+ return /* @__PURE__ */ S("div", {
200
272
  className: "overflow-x-auto",
201
- children: /* @__PURE__ */ h("div", {
273
+ children: /* @__PURE__ */ C("div", {
202
274
  className: "min-w-[520px]",
203
- children: [/* @__PURE__ */ m("div", {
275
+ children: [/* @__PURE__ */ S("div", {
204
276
  className: "flex h-60 items-end gap-2 border-b border-border-seam",
205
- children: e.map((e) => /* @__PURE__ */ h("div", {
277
+ children: e.map((e) => /* @__PURE__ */ C("div", {
206
278
  className: "group flex h-full flex-1 flex-col items-center justify-end gap-1.5",
207
- children: [/* @__PURE__ */ m("span", {
279
+ children: [/* @__PURE__ */ S("span", {
208
280
  className: "whitespace-nowrap text-[10px] font-semibold tabular-nums text-text-secondary",
209
- children: l(e.amount)
210
- }), /* @__PURE__ */ m("div", {
281
+ children: h(e.amount)
282
+ }), /* @__PURE__ */ S("div", {
211
283
  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",
212
284
  style: { height: `${Math.max(e.amount / t * 90, 2)}%` },
213
- title: l(e.amount)
285
+ title: h(e.amount)
214
286
  })]
215
287
  }, e.month))
216
- }), /* @__PURE__ */ m("div", {
288
+ }), /* @__PURE__ */ S("div", {
217
289
  className: "mt-2 flex gap-2",
218
- children: e.map((e) => /* @__PURE__ */ m("span", {
290
+ children: e.map((e) => /* @__PURE__ */ S("span", {
219
291
  className: "flex-1 text-center text-xs text-text-tertiary",
220
- children: T(e.month)
292
+ children: P(e.month)
221
293
  }, e.month))
222
294
  })]
223
295
  })
224
296
  });
225
- }, b = ({ items: e }) => {
226
- if (!e.length) return /* @__PURE__ */ m("p", {
297
+ }, k = ({ items: e }) => {
298
+ if (!e.length) return /* @__PURE__ */ S("p", {
227
299
  className: "text-sm text-text-tertiary",
228
300
  children: "No records yet."
229
301
  });
230
- let n = Math.max(...e.map((e) => e.count), 1), r = e.reduce((e, t) => e + t.count, 0) || 1;
231
- return /* @__PURE__ */ m("ul", {
302
+ let t = Math.max(...e.map((e) => e.count), 1), r = e.reduce((e, t) => e + t.count, 0) || 1;
303
+ return /* @__PURE__ */ S("ul", {
232
304
  className: "space-y-3.5",
233
- children: e.map((e) => /* @__PURE__ */ h("li", {
305
+ children: e.map((e) => /* @__PURE__ */ C("li", {
234
306
  className: "grid grid-cols-[7.5rem_minmax(0,1fr)_auto] items-center gap-3",
235
307
  children: [
236
- /* @__PURE__ */ m(s, {
308
+ /* @__PURE__ */ S(l, {
237
309
  status: e.status,
238
310
  className: "justify-self-start"
239
311
  }),
240
- /* @__PURE__ */ m("div", {
312
+ /* @__PURE__ */ S("div", {
241
313
  className: "h-2.5 overflow-hidden rounded-full bg-bg-sunken",
242
- children: /* @__PURE__ */ m("div", {
243
- className: t("h-full rounded-full bg-gradient-to-r transition-all duration-500", w(e.status)),
244
- style: { width: `${Math.max(e.count / n * 100, 4)}%` }
314
+ children: /* @__PURE__ */ S("div", {
315
+ className: n("h-full rounded-full bg-gradient-to-r transition-all duration-500", N(e.status)),
316
+ style: { width: `${Math.max(e.count / t * 100, 4)}%` }
245
317
  })
246
318
  }),
247
- /* @__PURE__ */ h("span", {
319
+ /* @__PURE__ */ C("span", {
248
320
  className: "flex items-baseline justify-end gap-1.5 tabular-nums",
249
- children: [/* @__PURE__ */ m("span", {
321
+ children: [/* @__PURE__ */ S("span", {
250
322
  className: "text-sm font-semibold text-text-primary",
251
323
  children: e.count
252
- }), /* @__PURE__ */ h("span", {
324
+ }), /* @__PURE__ */ C("span", {
253
325
  className: "text-xs text-text-tertiary",
254
326
  children: [Math.round(e.count / r * 100), "%"]
255
327
  })]
@@ -257,40 +329,40 @@ var g = () => {
257
329
  ]
258
330
  }, e.status))
259
331
  });
260
- }, x = ({ label: e, value: n, total: r, tone: i }) => {
261
- let a = r > 0 ? Math.round(n / r * 100) : 0, o = i === "accent" ? "from-accent/60 to-accent" : "from-status-warning-text/60 to-status-warning-text";
262
- return /* @__PURE__ */ h("div", {
332
+ }, A = ({ label: e, value: t, total: r, tone: i }) => {
333
+ let a = r > 0 ? Math.round(t / r * 100) : 0, o = i === "accent" ? "from-accent/60 to-accent" : "from-status-warning-text/60 to-status-warning-text";
334
+ return /* @__PURE__ */ C("div", {
263
335
  className: "rounded-xl border border-border-seam bg-bg-canvas p-4",
264
- children: [/* @__PURE__ */ h("div", {
336
+ children: [/* @__PURE__ */ C("div", {
265
337
  className: "mb-2.5 flex items-center justify-between gap-3",
266
- children: [/* @__PURE__ */ m("span", {
338
+ children: [/* @__PURE__ */ S("span", {
267
339
  className: "text-sm font-medium text-text-secondary",
268
340
  children: e
269
- }), /* @__PURE__ */ h("span", {
341
+ }), /* @__PURE__ */ C("span", {
270
342
  className: "text-sm font-semibold tabular-nums text-text-primary",
271
343
  children: [
272
- c(n),
344
+ m(t),
273
345
  " · ",
274
346
  a,
275
347
  "%"
276
348
  ]
277
349
  })]
278
- }), /* @__PURE__ */ m("div", {
350
+ }), /* @__PURE__ */ S("div", {
279
351
  className: "h-3 overflow-hidden rounded-full bg-bg-sunken",
280
- children: /* @__PURE__ */ m("div", {
281
- className: t("h-full rounded-full bg-gradient-to-r transition-all duration-700", o),
352
+ children: /* @__PURE__ */ S("div", {
353
+ className: n("h-full rounded-full bg-gradient-to-r transition-all duration-700", o),
282
354
  style: { width: `${a}%` }
283
355
  })
284
356
  })]
285
357
  });
286
- }, S = {
358
+ }, j = {
287
359
  success: "from-status-success-text/55 to-status-success-text",
288
360
  warning: "from-status-warning-text/55 to-status-warning-text",
289
361
  danger: "from-status-error-text/55 to-status-error-text",
290
362
  info: "from-text-link/55 to-text-link",
291
363
  accent: "from-accent/55 to-accent",
292
364
  neutral: "from-text-tertiary/30 to-text-tertiary/70"
293
- }, C = {
365
+ }, M = {
294
366
  ACTIVE: "success",
295
367
  DELIVERED: "success",
296
368
  INVOICED: "success",
@@ -330,16 +402,16 @@ var g = () => {
330
402
  DRAFT: "neutral",
331
403
  WAIVED: "neutral"
332
404
  };
333
- function w(e) {
334
- return S[C[e] ?? "accent"];
405
+ function N(e) {
406
+ return j[M[e] ?? "accent"];
335
407
  }
336
- function T(e) {
408
+ function P(e) {
337
409
  let [t, n] = e.split("-").map(Number);
338
410
  if (!t || !n) return e;
339
411
  let r = new Date(t, n - 1, 1);
340
412
  return Number.isNaN(r.getTime()) ? e : r.toLocaleDateString(void 0, { month: "short" });
341
413
  }
342
414
  //#endregion
343
- export { g as AnalyticsPage };
415
+ export { w as AnalyticsPage };
344
416
 
345
417
  //# sourceMappingURL=AnalyticsPage.js.map
@@ -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":"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"}
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 {\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 { data, isLoading, error, refetch } = useDashboardStats();\n const writeOffs = useWriteOffs({ pagination: { limit: 10 } });\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\n <div className=\"grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <MetricCard\n label=\"Realization\"\n value={`${Math.round(data.realizationPct)}%`}\n hint={`${fmtMoney(data.billedValue)} of ${fmtMoney(data.workedValue)} worked`}\n icon={<Scale className=\"h-4 w-4\" />}\n tone=\"accent\"\n />\n <MetricCard\n label=\"Write-offs\"\n value={fmtMoney(data.writeOffTotal)}\n hint=\"Total written off\"\n icon={<AlertTriangle className=\"h-4 w-4\" />}\n tone=\"warning\"\n />\n </div>\n\n <SectionCard\n title=\"Aged receivables\"\n description=\"Outstanding invoice value by age — the margin-leak early warning.\"\n >\n <div className=\"grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <AgingBucket label=\"Current\" bucket={data.agedReceivables.current} tone=\"success\" />\n <AgingBucket label=\"30 days\" bucket={data.agedReceivables.thirty} tone=\"info\" />\n <AgingBucket label=\"60 days\" bucket={data.agedReceivables.sixty} tone=\"warning\" />\n <AgingBucket label=\"90+ days\" bucket={data.agedReceivables.ninetyPlus} tone=\"danger\" />\n </div>\n </SectionCard>\n\n <SectionCard title=\"Write-offs\" description=\"Append-only audit of written-off amounts.\">\n {writeOffs.isLoading ? (\n <LoadingBlock label=\"Loading write-offs…\" />\n ) : !writeOffs.data?.length ? (\n <Empty title=\"No write-offs\" description=\"Nothing has been written off yet.\" />\n ) : (\n <TableWrap>\n <thead>\n <tr>\n <Th>Date</Th>\n <Th>Reason</Th>\n <Th className=\"text-right\">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-right\">{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 <MetricCard\n label={label}\n value={fmtMoney(bucket.amount)}\n hint={`${bucket.count} invoice${bucket.count === 1 ? '' : 's'}`}\n tone={tone}\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":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"InvoicesPage.d.ts","sourceRoot":"","sources":["../../../src/proserviceworld/pages/InvoicesPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAsChC,eAAO,MAAM,YAAY,EAAE,EAuL1B,CAAC"}
1
+ {"version":3,"file":"InvoicesPage.d.ts","sourceRoot":"","sources":["../../../src/proserviceworld/pages/InvoicesPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAmDhC,eAAO,MAAM,YAAY,EAAE,EAsP1B,CAAC"}