@burdenoff/microfe-proserviceworld 2026.625.1 → 2026.626.2
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.
- package/dist/proserviceworld/pages/AdminReportsPage.js +102 -68
- package/dist/proserviceworld/pages/AdminReportsPage.js.map +1 -1
- package/dist/proserviceworld/pages/AdminUsersPage.js +37 -33
- package/dist/proserviceworld/pages/AdminUsersPage.js.map +1 -1
- package/dist/proserviceworld/pages/AnalyticsPage.js +66 -36
- package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
- package/dist/proserviceworld/pages/BookingsPage.js +52 -48
- package/dist/proserviceworld/pages/BookingsPage.js.map +1 -1
- package/dist/proserviceworld/pages/DashboardPage.js +275 -243
- package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
- package/dist/proserviceworld/pages/DocumentsPage.js +204 -200
- package/dist/proserviceworld/pages/DocumentsPage.js.map +1 -1
- package/dist/proserviceworld/pages/MessagesPage.js +200 -186
- package/dist/proserviceworld/pages/MessagesPage.js.map +1 -1
- package/dist/proserviceworld/pages/PaymentsPage.js +70 -39
- package/dist/proserviceworld/pages/PaymentsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ProjectsPage.js +510 -477
- package/dist/proserviceworld/pages/ProjectsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ProviderDetailPage.js +71 -41
- package/dist/proserviceworld/pages/ProviderDetailPage.js.map +1 -1
- package/dist/proserviceworld/pages/ProvidersPage.js +62 -58
- package/dist/proserviceworld/pages/ProvidersPage.js.map +1 -1
- package/dist/proserviceworld/pages/ReviewsPage.js +80 -63
- package/dist/proserviceworld/pages/ReviewsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ServiceDetailsPage.js +118 -83
- package/dist/proserviceworld/pages/ServiceDetailsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ServicesPage.js +134 -130
- package/dist/proserviceworld/pages/ServicesPage.js.map +1 -1
- package/dist/proserviceworld/pages/VerificationPage.js +23 -19
- package/dist/proserviceworld/pages/VerificationPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -2,10 +2,10 @@ import { useProServiceWorldStore as e } from "../store/proserviceworldStore.js";
|
|
|
2
2
|
import { useState as t } from "react";
|
|
3
3
|
import { AlertTriangle as n, ArrowUpRight as r, BarChart3 as i, Calendar as a, DollarSign as o, Download as s, FileText as c, Minus as l, Shield as u, ShoppingCart as d, Star as f, TrendingDown as p, TrendingUp as m, Users as h } from "lucide-react";
|
|
4
4
|
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
5
|
-
import { AuroraBackground as v, Badge as y, CTAOverflowMenu as b, Card as x, CardContent as S, CardHeader as C, CardTitle as w, GlassCard as T, GradientText as E } from "@burdenoff/fe-libs/ui";
|
|
5
|
+
import { AuroraBackground as v, Badge as y, CTAOverflowMenu as b, Card as x, CardContent as S, CardHeader as C, CardTitle as w, GlassCard as T, GradientText as E, NextSteps as D, PagePurpose as O } from "@burdenoff/fe-libs/ui";
|
|
6
6
|
//#region src/proserviceworld/pages/AdminReportsPage.tsx
|
|
7
|
-
function
|
|
8
|
-
let { adminReportMetrics:
|
|
7
|
+
function k() {
|
|
8
|
+
let { adminReportMetrics: k, revenueData: A, servicePerformance: j, geographicData: M, adminUsers: N } = e(), [P, F] = t("overview"), [I, L] = t("30d"), R = [
|
|
9
9
|
{
|
|
10
10
|
key: "overview",
|
|
11
11
|
label: "Overview",
|
|
@@ -26,19 +26,19 @@ function D() {
|
|
|
26
26
|
label: "Bookings",
|
|
27
27
|
icon: d
|
|
28
28
|
}
|
|
29
|
-
],
|
|
29
|
+
], z = (e) => e === "positive" ? /* @__PURE__ */ g(m, { className: "h-4 w-4 text-status-success-text" }) : e === "negative" ? /* @__PURE__ */ g(p, { className: "h-4 w-4 text-status-error-text" }) : /* @__PURE__ */ g(l, { className: "h-4 w-4 text-text-secondary" }), B = (e) => e === "positive" ? "text-status-success-text" : e === "negative" ? "text-status-error-text" : "text-text-secondary", V = [
|
|
30
30
|
h,
|
|
31
31
|
u,
|
|
32
32
|
d,
|
|
33
33
|
o,
|
|
34
34
|
f,
|
|
35
35
|
n
|
|
36
|
-
],
|
|
36
|
+
], H = Math.max(...A.map((e) => e.value)), U = Math.max(...j.map((e) => e.value)), W = N.reduce((e, t) => (e[t.role] = (e[t.role] ?? 0) + 1, e), {}), G = N.reduce((e, t) => (e[t.status] = (e[t.status] ?? 0) + 1, e), {}), K = {
|
|
37
37
|
client: "bg-chart-1",
|
|
38
38
|
provider: "bg-chart-2",
|
|
39
39
|
admin: "bg-chart-3",
|
|
40
40
|
team_member: "bg-chart-4"
|
|
41
|
-
},
|
|
41
|
+
}, q = {
|
|
42
42
|
active: "bg-status-success-text",
|
|
43
43
|
suspended: "bg-status-warning-text",
|
|
44
44
|
pending: "bg-chart-5",
|
|
@@ -71,14 +71,44 @@ function D() {
|
|
|
71
71
|
})]
|
|
72
72
|
})]
|
|
73
73
|
}),
|
|
74
|
+
/* @__PURE__ */ g(O, {
|
|
75
|
+
icon: /* @__PURE__ */ g(i, { className: "h-4 w-4" }),
|
|
76
|
+
children: "The operational pulse of the platform. Track headline KPIs, dig into revenue, user, and booking trends across time ranges, and export reports for stakeholders — use it to spot what is growing, what needs attention, and where to act next."
|
|
77
|
+
}),
|
|
78
|
+
/* @__PURE__ */ g(D, {
|
|
79
|
+
storageKey: "proserviceworld-admin-reports",
|
|
80
|
+
steps: [
|
|
81
|
+
{
|
|
82
|
+
id: "revenue",
|
|
83
|
+
label: "Review revenue trends",
|
|
84
|
+
description: "Open the Revenue tab to compare months and spot growth or dips.",
|
|
85
|
+
icon: /* @__PURE__ */ g(o, { className: "h-4 w-4" }),
|
|
86
|
+
onClick: () => F("revenue")
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: "users",
|
|
90
|
+
label: "Check user health",
|
|
91
|
+
description: "See role and status distribution plus your top spenders.",
|
|
92
|
+
icon: /* @__PURE__ */ g(h, { className: "h-4 w-4" }),
|
|
93
|
+
onClick: () => F("users")
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "export",
|
|
97
|
+
label: "Export a report",
|
|
98
|
+
description: "Generate a shareable report or CSV for stakeholders.",
|
|
99
|
+
icon: /* @__PURE__ */ g(s, { className: "h-4 w-4" }),
|
|
100
|
+
onClick: () => {}
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}),
|
|
74
104
|
/* @__PURE__ */ _("div", {
|
|
75
105
|
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
76
106
|
children: [/* @__PURE__ */ g("div", {
|
|
77
107
|
className: "flex gap-1 border-b border-border-seam",
|
|
78
|
-
children:
|
|
79
|
-
onClick: () =>
|
|
80
|
-
"aria-pressed":
|
|
81
|
-
className: `flex items-center gap-2 border-b-2 px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${
|
|
108
|
+
children: R.map(({ key: e, label: t, icon: n }) => /* @__PURE__ */ _("button", {
|
|
109
|
+
onClick: () => F(e),
|
|
110
|
+
"aria-pressed": P === e,
|
|
111
|
+
className: `flex items-center gap-2 border-b-2 px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${P === e ? "border-accent-blue text-accent-blue" : "border-transparent text-text-secondary hover:text-text-primary"}`,
|
|
82
112
|
children: [/* @__PURE__ */ g(n, { className: "h-4 w-4" }), t]
|
|
83
113
|
}, e))
|
|
84
114
|
}), /* @__PURE__ */ _("div", {
|
|
@@ -91,9 +121,9 @@ function D() {
|
|
|
91
121
|
"90d",
|
|
92
122
|
"1y"
|
|
93
123
|
].map((e) => /* @__PURE__ */ g("button", {
|
|
94
|
-
onClick: () =>
|
|
95
|
-
"aria-pressed":
|
|
96
|
-
className: `px-3 py-1 text-xs font-medium tabular-nums focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${
|
|
124
|
+
onClick: () => L(e),
|
|
125
|
+
"aria-pressed": I === e,
|
|
126
|
+
className: `px-3 py-1 text-xs font-medium tabular-nums focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${I === e ? "bg-accent-blue text-action-primary-text" : "text-text-secondary hover:bg-bg-sunken"}`,
|
|
97
127
|
children: e
|
|
98
128
|
}, e))
|
|
99
129
|
})]
|
|
@@ -101,39 +131,43 @@ function D() {
|
|
|
101
131
|
}),
|
|
102
132
|
/* @__PURE__ */ g("div", {
|
|
103
133
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
104
|
-
children:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
e.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
children: k.map((e, t) => {
|
|
135
|
+
let n = V[t % V.length] ?? i;
|
|
136
|
+
return /* @__PURE__ */ g(T, {
|
|
137
|
+
treatment: t === 0 ? "glass" : "metric",
|
|
138
|
+
glow: t === 0,
|
|
139
|
+
className: "border-border-seam",
|
|
140
|
+
children: /* @__PURE__ */ _(S, {
|
|
141
|
+
className: "pt-4",
|
|
142
|
+
children: [
|
|
143
|
+
/* @__PURE__ */ _("div", {
|
|
144
|
+
className: "flex items-center justify-between",
|
|
145
|
+
children: [/* @__PURE__ */ _("div", {
|
|
146
|
+
className: "flex items-center gap-2",
|
|
147
|
+
children: [/* @__PURE__ */ g(n, { className: "h-4 w-4 text-text-secondary" }), /* @__PURE__ */ g("p", {
|
|
148
|
+
className: "text-sm font-medium text-text-secondary",
|
|
149
|
+
children: e.label
|
|
150
|
+
})]
|
|
151
|
+
}), z(e.changeType)]
|
|
152
|
+
}),
|
|
153
|
+
/* @__PURE__ */ g("p", {
|
|
154
|
+
className: "mt-2 text-3xl font-bold tabular-nums",
|
|
155
|
+
children: e.value
|
|
156
|
+
}),
|
|
157
|
+
/* @__PURE__ */ _("p", {
|
|
158
|
+
className: `mt-1 flex items-center gap-1 text-xs ${B(e.changeType)}`,
|
|
159
|
+
children: [
|
|
160
|
+
/* @__PURE__ */ g(r, { className: "h-3 w-3" }),
|
|
161
|
+
e.change,
|
|
162
|
+
" vs last period"
|
|
163
|
+
]
|
|
164
|
+
})
|
|
165
|
+
]
|
|
166
|
+
})
|
|
167
|
+
}, t);
|
|
168
|
+
})
|
|
135
169
|
}),
|
|
136
|
-
|
|
170
|
+
P === "overview" && /* @__PURE__ */ _("div", {
|
|
137
171
|
className: "grid gap-6 lg:grid-cols-2",
|
|
138
172
|
children: [
|
|
139
173
|
/* @__PURE__ */ _(x, {
|
|
@@ -149,7 +183,7 @@ function D() {
|
|
|
149
183
|
})]
|
|
150
184
|
}) }), /* @__PURE__ */ g(S, {
|
|
151
185
|
className: "space-y-3",
|
|
152
|
-
children:
|
|
186
|
+
children: A.map((e) => /* @__PURE__ */ _("div", {
|
|
153
187
|
className: "flex items-center gap-3",
|
|
154
188
|
children: [
|
|
155
189
|
/* @__PURE__ */ g("span", {
|
|
@@ -160,7 +194,7 @@ function D() {
|
|
|
160
194
|
className: "flex-1 h-5 bg-bg-sunken rounded overflow-hidden",
|
|
161
195
|
children: /* @__PURE__ */ g("div", {
|
|
162
196
|
className: "h-5 bg-accent-blue rounded",
|
|
163
|
-
style: { width: `${e.value /
|
|
197
|
+
style: { width: `${e.value / H * 100}%` }
|
|
164
198
|
})
|
|
165
199
|
}),
|
|
166
200
|
/* @__PURE__ */ _("span", {
|
|
@@ -188,7 +222,7 @@ function D() {
|
|
|
188
222
|
})]
|
|
189
223
|
}) }), /* @__PURE__ */ g(S, {
|
|
190
224
|
className: "space-y-3",
|
|
191
|
-
children: [...
|
|
225
|
+
children: [...j].sort((e, t) => t.value - e.value).map((e) => /* @__PURE__ */ _("div", {
|
|
192
226
|
className: "flex items-center gap-3",
|
|
193
227
|
children: [
|
|
194
228
|
/* @__PURE__ */ g("span", {
|
|
@@ -199,7 +233,7 @@ function D() {
|
|
|
199
233
|
className: "flex-1 h-5 bg-bg-sunken rounded overflow-hidden",
|
|
200
234
|
children: /* @__PURE__ */ g("div", {
|
|
201
235
|
className: "h-5 bg-accent-purple rounded",
|
|
202
|
-
style: { width: `${e.value /
|
|
236
|
+
style: { width: `${e.value / U * 100}%` }
|
|
203
237
|
})
|
|
204
238
|
}),
|
|
205
239
|
/* @__PURE__ */ g("span", {
|
|
@@ -243,8 +277,8 @@ function D() {
|
|
|
243
277
|
children: "Share"
|
|
244
278
|
})
|
|
245
279
|
]
|
|
246
|
-
}) }), /* @__PURE__ */ g("tbody", { children:
|
|
247
|
-
let t =
|
|
280
|
+
}) }), /* @__PURE__ */ g("tbody", { children: M.map((e) => {
|
|
281
|
+
let t = M.reduce((e, t) => e + t.revenue, 0), n = Math.round(e.revenue / t * 100), r = Math.round(e.revenue / e.clients);
|
|
248
282
|
return /* @__PURE__ */ _("tr", {
|
|
249
283
|
className: "border-b border-border-seam hover:bg-bg-sunken",
|
|
250
284
|
children: [
|
|
@@ -292,7 +326,7 @@ function D() {
|
|
|
292
326
|
})
|
|
293
327
|
]
|
|
294
328
|
}),
|
|
295
|
-
|
|
329
|
+
P === "users" && /* @__PURE__ */ _("div", {
|
|
296
330
|
className: "grid gap-6 lg:grid-cols-2",
|
|
297
331
|
children: [
|
|
298
332
|
/* @__PURE__ */ _(x, {
|
|
@@ -302,8 +336,8 @@ function D() {
|
|
|
302
336
|
children: "User Role Distribution"
|
|
303
337
|
}) }), /* @__PURE__ */ g(S, {
|
|
304
338
|
className: "space-y-3",
|
|
305
|
-
children: Object.entries(
|
|
306
|
-
let n = Math.round(t /
|
|
339
|
+
children: Object.entries(W).map(([e, t]) => {
|
|
340
|
+
let n = Math.round(t / N.length * 100);
|
|
307
341
|
return /* @__PURE__ */ _("div", {
|
|
308
342
|
className: "flex items-center gap-3",
|
|
309
343
|
children: [
|
|
@@ -314,7 +348,7 @@ function D() {
|
|
|
314
348
|
/* @__PURE__ */ g("div", {
|
|
315
349
|
className: "flex-1 h-5 bg-bg-sunken rounded overflow-hidden",
|
|
316
350
|
children: /* @__PURE__ */ g("div", {
|
|
317
|
-
className: `h-5 rounded ${
|
|
351
|
+
className: `h-5 rounded ${K[e] ?? "bg-chart-6"}`,
|
|
318
352
|
style: { width: `${n}%` }
|
|
319
353
|
})
|
|
320
354
|
}),
|
|
@@ -339,8 +373,8 @@ function D() {
|
|
|
339
373
|
children: "User Status Distribution"
|
|
340
374
|
}) }), /* @__PURE__ */ g(S, {
|
|
341
375
|
className: "space-y-3",
|
|
342
|
-
children: Object.entries(
|
|
343
|
-
let n = Math.round(t /
|
|
376
|
+
children: Object.entries(G).map(([e, t]) => {
|
|
377
|
+
let n = Math.round(t / N.length * 100);
|
|
344
378
|
return /* @__PURE__ */ _("div", {
|
|
345
379
|
className: "flex items-center gap-3",
|
|
346
380
|
children: [
|
|
@@ -351,7 +385,7 @@ function D() {
|
|
|
351
385
|
/* @__PURE__ */ g("div", {
|
|
352
386
|
className: "flex-1 h-5 bg-bg-sunken rounded overflow-hidden",
|
|
353
387
|
children: /* @__PURE__ */ g("div", {
|
|
354
|
-
className: `h-5 rounded ${
|
|
388
|
+
className: `h-5 rounded ${q[e] ?? "bg-chart-6"}`,
|
|
355
389
|
style: { width: `${n}%` }
|
|
356
390
|
})
|
|
357
391
|
}),
|
|
@@ -376,7 +410,7 @@ function D() {
|
|
|
376
410
|
children: "Top Users by Spending"
|
|
377
411
|
}) }), /* @__PURE__ */ g(S, { children: /* @__PURE__ */ g("div", {
|
|
378
412
|
className: "space-y-2",
|
|
379
|
-
children: [...
|
|
413
|
+
children: [...N].filter((e) => e.totalSpent > 0).sort((e, t) => t.totalSpent - e.totalSpent).slice(0, 5).map((e, t) => /* @__PURE__ */ _("div", {
|
|
380
414
|
className: "flex items-center justify-between rounded-lg border border-border-seam p-3",
|
|
381
415
|
children: [/* @__PURE__ */ _("div", {
|
|
382
416
|
className: "flex items-center gap-3",
|
|
@@ -405,7 +439,7 @@ function D() {
|
|
|
405
439
|
})
|
|
406
440
|
]
|
|
407
441
|
}),
|
|
408
|
-
|
|
442
|
+
P === "revenue" && /* @__PURE__ */ g("div", {
|
|
409
443
|
className: "space-y-6",
|
|
410
444
|
children: /* @__PURE__ */ _(x, {
|
|
411
445
|
className: "border-border-seam",
|
|
@@ -414,7 +448,7 @@ function D() {
|
|
|
414
448
|
children: "Monthly Revenue Comparison"
|
|
415
449
|
}) }), /* @__PURE__ */ g(S, {
|
|
416
450
|
className: "space-y-4",
|
|
417
|
-
children:
|
|
451
|
+
children: A.map((e) => {
|
|
418
452
|
let t = e.prev ? ((e.value - e.prev) / e.prev * 100).toFixed(1) : "0", n = e.prev ? e.value >= e.prev : !0;
|
|
419
453
|
return /* @__PURE__ */ _("div", {
|
|
420
454
|
className: "rounded-lg border border-border-seam p-4",
|
|
@@ -446,10 +480,10 @@ function D() {
|
|
|
446
480
|
className: "mt-3 flex gap-1 h-4",
|
|
447
481
|
children: [/* @__PURE__ */ g("div", {
|
|
448
482
|
className: "h-4 rounded bg-accent-blue-subtle",
|
|
449
|
-
style: { width: `${(e.prev ?? 0) /
|
|
483
|
+
style: { width: `${(e.prev ?? 0) / H * 100}%` }
|
|
450
484
|
}), /* @__PURE__ */ g("div", {
|
|
451
485
|
className: "h-4 rounded bg-accent-blue",
|
|
452
|
-
style: { width: `${Math.max(0, (e.value - (e.prev ?? 0)) /
|
|
486
|
+
style: { width: `${Math.max(0, (e.value - (e.prev ?? 0)) / H * 100)}%` }
|
|
453
487
|
})]
|
|
454
488
|
})]
|
|
455
489
|
}, e.name);
|
|
@@ -457,7 +491,7 @@ function D() {
|
|
|
457
491
|
})]
|
|
458
492
|
})
|
|
459
493
|
}),
|
|
460
|
-
|
|
494
|
+
P === "bookings" && /* @__PURE__ */ _("div", {
|
|
461
495
|
className: "grid gap-6 lg:grid-cols-2",
|
|
462
496
|
children: [/* @__PURE__ */ _(x, {
|
|
463
497
|
className: "border-border-seam",
|
|
@@ -466,7 +500,7 @@ function D() {
|
|
|
466
500
|
children: "Bookings by Service Category"
|
|
467
501
|
}) }), /* @__PURE__ */ g(S, {
|
|
468
502
|
className: "space-y-3",
|
|
469
|
-
children: [...
|
|
503
|
+
children: [...j].sort((e, t) => t.value - e.value).map((e, t) => /* @__PURE__ */ _("div", {
|
|
470
504
|
className: "flex items-center gap-3",
|
|
471
505
|
children: [/* @__PURE__ */ _("span", {
|
|
472
506
|
className: "w-6 text-sm font-bold text-text-secondary tabular-nums",
|
|
@@ -486,7 +520,7 @@ function D() {
|
|
|
486
520
|
className: "mt-1 h-2 overflow-hidden rounded-full bg-bg-sunken",
|
|
487
521
|
children: /* @__PURE__ */ g("div", {
|
|
488
522
|
className: "h-2 rounded-full bg-accent-blue",
|
|
489
|
-
style: { width: `${e.value /
|
|
523
|
+
style: { width: `${e.value / U * 100}%` }
|
|
490
524
|
})
|
|
491
525
|
})]
|
|
492
526
|
})]
|
|
@@ -502,7 +536,7 @@ function D() {
|
|
|
502
536
|
children: [
|
|
503
537
|
{
|
|
504
538
|
label: "Total Bookings",
|
|
505
|
-
value:
|
|
539
|
+
value: j.reduce((e, t) => e + t.value, 0).toLocaleString(),
|
|
506
540
|
icon: d
|
|
507
541
|
},
|
|
508
542
|
{
|
|
@@ -540,6 +574,6 @@ function D() {
|
|
|
540
574
|
});
|
|
541
575
|
}
|
|
542
576
|
//#endregion
|
|
543
|
-
export {
|
|
577
|
+
export { k as AdminReportsPage };
|
|
544
578
|
|
|
545
579
|
//# sourceMappingURL=AdminReportsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminReportsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/AdminReportsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Badge,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n AuroraBackground,\n GradientText,\n CTAOverflowMenu,\n} from '@burdenoff/fe-libs/ui';\nimport {\n TrendingUp,\n TrendingDown,\n Minus,\n Download,\n FileText,\n BarChart3,\n Calendar,\n Users,\n DollarSign,\n ShoppingCart,\n Star,\n AlertTriangle,\n ArrowUpRight,\n Shield,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\n\ntype ReportTab = 'overview' | 'users' | 'revenue' | 'bookings';\ntype TimeRange = '7d' | '30d' | '90d' | '1y';\n\nexport function AdminReportsPage() {\n const { adminReportMetrics, revenueData, servicePerformance, geographicData, adminUsers } =\n useProServiceWorldStore();\n const [activeTab, setActiveTab] = useState<ReportTab>('overview');\n const [timeRange, setTimeRange] = useState<TimeRange>('30d');\n\n const tabs: { key: ReportTab; label: string; icon: typeof BarChart3 }[] = [\n { key: 'overview', label: 'Overview', icon: BarChart3 },\n { key: 'users', label: 'Users', icon: Users },\n { key: 'revenue', label: 'Revenue', icon: DollarSign },\n { key: 'bookings', label: 'Bookings', icon: ShoppingCart },\n ];\n\n const changeIcon = (type: string) => {\n if (type === 'positive')\n return <TrendingUp className=\"h-4 w-4 text-status-success-text\" />;\n if (type === 'negative')\n return <TrendingDown className=\"h-4 w-4 text-status-error-text\" />;\n return <Minus className=\"h-4 w-4 text-text-secondary\" />;\n };\n\n const changeColor = (type: string) =>\n type === 'positive'\n ? 'text-status-success-text'\n : type === 'negative'\n ? 'text-status-error-text'\n : 'text-text-secondary';\n\n const metricIcons = [Users, Shield, ShoppingCart, DollarSign, Star, AlertTriangle];\n\n const maxRevenue = Math.max(...revenueData.map((d) => d.value));\n const maxServiceBookings = Math.max(...servicePerformance.map((d) => d.value));\n\n // Role distribution\n const roleCounts = adminUsers.reduce<Record<string, number>>((acc, u) => {\n acc[u.role] = (acc[u.role] ?? 0) + 1;\n return acc;\n }, {});\n\n // Status distribution\n const statusCounts = adminUsers.reduce<Record<string, number>>((acc, u) => {\n acc[u.status] = (acc[u.status] ?? 0) + 1;\n return acc;\n }, {});\n\n const roleColors: Record<string, string> = {\n client: 'bg-chart-1',\n provider: 'bg-chart-2',\n admin: 'bg-chart-3',\n team_member: 'bg-chart-4',\n };\n\n const statusColors: Record<string, string> = {\n active: 'bg-status-success-text',\n suspended: 'bg-status-warning-text',\n pending: 'bg-chart-5',\n banned: 'bg-status-error-text',\n };\n\n return (\n <div className=\"space-y-6 p-6 bg-bg-canvas min-h-screen\">\n {/* Hero header — the ONE aurora backdrop for this page. */}\n <div className=\"relative overflow-hidden rounded-card -mx-2 px-2 pt-1\">\n <AuroraBackground intensity=\"subtle\" />\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold tracking-tight\">\n <GradientText>Admin Reports</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Platform analytics, performance metrics, and operational reports.\n </p>\n </div>\n <CTAOverflowMenu\n primary={{\n label: 'Generate Report',\n onSelect: () => {},\n icon: <FileText className=\"h-4 w-4\" />,\n }}\n actions={[\n {\n label: 'Export CSV',\n onSelect: () => {},\n icon: <Download className=\"h-4 w-4\" />,\n },\n ]}\n />\n </div>\n </div>\n\n {/* Time Range + Tabs */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex gap-1 border-b border-border-seam\">\n {tabs.map(({ key, label, icon: Icon }) => (\n <button\n key={key}\n onClick={() => setActiveTab(key)}\n aria-pressed={activeTab === key}\n className={`flex items-center gap-2 border-b-2 px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${\n activeTab === key\n ? 'border-accent-blue text-accent-blue'\n : 'border-transparent text-text-secondary hover:text-text-primary'\n }`}\n >\n <Icon className=\"h-4 w-4\" />\n {label}\n </button>\n ))}\n </div>\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-text-secondary\" />\n <div className=\"flex rounded border border-border-seam\">\n {(['7d', '30d', '90d', '1y'] as TimeRange[]).map((range) => (\n <button\n key={range}\n onClick={() => setTimeRange(range)}\n aria-pressed={timeRange === range}\n className={`px-3 py-1 text-xs font-medium tabular-nums focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${\n timeRange === range\n ? 'bg-accent-blue text-action-primary-text'\n : 'text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {range}\n </button>\n ))}\n </div>\n </div>\n </div>\n\n {/* Metric Cards */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {adminReportMetrics.map((metric, i) => {\n const MetricIcon = metricIcons[i % metricIcons.length] ?? BarChart3;\n return (\n <GlassCard key={i} treatment=\"metric\" className=\"border-border-seam\">\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <MetricIcon className=\"h-4 w-4 text-text-secondary\" />\n <p className=\"text-sm font-medium text-text-secondary\">{metric.label}</p>\n </div>\n {changeIcon(metric.changeType)}\n </div>\n <p className=\"mt-2 text-3xl font-bold tabular-nums\">{metric.value}</p>\n <p className={`mt-1 flex items-center gap-1 text-xs ${changeColor(metric.changeType)}`}>\n <ArrowUpRight className=\"h-3 w-3\" />\n {metric.change} vs last period\n </p>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n\n {/* Overview Tab */}\n {activeTab === 'overview' && (\n <div className=\"grid gap-6 lg:grid-cols-2\">\n {/* Revenue trend */}\n <Card className=\"border-border-seam\">\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">Platform Revenue</CardTitle>\n <Badge variant=\"secondary\">Monthly</Badge>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {revenueData.map((point) => (\n <div key={point.name} className=\"flex items-center gap-3\">\n <span className=\"w-10 text-xs text-text-secondary\">{point.name}</span>\n <div className=\"flex-1 h-5 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-5 bg-accent-blue rounded\"\n style={{ width: `${(point.value / maxRevenue) * 100}%` }}\n />\n </div>\n <span className=\"w-16 text-right text-xs font-medium tabular-nums\">\n ${(point.value / 1000).toFixed(1)}k\n </span>\n </div>\n ))}\n </CardContent>\n </Card>\n\n {/* Service breakdown */}\n <Card className=\"border-border-seam\">\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">Service Bookings</CardTitle>\n <Badge variant=\"secondary\">Total</Badge>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {[...servicePerformance]\n .sort((a, b) => b.value - a.value)\n .map((service) => (\n <div key={service.name} className=\"flex items-center gap-3\">\n <span className=\"w-32 truncate text-xs text-text-secondary\">\n {service.name}\n </span>\n <div className=\"flex-1 h-5 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-5 bg-accent-purple rounded\"\n style={{ width: `${(service.value / maxServiceBookings) * 100}%` }}\n />\n </div>\n <span className=\"w-12 text-right text-xs font-medium tabular-nums\">\n {service.value}\n </span>\n </div>\n ))}\n </CardContent>\n </Card>\n\n {/* Geographic revenue */}\n <Card className=\"lg:col-span-2 border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Revenue by Region</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-text-secondary\">\n <th className=\"p-3\">Region</th>\n <th className=\"p-3 text-right\">Clients</th>\n <th className=\"p-3 text-right\">Revenue</th>\n <th className=\"p-3 text-right\">Avg. per Client</th>\n <th className=\"p-3\">Share</th>\n </tr>\n </thead>\n <tbody>\n {geographicData.map((geo) => {\n const totalRevenue = geographicData.reduce((s, g) => s + g.revenue, 0);\n const pct = Math.round((geo.revenue / totalRevenue) * 100);\n const avgPerClient = Math.round(geo.revenue / geo.clients);\n return (\n <tr\n key={geo.country}\n className=\"border-b border-border-seam hover:bg-bg-sunken\"\n >\n <td className=\"p-3 font-medium\">{geo.country}</td>\n <td className=\"p-3 text-right tabular-nums\">{geo.clients}</td>\n <td className=\"p-3 text-right font-medium tabular-nums\">\n ${(geo.revenue / 1000).toFixed(0)}k\n </td>\n <td className=\"p-3 text-right text-text-secondary tabular-nums\">\n ${avgPerClient.toLocaleString()}\n </td>\n <td className=\"p-3\">\n <div className=\"flex items-center gap-2\">\n <div className=\"h-2 flex-1 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-accent-blue\"\n style={{ width: `${pct}%` }}\n />\n </div>\n <span className=\"w-10 text-right text-xs tabular-nums\">{pct}%</span>\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </CardContent>\n </Card>\n </div>\n )}\n\n {/* Users Tab */}\n {activeTab === 'users' && (\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">User Role Distribution</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(roleCounts).map(([role, count]) => {\n const pct = Math.round((count / adminUsers.length) * 100);\n return (\n <div key={role} className=\"flex items-center gap-3\">\n <span className=\"w-24 text-sm font-medium capitalize\">\n {role.replace('_', ' ')}\n </span>\n <div className=\"flex-1 h-5 bg-bg-sunken rounded overflow-hidden\">\n <div\n className={`h-5 rounded ${roleColors[role] ?? 'bg-chart-6'}`}\n style={{ width: `${pct}%` }}\n />\n </div>\n <span className=\"w-16 text-right text-sm tabular-nums\">\n {count} ({pct}%)\n </span>\n </div>\n );\n })}\n </CardContent>\n </Card>\n\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">User Status Distribution</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(statusCounts).map(([status, count]) => {\n const pct = Math.round((count / adminUsers.length) * 100);\n return (\n <div key={status} className=\"flex items-center gap-3\">\n <span className=\"w-24 text-sm font-medium capitalize\">{status}</span>\n <div className=\"flex-1 h-5 bg-bg-sunken rounded overflow-hidden\">\n <div\n className={`h-5 rounded ${statusColors[status] ?? 'bg-chart-6'}`}\n style={{ width: `${pct}%` }}\n />\n </div>\n <span className=\"w-16 text-right text-sm tabular-nums\">\n {count} ({pct}%)\n </span>\n </div>\n );\n })}\n </CardContent>\n </Card>\n\n {/* Top users by spend */}\n <Card className=\"lg:col-span-2 border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Top Users by Spending</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-2\">\n {[...adminUsers]\n .filter((u) => u.totalSpent > 0)\n .sort((a, b) => b.totalSpent - a.totalSpent)\n .slice(0, 5)\n .map((user, idx) => (\n <div\n key={user.id}\n className=\"flex items-center justify-between rounded-lg border border-border-seam p-3\"\n >\n <div className=\"flex items-center gap-3\">\n <span className=\"flex h-8 w-8 items-center justify-center rounded-full bg-accent-soft text-sm font-bold text-text-primary tabular-nums\">\n {idx + 1}\n </span>\n <div>\n <p className=\"font-medium\">{user.name}</p>\n <p className=\"text-xs text-text-secondary\">{user.email}</p>\n </div>\n </div>\n <div className=\"text-right\">\n <p className=\"font-bold tabular-nums\">\n ${user.totalSpent.toLocaleString()}\n </p>\n <p className=\"text-xs text-text-secondary tabular-nums\">\n {user.bookingsCount} bookings\n </p>\n </div>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n </div>\n )}\n\n {/* Revenue Tab */}\n {activeTab === 'revenue' && (\n <div className=\"space-y-6\">\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Monthly Revenue Comparison</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {revenueData.map((point) => {\n const growth = point.prev\n ? (((point.value - point.prev) / point.prev) * 100).toFixed(1)\n : '0';\n const isPositive = point.prev ? point.value >= point.prev : true;\n return (\n <div key={point.name} className=\"rounded-lg border border-border-seam p-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"font-medium\">{point.name} 2025</p>\n <div className=\"mt-1 flex items-center gap-4 text-sm text-text-secondary tabular-nums\">\n <span>Current: ${(point.value / 1000).toFixed(1)}k</span>\n <span>Previous: ${((point.prev ?? 0) / 1000).toFixed(1)}k</span>\n </div>\n </div>\n <Badge variant={isPositive ? 'default' : 'destructive'}>\n {isPositive ? '+' : ''}\n {growth}%\n </Badge>\n </div>\n <div className=\"mt-3 flex gap-1 h-4\">\n <div\n className=\"h-4 rounded bg-accent-blue-subtle\"\n style={{ width: `${((point.prev ?? 0) / maxRevenue) * 100}%` }}\n />\n <div\n className=\"h-4 rounded bg-accent-blue\"\n style={{\n width: `${Math.max(0, ((point.value - (point.prev ?? 0)) / maxRevenue) * 100)}%`,\n }}\n />\n </div>\n </div>\n );\n })}\n </CardContent>\n </Card>\n </div>\n )}\n\n {/* Bookings Tab */}\n {activeTab === 'bookings' && (\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Bookings by Service Category</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {[...servicePerformance]\n .sort((a, b) => b.value - a.value)\n .map((service, idx) => (\n <div key={service.name} className=\"flex items-center gap-3\">\n <span className=\"w-6 text-sm font-bold text-text-secondary tabular-nums\">\n #{idx + 1}\n </span>\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"font-medium\">{service.name}</span>\n <span className=\"tabular-nums\">{service.value} bookings</span>\n </div>\n <div className=\"mt-1 h-2 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-accent-blue\"\n style={{ width: `${(service.value / maxServiceBookings) * 100}%` }}\n />\n </div>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Quick Stats</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {[\n {\n label: 'Total Bookings',\n value: servicePerformance.reduce((s, p) => s + p.value, 0).toLocaleString(),\n icon: ShoppingCart,\n },\n { label: 'Avg. Booking Value', value: '$3,804', icon: DollarSign },\n { label: 'Completion Rate', value: '96.5%', icon: TrendingUp },\n { label: 'Client Satisfaction', value: '4.7/5.0', icon: Star },\n ].map(({ label, value, icon: Icon }) => (\n <div\n key={label}\n className=\"flex items-center justify-between rounded-lg border border-border-seam p-3\"\n >\n <div className=\"flex items-center gap-3\">\n <Icon className=\"h-5 w-5 text-text-secondary\" />\n <span className=\"text-sm font-medium\">{label}</span>\n </div>\n <span className=\"text-lg font-bold tabular-nums\">{value}</span>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AdminReportsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/AdminReportsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Badge,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n AuroraBackground,\n GradientText,\n CTAOverflowMenu,\n PagePurpose,\n NextSteps,\n} from '@burdenoff/fe-libs/ui';\nimport {\n TrendingUp,\n TrendingDown,\n Minus,\n Download,\n FileText,\n BarChart3,\n Calendar,\n Users,\n DollarSign,\n ShoppingCart,\n Star,\n AlertTriangle,\n ArrowUpRight,\n Shield,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\n\ntype ReportTab = 'overview' | 'users' | 'revenue' | 'bookings';\ntype TimeRange = '7d' | '30d' | '90d' | '1y';\n\nexport function AdminReportsPage() {\n const { adminReportMetrics, revenueData, servicePerformance, geographicData, adminUsers } =\n useProServiceWorldStore();\n const [activeTab, setActiveTab] = useState<ReportTab>('overview');\n const [timeRange, setTimeRange] = useState<TimeRange>('30d');\n\n const tabs: { key: ReportTab; label: string; icon: typeof BarChart3 }[] = [\n { key: 'overview', label: 'Overview', icon: BarChart3 },\n { key: 'users', label: 'Users', icon: Users },\n { key: 'revenue', label: 'Revenue', icon: DollarSign },\n { key: 'bookings', label: 'Bookings', icon: ShoppingCart },\n ];\n\n const changeIcon = (type: string) => {\n if (type === 'positive')\n return <TrendingUp className=\"h-4 w-4 text-status-success-text\" />;\n if (type === 'negative')\n return <TrendingDown className=\"h-4 w-4 text-status-error-text\" />;\n return <Minus className=\"h-4 w-4 text-text-secondary\" />;\n };\n\n const changeColor = (type: string) =>\n type === 'positive'\n ? 'text-status-success-text'\n : type === 'negative'\n ? 'text-status-error-text'\n : 'text-text-secondary';\n\n const metricIcons = [Users, Shield, ShoppingCart, DollarSign, Star, AlertTriangle];\n\n const maxRevenue = Math.max(...revenueData.map((d) => d.value));\n const maxServiceBookings = Math.max(...servicePerformance.map((d) => d.value));\n\n // Role distribution\n const roleCounts = adminUsers.reduce<Record<string, number>>((acc, u) => {\n acc[u.role] = (acc[u.role] ?? 0) + 1;\n return acc;\n }, {});\n\n // Status distribution\n const statusCounts = adminUsers.reduce<Record<string, number>>((acc, u) => {\n acc[u.status] = (acc[u.status] ?? 0) + 1;\n return acc;\n }, {});\n\n const roleColors: Record<string, string> = {\n client: 'bg-chart-1',\n provider: 'bg-chart-2',\n admin: 'bg-chart-3',\n team_member: 'bg-chart-4',\n };\n\n const statusColors: Record<string, string> = {\n active: 'bg-status-success-text',\n suspended: 'bg-status-warning-text',\n pending: 'bg-chart-5',\n banned: 'bg-status-error-text',\n };\n\n return (\n <div className=\"space-y-6 p-6 bg-bg-canvas min-h-screen\">\n {/* Hero header — the ONE aurora backdrop for this page. */}\n <div className=\"relative overflow-hidden rounded-card -mx-2 px-2 pt-1\">\n <AuroraBackground intensity=\"subtle\" />\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold tracking-tight\">\n <GradientText>Admin Reports</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Platform analytics, performance metrics, and operational reports.\n </p>\n </div>\n <CTAOverflowMenu\n primary={{\n label: 'Generate Report',\n onSelect: () => {},\n icon: <FileText className=\"h-4 w-4\" />,\n }}\n actions={[\n {\n label: 'Export CSV',\n onSelect: () => {},\n icon: <Download className=\"h-4 w-4\" />,\n },\n ]}\n />\n </div>\n </div>\n\n <PagePurpose icon={<BarChart3 className=\"h-4 w-4\" />}>\n The operational pulse of the platform. Track headline KPIs, dig into revenue, user, and\n booking trends across time ranges, and export reports for stakeholders — use it to spot\n what is growing, what needs attention, and where to act next.\n </PagePurpose>\n\n <NextSteps\n storageKey=\"proserviceworld-admin-reports\"\n steps={[\n {\n id: 'revenue',\n label: 'Review revenue trends',\n description: 'Open the Revenue tab to compare months and spot growth or dips.',\n icon: <DollarSign className=\"h-4 w-4\" />,\n onClick: () => setActiveTab('revenue'),\n },\n {\n id: 'users',\n label: 'Check user health',\n description: 'See role and status distribution plus your top spenders.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => setActiveTab('users'),\n },\n {\n id: 'export',\n label: 'Export a report',\n description: 'Generate a shareable report or CSV for stakeholders.',\n icon: <Download className=\"h-4 w-4\" />,\n onClick: () => {},\n },\n ]}\n />\n\n {/* Time Range + Tabs */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex gap-1 border-b border-border-seam\">\n {tabs.map(({ key, label, icon: Icon }) => (\n <button\n key={key}\n onClick={() => setActiveTab(key)}\n aria-pressed={activeTab === key}\n className={`flex items-center gap-2 border-b-2 px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${\n activeTab === key\n ? 'border-accent-blue text-accent-blue'\n : 'border-transparent text-text-secondary hover:text-text-primary'\n }`}\n >\n <Icon className=\"h-4 w-4\" />\n {label}\n </button>\n ))}\n </div>\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-text-secondary\" />\n <div className=\"flex rounded border border-border-seam\">\n {(['7d', '30d', '90d', '1y'] as TimeRange[]).map((range) => (\n <button\n key={range}\n onClick={() => setTimeRange(range)}\n aria-pressed={timeRange === range}\n className={`px-3 py-1 text-xs font-medium tabular-nums focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${\n timeRange === range\n ? 'bg-accent-blue text-action-primary-text'\n : 'text-text-secondary hover:bg-bg-sunken'\n }`}\n >\n {range}\n </button>\n ))}\n </div>\n </div>\n </div>\n\n {/* Metric Cards */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {adminReportMetrics.map((metric, i) => {\n const MetricIcon = metricIcons[i % metricIcons.length] ?? BarChart3;\n return (\n <GlassCard\n key={i}\n treatment={i === 0 ? 'glass' : 'metric'}\n glow={i === 0}\n className=\"border-border-seam\"\n >\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <MetricIcon className=\"h-4 w-4 text-text-secondary\" />\n <p className=\"text-sm font-medium text-text-secondary\">{metric.label}</p>\n </div>\n {changeIcon(metric.changeType)}\n </div>\n <p className=\"mt-2 text-3xl font-bold tabular-nums\">{metric.value}</p>\n <p className={`mt-1 flex items-center gap-1 text-xs ${changeColor(metric.changeType)}`}>\n <ArrowUpRight className=\"h-3 w-3\" />\n {metric.change} vs last period\n </p>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n\n {/* Overview Tab */}\n {activeTab === 'overview' && (\n <div className=\"grid gap-6 lg:grid-cols-2\">\n {/* Revenue trend */}\n <Card className=\"border-border-seam\">\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">Platform Revenue</CardTitle>\n <Badge variant=\"secondary\">Monthly</Badge>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {revenueData.map((point) => (\n <div key={point.name} className=\"flex items-center gap-3\">\n <span className=\"w-10 text-xs text-text-secondary\">{point.name}</span>\n <div className=\"flex-1 h-5 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-5 bg-accent-blue rounded\"\n style={{ width: `${(point.value / maxRevenue) * 100}%` }}\n />\n </div>\n <span className=\"w-16 text-right text-xs font-medium tabular-nums\">\n ${(point.value / 1000).toFixed(1)}k\n </span>\n </div>\n ))}\n </CardContent>\n </Card>\n\n {/* Service breakdown */}\n <Card className=\"border-border-seam\">\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">Service Bookings</CardTitle>\n <Badge variant=\"secondary\">Total</Badge>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {[...servicePerformance]\n .sort((a, b) => b.value - a.value)\n .map((service) => (\n <div key={service.name} className=\"flex items-center gap-3\">\n <span className=\"w-32 truncate text-xs text-text-secondary\">\n {service.name}\n </span>\n <div className=\"flex-1 h-5 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-5 bg-accent-purple rounded\"\n style={{ width: `${(service.value / maxServiceBookings) * 100}%` }}\n />\n </div>\n <span className=\"w-12 text-right text-xs font-medium tabular-nums\">\n {service.value}\n </span>\n </div>\n ))}\n </CardContent>\n </Card>\n\n {/* Geographic revenue */}\n <Card className=\"lg:col-span-2 border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Revenue by Region</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-text-secondary\">\n <th className=\"p-3\">Region</th>\n <th className=\"p-3 text-right\">Clients</th>\n <th className=\"p-3 text-right\">Revenue</th>\n <th className=\"p-3 text-right\">Avg. per Client</th>\n <th className=\"p-3\">Share</th>\n </tr>\n </thead>\n <tbody>\n {geographicData.map((geo) => {\n const totalRevenue = geographicData.reduce((s, g) => s + g.revenue, 0);\n const pct = Math.round((geo.revenue / totalRevenue) * 100);\n const avgPerClient = Math.round(geo.revenue / geo.clients);\n return (\n <tr\n key={geo.country}\n className=\"border-b border-border-seam hover:bg-bg-sunken\"\n >\n <td className=\"p-3 font-medium\">{geo.country}</td>\n <td className=\"p-3 text-right tabular-nums\">{geo.clients}</td>\n <td className=\"p-3 text-right font-medium tabular-nums\">\n ${(geo.revenue / 1000).toFixed(0)}k\n </td>\n <td className=\"p-3 text-right text-text-secondary tabular-nums\">\n ${avgPerClient.toLocaleString()}\n </td>\n <td className=\"p-3\">\n <div className=\"flex items-center gap-2\">\n <div className=\"h-2 flex-1 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-accent-blue\"\n style={{ width: `${pct}%` }}\n />\n </div>\n <span className=\"w-10 text-right text-xs tabular-nums\">{pct}%</span>\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </CardContent>\n </Card>\n </div>\n )}\n\n {/* Users Tab */}\n {activeTab === 'users' && (\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">User Role Distribution</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(roleCounts).map(([role, count]) => {\n const pct = Math.round((count / adminUsers.length) * 100);\n return (\n <div key={role} className=\"flex items-center gap-3\">\n <span className=\"w-24 text-sm font-medium capitalize\">\n {role.replace('_', ' ')}\n </span>\n <div className=\"flex-1 h-5 bg-bg-sunken rounded overflow-hidden\">\n <div\n className={`h-5 rounded ${roleColors[role] ?? 'bg-chart-6'}`}\n style={{ width: `${pct}%` }}\n />\n </div>\n <span className=\"w-16 text-right text-sm tabular-nums\">\n {count} ({pct}%)\n </span>\n </div>\n );\n })}\n </CardContent>\n </Card>\n\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">User Status Distribution</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(statusCounts).map(([status, count]) => {\n const pct = Math.round((count / adminUsers.length) * 100);\n return (\n <div key={status} className=\"flex items-center gap-3\">\n <span className=\"w-24 text-sm font-medium capitalize\">{status}</span>\n <div className=\"flex-1 h-5 bg-bg-sunken rounded overflow-hidden\">\n <div\n className={`h-5 rounded ${statusColors[status] ?? 'bg-chart-6'}`}\n style={{ width: `${pct}%` }}\n />\n </div>\n <span className=\"w-16 text-right text-sm tabular-nums\">\n {count} ({pct}%)\n </span>\n </div>\n );\n })}\n </CardContent>\n </Card>\n\n {/* Top users by spend */}\n <Card className=\"lg:col-span-2 border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Top Users by Spending</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-2\">\n {[...adminUsers]\n .filter((u) => u.totalSpent > 0)\n .sort((a, b) => b.totalSpent - a.totalSpent)\n .slice(0, 5)\n .map((user, idx) => (\n <div\n key={user.id}\n className=\"flex items-center justify-between rounded-lg border border-border-seam p-3\"\n >\n <div className=\"flex items-center gap-3\">\n <span className=\"flex h-8 w-8 items-center justify-center rounded-full bg-accent-soft text-sm font-bold text-text-primary tabular-nums\">\n {idx + 1}\n </span>\n <div>\n <p className=\"font-medium\">{user.name}</p>\n <p className=\"text-xs text-text-secondary\">{user.email}</p>\n </div>\n </div>\n <div className=\"text-right\">\n <p className=\"font-bold tabular-nums\">\n ${user.totalSpent.toLocaleString()}\n </p>\n <p className=\"text-xs text-text-secondary tabular-nums\">\n {user.bookingsCount} bookings\n </p>\n </div>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n </div>\n )}\n\n {/* Revenue Tab */}\n {activeTab === 'revenue' && (\n <div className=\"space-y-6\">\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Monthly Revenue Comparison</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {revenueData.map((point) => {\n const growth = point.prev\n ? (((point.value - point.prev) / point.prev) * 100).toFixed(1)\n : '0';\n const isPositive = point.prev ? point.value >= point.prev : true;\n return (\n <div key={point.name} className=\"rounded-lg border border-border-seam p-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"font-medium\">{point.name} 2025</p>\n <div className=\"mt-1 flex items-center gap-4 text-sm text-text-secondary tabular-nums\">\n <span>Current: ${(point.value / 1000).toFixed(1)}k</span>\n <span>Previous: ${((point.prev ?? 0) / 1000).toFixed(1)}k</span>\n </div>\n </div>\n <Badge variant={isPositive ? 'default' : 'destructive'}>\n {isPositive ? '+' : ''}\n {growth}%\n </Badge>\n </div>\n <div className=\"mt-3 flex gap-1 h-4\">\n <div\n className=\"h-4 rounded bg-accent-blue-subtle\"\n style={{ width: `${((point.prev ?? 0) / maxRevenue) * 100}%` }}\n />\n <div\n className=\"h-4 rounded bg-accent-blue\"\n style={{\n width: `${Math.max(0, ((point.value - (point.prev ?? 0)) / maxRevenue) * 100)}%`,\n }}\n />\n </div>\n </div>\n );\n })}\n </CardContent>\n </Card>\n </div>\n )}\n\n {/* Bookings Tab */}\n {activeTab === 'bookings' && (\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Bookings by Service Category</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {[...servicePerformance]\n .sort((a, b) => b.value - a.value)\n .map((service, idx) => (\n <div key={service.name} className=\"flex items-center gap-3\">\n <span className=\"w-6 text-sm font-bold text-text-secondary tabular-nums\">\n #{idx + 1}\n </span>\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"font-medium\">{service.name}</span>\n <span className=\"tabular-nums\">{service.value} bookings</span>\n </div>\n <div className=\"mt-1 h-2 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-accent-blue\"\n style={{ width: `${(service.value / maxServiceBookings) * 100}%` }}\n />\n </div>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Quick Stats</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {[\n {\n label: 'Total Bookings',\n value: servicePerformance.reduce((s, p) => s + p.value, 0).toLocaleString(),\n icon: ShoppingCart,\n },\n { label: 'Avg. Booking Value', value: '$3,804', icon: DollarSign },\n { label: 'Completion Rate', value: '96.5%', icon: TrendingUp },\n { label: 'Client Satisfaction', value: '4.7/5.0', icon: Star },\n ].map(({ label, value, icon: Icon }) => (\n <div\n key={label}\n className=\"flex items-center justify-between rounded-lg border border-border-seam p-3\"\n >\n <div className=\"flex items-center gap-3\">\n <Icon className=\"h-5 w-5 text-text-secondary\" />\n <span className=\"text-sm font-medium\">{label}</span>\n </div>\n <span className=\"text-lg font-bold tabular-nums\">{value}</span>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|