@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,9 +2,9 @@ import { useProServiceWorldStore as e } from "../store/proserviceworldStore.js";
|
|
|
2
2
|
import { useMemo as t, useState as n } from "react";
|
|
3
3
|
import { Ban as r, CheckCircle2 as i, Clock as a, Download as o, Filter as s, Mail as c, MoreHorizontal as l, Plus as u, Search as d, Shield as f, Star as p, UserCheck as m, Users as h, XCircle as g } from "lucide-react";
|
|
4
4
|
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
5
|
-
import { AuroraBackground as y, Badge as b, Button as x, CTAOverflowMenu as S, Card as C, CardContent as w, GlassCard as T, GradientText as E, IllustratedEmptyState as D, Input as O } from "@burdenoff/fe-libs/ui";
|
|
5
|
+
import { AuroraBackground as y, Badge as b, Button as x, CTAOverflowMenu as S, Card as C, CardContent as w, GlassCard as T, GradientText as E, IllustratedEmptyState as D, Input as O, PagePurpose as k } from "@burdenoff/fe-libs/ui";
|
|
6
6
|
//#region src/proserviceworld/pages/AdminUsersPage.tsx
|
|
7
|
-
var
|
|
7
|
+
var A = {
|
|
8
8
|
client: {
|
|
9
9
|
label: "Client",
|
|
10
10
|
variant: "secondary"
|
|
@@ -21,7 +21,7 @@ var k = {
|
|
|
21
21
|
label: "Team",
|
|
22
22
|
variant: "outline"
|
|
23
23
|
}
|
|
24
|
-
},
|
|
24
|
+
}, j = {
|
|
25
25
|
active: {
|
|
26
26
|
label: "Active",
|
|
27
27
|
icon: i,
|
|
@@ -43,22 +43,22 @@ var k = {
|
|
|
43
43
|
color: "text-status-error-text"
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
|
-
function
|
|
47
|
-
let { adminUsers: r } = e(), [a,
|
|
46
|
+
function M() {
|
|
47
|
+
let { adminUsers: r } = e(), [a, M] = n(""), [N, P] = n("all"), [F, I] = n("all"), [L, R] = n(/* @__PURE__ */ new Set()), z = t(() => {
|
|
48
48
|
let e = [...r];
|
|
49
|
-
if (
|
|
49
|
+
if (N !== "all" && (e = e.filter((e) => e.role === N)), F !== "all" && (e = e.filter((e) => e.status === F)), a.trim()) {
|
|
50
50
|
let t = a.toLowerCase();
|
|
51
51
|
e = e.filter((e) => e.name.toLowerCase().includes(t) || e.email.toLowerCase().includes(t));
|
|
52
52
|
}
|
|
53
53
|
return e;
|
|
54
54
|
}, [
|
|
55
55
|
r,
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
N,
|
|
57
|
+
F,
|
|
58
58
|
a
|
|
59
|
-
]),
|
|
60
|
-
let t = new Set(
|
|
61
|
-
t.has(e) ? t.delete(e) : t.add(e),
|
|
59
|
+
]), B = r.length, V = r.filter((e) => e.status === "active").length, H = r.filter((e) => e.role === "provider").length, U = r.filter((e) => e.verified).length, W = (e) => {
|
|
60
|
+
let t = new Set(L);
|
|
61
|
+
t.has(e) ? t.delete(e) : t.add(e), R(t);
|
|
62
62
|
};
|
|
63
63
|
return /* @__PURE__ */ v("div", {
|
|
64
64
|
className: "space-y-6 p-6 bg-bg-canvas min-h-screen",
|
|
@@ -88,6 +88,10 @@ function j() {
|
|
|
88
88
|
})
|
|
89
89
|
]
|
|
90
90
|
}),
|
|
91
|
+
/* @__PURE__ */ _(k, {
|
|
92
|
+
icon: /* @__PURE__ */ _(h, { className: "h-4 w-4" }),
|
|
93
|
+
children: "The control room for everyone on the platform. Search, filter by role and status, and review each user's activity and spend at a glance — then invite new members or take bulk action to activate, suspend, or ban accounts as needed."
|
|
94
|
+
}),
|
|
91
95
|
/* @__PURE__ */ v("div", {
|
|
92
96
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
93
97
|
children: [
|
|
@@ -102,7 +106,7 @@ function j() {
|
|
|
102
106
|
children: /* @__PURE__ */ _(h, { className: "h-5 w-5 text-accent-blue" })
|
|
103
107
|
}), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
104
108
|
className: "text-2xl font-bold tabular-nums",
|
|
105
|
-
children:
|
|
109
|
+
children: B
|
|
106
110
|
}), /* @__PURE__ */ _("p", {
|
|
107
111
|
className: "text-xs text-text-secondary",
|
|
108
112
|
children: "Total Users"
|
|
@@ -119,7 +123,7 @@ function j() {
|
|
|
119
123
|
children: /* @__PURE__ */ _(m, { className: "h-5 w-5 text-status-success-text" })
|
|
120
124
|
}), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
121
125
|
className: "text-2xl font-bold tabular-nums",
|
|
122
|
-
children:
|
|
126
|
+
children: V
|
|
123
127
|
}), /* @__PURE__ */ _("p", {
|
|
124
128
|
className: "text-xs text-text-secondary",
|
|
125
129
|
children: "Active Users"
|
|
@@ -136,7 +140,7 @@ function j() {
|
|
|
136
140
|
children: /* @__PURE__ */ _(f, { className: "h-5 w-5 text-accent-purple" })
|
|
137
141
|
}), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
138
142
|
className: "text-2xl font-bold tabular-nums",
|
|
139
|
-
children:
|
|
143
|
+
children: H
|
|
140
144
|
}), /* @__PURE__ */ _("p", {
|
|
141
145
|
className: "text-xs text-text-secondary",
|
|
142
146
|
children: "Providers"
|
|
@@ -153,7 +157,7 @@ function j() {
|
|
|
153
157
|
children: /* @__PURE__ */ _(i, { className: "h-5 w-5 text-status-warning-text" })
|
|
154
158
|
}), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
155
159
|
className: "text-2xl font-bold tabular-nums",
|
|
156
|
-
children:
|
|
160
|
+
children: U
|
|
157
161
|
}), /* @__PURE__ */ _("p", {
|
|
158
162
|
className: "text-xs text-text-secondary",
|
|
159
163
|
children: "Verified"
|
|
@@ -169,7 +173,7 @@ function j() {
|
|
|
169
173
|
children: [/* @__PURE__ */ _(d, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ _(O, {
|
|
170
174
|
placeholder: "Search by name or email...",
|
|
171
175
|
value: a,
|
|
172
|
-
onChange: (e) =>
|
|
176
|
+
onChange: (e) => M(e.target.value),
|
|
173
177
|
className: "pl-10"
|
|
174
178
|
})]
|
|
175
179
|
}), /* @__PURE__ */ v("div", {
|
|
@@ -178,8 +182,8 @@ function j() {
|
|
|
178
182
|
/* @__PURE__ */ _(s, { className: "h-4 w-4 text-text-secondary" }),
|
|
179
183
|
/* @__PURE__ */ v("select", {
|
|
180
184
|
className: "rounded border border-border-seam bg-bg-surface px-2 py-1 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
181
|
-
value:
|
|
182
|
-
onChange: (e) =>
|
|
185
|
+
value: N,
|
|
186
|
+
onChange: (e) => P(e.target.value),
|
|
183
187
|
children: [
|
|
184
188
|
/* @__PURE__ */ _("option", {
|
|
185
189
|
value: "all",
|
|
@@ -205,8 +209,8 @@ function j() {
|
|
|
205
209
|
}),
|
|
206
210
|
/* @__PURE__ */ v("select", {
|
|
207
211
|
className: "rounded border border-border-seam bg-bg-surface px-2 py-1 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus",
|
|
208
|
-
value:
|
|
209
|
-
onChange: (e) =>
|
|
212
|
+
value: F,
|
|
213
|
+
onChange: (e) => I(e.target.value),
|
|
210
214
|
children: [
|
|
211
215
|
/* @__PURE__ */ _("option", {
|
|
212
216
|
value: "all",
|
|
@@ -233,12 +237,12 @@ function j() {
|
|
|
233
237
|
]
|
|
234
238
|
})]
|
|
235
239
|
}),
|
|
236
|
-
|
|
240
|
+
L.size > 0 && /* @__PURE__ */ v("div", {
|
|
237
241
|
className: "flex items-center gap-3 rounded-lg bg-accent-soft border border-border-seam p-3",
|
|
238
242
|
children: [
|
|
239
243
|
/* @__PURE__ */ v("span", {
|
|
240
244
|
className: "text-sm font-medium",
|
|
241
|
-
children: [
|
|
245
|
+
children: [L.size, " user(s) selected"]
|
|
242
246
|
}),
|
|
243
247
|
/* @__PURE__ */ _(x, {
|
|
244
248
|
variant: "outline",
|
|
@@ -272,9 +276,9 @@ function j() {
|
|
|
272
276
|
className: "p-3",
|
|
273
277
|
children: /* @__PURE__ */ _("input", {
|
|
274
278
|
type: "checkbox",
|
|
275
|
-
checked:
|
|
279
|
+
checked: L.size === z.length && z.length > 0,
|
|
276
280
|
onChange: () => {
|
|
277
|
-
|
|
281
|
+
L.size === z.length ? R(/* @__PURE__ */ new Set()) : R(new Set(z.map((e) => e.id)));
|
|
278
282
|
},
|
|
279
283
|
className: "rounded"
|
|
280
284
|
})
|
|
@@ -320,7 +324,7 @@ function j() {
|
|
|
320
324
|
children: "Actions"
|
|
321
325
|
})
|
|
322
326
|
]
|
|
323
|
-
}) }), /* @__PURE__ */ _("tbody", { children:
|
|
327
|
+
}) }), /* @__PURE__ */ _("tbody", { children: z.length === 0 ? /* @__PURE__ */ _("tr", { children: /* @__PURE__ */ _("td", {
|
|
324
328
|
colSpan: 11,
|
|
325
329
|
className: "p-4",
|
|
326
330
|
children: /* @__PURE__ */ _(D, {
|
|
@@ -328,8 +332,8 @@ function j() {
|
|
|
328
332
|
title: "No users found",
|
|
329
333
|
description: "No users match your current filters."
|
|
330
334
|
})
|
|
331
|
-
}) }) :
|
|
332
|
-
let t =
|
|
335
|
+
}) }) : z.map((e) => {
|
|
336
|
+
let t = j[e.status], n = t.icon;
|
|
333
337
|
return /* @__PURE__ */ v("tr", {
|
|
334
338
|
className: "border-b border-border-seam transition-colors hover:bg-bg-sunken/50",
|
|
335
339
|
children: [
|
|
@@ -338,8 +342,8 @@ function j() {
|
|
|
338
342
|
children: /* @__PURE__ */ _("input", {
|
|
339
343
|
type: "checkbox",
|
|
340
344
|
"aria-label": `Select ${e.name}`,
|
|
341
|
-
checked:
|
|
342
|
-
onChange: () =>
|
|
345
|
+
checked: L.has(e.id),
|
|
346
|
+
onChange: () => W(e.id),
|
|
343
347
|
className: "rounded"
|
|
344
348
|
})
|
|
345
349
|
}),
|
|
@@ -362,8 +366,8 @@ function j() {
|
|
|
362
366
|
/* @__PURE__ */ _("td", {
|
|
363
367
|
className: "p-3",
|
|
364
368
|
children: /* @__PURE__ */ _(b, {
|
|
365
|
-
variant:
|
|
366
|
-
children:
|
|
369
|
+
variant: A[e.role].variant,
|
|
370
|
+
children: A[e.role].label
|
|
367
371
|
})
|
|
368
372
|
}),
|
|
369
373
|
/* @__PURE__ */ _("td", {
|
|
@@ -434,7 +438,7 @@ function j() {
|
|
|
434
438
|
className: "mt-4 flex items-center justify-between border-t border-border-seam pt-4 text-sm text-text-secondary",
|
|
435
439
|
children: [/* @__PURE__ */ v("span", { children: [
|
|
436
440
|
"Showing ",
|
|
437
|
-
|
|
441
|
+
z.length,
|
|
438
442
|
" of ",
|
|
439
443
|
r.length,
|
|
440
444
|
" users"
|
|
@@ -459,6 +463,6 @@ function j() {
|
|
|
459
463
|
});
|
|
460
464
|
}
|
|
461
465
|
//#endregion
|
|
462
|
-
export {
|
|
466
|
+
export { M as AdminUsersPage };
|
|
463
467
|
|
|
464
468
|
//# sourceMappingURL=AdminUsersPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminUsersPage.js","names":[],"sources":["../../../src/proserviceworld/pages/AdminUsersPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n Input,\n GlassCard,\n AuroraBackground,\n GradientText,\n CTAOverflowMenu,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Search,\n Filter,\n Users,\n UserCheck,\n Shield,\n Star,\n CheckCircle2,\n XCircle,\n Clock,\n Ban,\n MoreHorizontal,\n Download,\n Plus,\n Mail,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\nimport type { AdminUserRole, AdminUserStatus } from '../types';\n\nconst roleConfig: Record<\n AdminUserRole,\n { label: string; variant: 'default' | 'secondary' | 'outline' | 'destructive' }\n> = {\n client: { label: 'Client', variant: 'secondary' },\n provider: { label: 'Provider', variant: 'default' },\n admin: { label: 'Admin', variant: 'destructive' },\n team_member: { label: 'Team', variant: 'outline' },\n};\n\nconst statusConfig: Record<\n AdminUserStatus,\n { label: string; icon: typeof CheckCircle2; color: string }\n> = {\n active: { label: 'Active', icon: CheckCircle2, color: 'text-status-success-text' },\n suspended: { label: 'Suspended', icon: Ban, color: 'text-status-warning-text' },\n pending: { label: 'Pending', icon: Clock, color: 'text-status-warning-text' },\n banned: { label: 'Banned', icon: XCircle, color: 'text-status-error-text' },\n};\n\nexport function AdminUsersPage() {\n const { adminUsers } = useProServiceWorldStore();\n const [searchQuery, setSearchQuery] = useState('');\n const [roleFilter, setRoleFilter] = useState<AdminUserRole | 'all'>('all');\n const [statusFilter, setStatusFilter] = useState<AdminUserStatus | 'all'>('all');\n const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());\n\n const filtered = useMemo(() => {\n let result = [...adminUsers];\n if (roleFilter !== 'all') result = result.filter((u) => u.role === roleFilter);\n if (statusFilter !== 'all') result = result.filter((u) => u.status === statusFilter);\n if (searchQuery.trim()) {\n const q = searchQuery.toLowerCase();\n result = result.filter(\n (u) => u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q)\n );\n }\n return result;\n }, [adminUsers, roleFilter, statusFilter, searchQuery]);\n\n // Stats\n const totalUsers = adminUsers.length;\n const activeUsers = adminUsers.filter((u) => u.status === 'active').length;\n const providers = adminUsers.filter((u) => u.role === 'provider').length;\n const verifiedUsers = adminUsers.filter((u) => u.verified).length;\n\n const toggleSelect = (id: string) => {\n const next = new Set(selectedIds);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n setSelectedIds(next);\n };\n\n const toggleSelectAll = () => {\n if (selectedIds.size === filtered.length) setSelectedIds(new Set());\n else setSelectedIds(new Set(filtered.map((u) => u.id)));\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 MFE */}\n <div className=\"relative overflow-hidden rounded-card -mx-2 px-2 pt-1 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <AuroraBackground intensity=\"subtle\" />\n <div>\n <h1 className=\"text-2xl font-semibold tracking-tight\">\n <GradientText>User Management</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Admin panel to manage platform users, roles, and access.\n </p>\n </div>\n <CTAOverflowMenu\n primary={{\n label: 'Invite User',\n onSelect: () => {},\n icon: <Plus className=\"h-4 w-4\" />,\n }}\n actions={[\n {\n label: 'Export',\n onSelect: () => {},\n icon: <Download className=\"h-4 w-4\" />,\n },\n ]}\n />\n </div>\n\n {/* Stats */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-status-info-bg-subtle p-2\">\n <Users className=\"h-5 w-5 text-accent-blue\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{totalUsers}</p>\n <p className=\"text-xs text-text-secondary\">Total Users</p>\n </div>\n </div>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-status-success-bg-subtle p-2\">\n <UserCheck className=\"h-5 w-5 text-status-success-text\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{activeUsers}</p>\n <p className=\"text-xs text-text-secondary\">Active Users</p>\n </div>\n </div>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-accent-purple-subtle p-2\">\n <Shield className=\"h-5 w-5 text-accent-purple\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{providers}</p>\n <p className=\"text-xs text-text-secondary\">Providers</p>\n </div>\n </div>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-status-warning-bg-subtle p-2\">\n <CheckCircle2 className=\"h-5 w-5 text-status-warning-text\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{verifiedUsers}</p>\n <p className=\"text-xs text-text-secondary\">Verified</p>\n </div>\n </div>\n </GlassCard>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search by name or email...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"pl-10\"\n />\n </div>\n <div className=\"flex items-center gap-2\">\n <Filter className=\"h-4 w-4 text-text-secondary\" />\n <select\n className=\"rounded border border-border-seam bg-bg-surface px-2 py-1 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n value={roleFilter}\n onChange={(e) => setRoleFilter(e.target.value as AdminUserRole | 'all')}\n >\n <option value=\"all\">All Roles</option>\n <option value=\"client\">Client</option>\n <option value=\"provider\">Provider</option>\n <option value=\"admin\">Admin</option>\n <option value=\"team_member\">Team Member</option>\n </select>\n <select\n className=\"rounded border border-border-seam bg-bg-surface px-2 py-1 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value as AdminUserStatus | 'all')}\n >\n <option value=\"all\">All Status</option>\n <option value=\"active\">Active</option>\n <option value=\"suspended\">Suspended</option>\n <option value=\"pending\">Pending</option>\n <option value=\"banned\">Banned</option>\n </select>\n </div>\n </div>\n\n {/* Bulk actions */}\n {selectedIds.size > 0 && (\n <div className=\"flex items-center gap-3 rounded-lg bg-accent-soft border border-border-seam p-3\">\n <span className=\"text-sm font-medium\">{selectedIds.size} user(s) selected</span>\n <Button variant=\"outline\" size=\"sm\">\n Activate\n </Button>\n <Button variant=\"outline\" size=\"sm\">\n Suspend\n </Button>\n <Button variant=\"destructive\" size=\"sm\">\n Ban\n </Button>\n </div>\n )}\n\n {/* User table */}\n <Card className=\"border-border-seam\">\n <CardContent className=\"pt-4\">\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\">\n <input\n type=\"checkbox\"\n checked={selectedIds.size === filtered.length && filtered.length > 0}\n onChange={toggleSelectAll}\n className=\"rounded\"\n />\n </th>\n <th className=\"p-3\">User</th>\n <th className=\"p-3\">Role</th>\n <th className=\"p-3\">Status</th>\n <th className=\"p-3\">Verified</th>\n <th className=\"p-3\">Joined</th>\n <th className=\"p-3\">Last Active</th>\n <th className=\"p-3 text-right\">Bookings</th>\n <th className=\"p-3 text-right\">Spent</th>\n <th className=\"p-3\">Rating</th>\n <th className=\"p-3 text-right\">Actions</th>\n </tr>\n </thead>\n <tbody>\n {filtered.length === 0 ? (\n <tr>\n <td colSpan={11} className=\"p-4\">\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No users found\"\n description=\"No users match your current filters.\"\n />\n </td>\n </tr>\n ) : (\n filtered.map((user) => {\n const sCfg = statusConfig[user.status];\n const StatusIcon = sCfg.icon;\n return (\n <tr key={user.id} className=\"border-b border-border-seam transition-colors hover:bg-bg-sunken/50\">\n <td className=\"p-3\">\n <input\n type=\"checkbox\"\n aria-label={`Select ${user.name}`}\n checked={selectedIds.has(user.id)}\n onChange={() => toggleSelect(user.id)}\n className=\"rounded\"\n />\n </td>\n <td className=\"p-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-8 w-8 items-center justify-center rounded-full bg-accent-soft text-xs font-semibold text-text-primary\">\n {user.name.charAt(0)}\n </div>\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 </td>\n <td className=\"p-3\">\n <Badge variant={roleConfig[user.role].variant}>\n {roleConfig[user.role].label}\n </Badge>\n </td>\n <td className=\"p-3\">\n <div className=\"flex items-center gap-1\">\n <StatusIcon className={`h-4 w-4 ${sCfg.color}`} />\n <span className={`text-xs font-medium ${sCfg.color}`}>\n {sCfg.label}\n </span>\n </div>\n </td>\n <td className=\"p-3\">\n {user.verified ? (\n <CheckCircle2 className=\"h-4 w-4 text-status-success-text\" />\n ) : (\n <XCircle className=\"h-4 w-4 text-text-muted\" />\n )}\n </td>\n <td className=\"p-3 text-text-secondary\">{user.joinedDate}</td>\n <td className=\"p-3 text-text-secondary\">{user.lastActive}</td>\n <td className=\"p-3 text-right tabular-nums\">{user.bookingsCount}</td>\n <td className=\"p-3 text-right tabular-nums\">\n {user.totalSpent > 0 ? `$${user.totalSpent.toLocaleString()}` : '-'}\n </td>\n <td className=\"p-3\">\n {user.rating ? (\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 fill-status-warning-text text-status-warning-text\" />\n <span className=\"text-xs font-medium tabular-nums\">{user.rating}</span>\n </div>\n ) : (\n <span className=\"text-xs text-text-secondary\">-</span>\n )}\n </td>\n <td className=\"p-3 text-right\">\n <div className=\"flex justify-end gap-1\">\n <Button variant=\"outline\" size=\"sm\" aria-label={`Email ${user.name}`}>\n <Mail className=\"h-3 w-3\" />\n </Button>\n <Button variant=\"outline\" size=\"sm\" aria-label={`More actions for ${user.name}`}>\n <MoreHorizontal className=\"h-3 w-3\" />\n </Button>\n </div>\n </td>\n </tr>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n\n {/* Pagination hint */}\n <div className=\"mt-4 flex items-center justify-between border-t border-border-seam pt-4 text-sm text-text-secondary\">\n <span>\n Showing {filtered.length} of {adminUsers.length} users\n </span>\n <div className=\"flex gap-1\">\n <Button variant=\"outline\" size=\"sm\" disabled>\n Previous\n </Button>\n <Button variant=\"outline\" size=\"sm\" disabled>\n Next\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AdminUsersPage.js","names":[],"sources":["../../../src/proserviceworld/pages/AdminUsersPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n Input,\n GlassCard,\n AuroraBackground,\n GradientText,\n CTAOverflowMenu,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Search,\n Filter,\n Users,\n UserCheck,\n Shield,\n Star,\n CheckCircle2,\n XCircle,\n Clock,\n Ban,\n MoreHorizontal,\n Download,\n Plus,\n Mail,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\nimport type { AdminUserRole, AdminUserStatus } from '../types';\n\nconst roleConfig: Record<\n AdminUserRole,\n { label: string; variant: 'default' | 'secondary' | 'outline' | 'destructive' }\n> = {\n client: { label: 'Client', variant: 'secondary' },\n provider: { label: 'Provider', variant: 'default' },\n admin: { label: 'Admin', variant: 'destructive' },\n team_member: { label: 'Team', variant: 'outline' },\n};\n\nconst statusConfig: Record<\n AdminUserStatus,\n { label: string; icon: typeof CheckCircle2; color: string }\n> = {\n active: { label: 'Active', icon: CheckCircle2, color: 'text-status-success-text' },\n suspended: { label: 'Suspended', icon: Ban, color: 'text-status-warning-text' },\n pending: { label: 'Pending', icon: Clock, color: 'text-status-warning-text' },\n banned: { label: 'Banned', icon: XCircle, color: 'text-status-error-text' },\n};\n\nexport function AdminUsersPage() {\n const { adminUsers } = useProServiceWorldStore();\n const [searchQuery, setSearchQuery] = useState('');\n const [roleFilter, setRoleFilter] = useState<AdminUserRole | 'all'>('all');\n const [statusFilter, setStatusFilter] = useState<AdminUserStatus | 'all'>('all');\n const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());\n\n const filtered = useMemo(() => {\n let result = [...adminUsers];\n if (roleFilter !== 'all') result = result.filter((u) => u.role === roleFilter);\n if (statusFilter !== 'all') result = result.filter((u) => u.status === statusFilter);\n if (searchQuery.trim()) {\n const q = searchQuery.toLowerCase();\n result = result.filter(\n (u) => u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q)\n );\n }\n return result;\n }, [adminUsers, roleFilter, statusFilter, searchQuery]);\n\n // Stats\n const totalUsers = adminUsers.length;\n const activeUsers = adminUsers.filter((u) => u.status === 'active').length;\n const providers = adminUsers.filter((u) => u.role === 'provider').length;\n const verifiedUsers = adminUsers.filter((u) => u.verified).length;\n\n const toggleSelect = (id: string) => {\n const next = new Set(selectedIds);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n setSelectedIds(next);\n };\n\n const toggleSelectAll = () => {\n if (selectedIds.size === filtered.length) setSelectedIds(new Set());\n else setSelectedIds(new Set(filtered.map((u) => u.id)));\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 MFE */}\n <div className=\"relative overflow-hidden rounded-card -mx-2 px-2 pt-1 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <AuroraBackground intensity=\"subtle\" />\n <div>\n <h1 className=\"text-2xl font-semibold tracking-tight\">\n <GradientText>User Management</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Admin panel to manage platform users, roles, and access.\n </p>\n </div>\n <CTAOverflowMenu\n primary={{\n label: 'Invite User',\n onSelect: () => {},\n icon: <Plus className=\"h-4 w-4\" />,\n }}\n actions={[\n {\n label: 'Export',\n onSelect: () => {},\n icon: <Download className=\"h-4 w-4\" />,\n },\n ]}\n />\n </div>\n\n <PagePurpose icon={<Users className=\"h-4 w-4\" />}>\n The control room for everyone on the platform. Search, filter by role and status, and\n review each user's activity and spend at a glance — then invite new members or take\n bulk action to activate, suspend, or ban accounts as needed.\n </PagePurpose>\n\n {/* Stats */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-status-info-bg-subtle p-2\">\n <Users className=\"h-5 w-5 text-accent-blue\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{totalUsers}</p>\n <p className=\"text-xs text-text-secondary\">Total Users</p>\n </div>\n </div>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-status-success-bg-subtle p-2\">\n <UserCheck className=\"h-5 w-5 text-status-success-text\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{activeUsers}</p>\n <p className=\"text-xs text-text-secondary\">Active Users</p>\n </div>\n </div>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-accent-purple-subtle p-2\">\n <Shield className=\"h-5 w-5 text-accent-purple\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{providers}</p>\n <p className=\"text-xs text-text-secondary\">Providers</p>\n </div>\n </div>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-status-warning-bg-subtle p-2\">\n <CheckCircle2 className=\"h-5 w-5 text-status-warning-text\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{verifiedUsers}</p>\n <p className=\"text-xs text-text-secondary\">Verified</p>\n </div>\n </div>\n </GlassCard>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search by name or email...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"pl-10\"\n />\n </div>\n <div className=\"flex items-center gap-2\">\n <Filter className=\"h-4 w-4 text-text-secondary\" />\n <select\n className=\"rounded border border-border-seam bg-bg-surface px-2 py-1 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n value={roleFilter}\n onChange={(e) => setRoleFilter(e.target.value as AdminUserRole | 'all')}\n >\n <option value=\"all\">All Roles</option>\n <option value=\"client\">Client</option>\n <option value=\"provider\">Provider</option>\n <option value=\"admin\">Admin</option>\n <option value=\"team_member\">Team Member</option>\n </select>\n <select\n className=\"rounded border border-border-seam bg-bg-surface px-2 py-1 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus\"\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value as AdminUserStatus | 'all')}\n >\n <option value=\"all\">All Status</option>\n <option value=\"active\">Active</option>\n <option value=\"suspended\">Suspended</option>\n <option value=\"pending\">Pending</option>\n <option value=\"banned\">Banned</option>\n </select>\n </div>\n </div>\n\n {/* Bulk actions */}\n {selectedIds.size > 0 && (\n <div className=\"flex items-center gap-3 rounded-lg bg-accent-soft border border-border-seam p-3\">\n <span className=\"text-sm font-medium\">{selectedIds.size} user(s) selected</span>\n <Button variant=\"outline\" size=\"sm\">\n Activate\n </Button>\n <Button variant=\"outline\" size=\"sm\">\n Suspend\n </Button>\n <Button variant=\"destructive\" size=\"sm\">\n Ban\n </Button>\n </div>\n )}\n\n {/* User table */}\n <Card className=\"border-border-seam\">\n <CardContent className=\"pt-4\">\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\">\n <input\n type=\"checkbox\"\n checked={selectedIds.size === filtered.length && filtered.length > 0}\n onChange={toggleSelectAll}\n className=\"rounded\"\n />\n </th>\n <th className=\"p-3\">User</th>\n <th className=\"p-3\">Role</th>\n <th className=\"p-3\">Status</th>\n <th className=\"p-3\">Verified</th>\n <th className=\"p-3\">Joined</th>\n <th className=\"p-3\">Last Active</th>\n <th className=\"p-3 text-right\">Bookings</th>\n <th className=\"p-3 text-right\">Spent</th>\n <th className=\"p-3\">Rating</th>\n <th className=\"p-3 text-right\">Actions</th>\n </tr>\n </thead>\n <tbody>\n {filtered.length === 0 ? (\n <tr>\n <td colSpan={11} className=\"p-4\">\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No users found\"\n description=\"No users match your current filters.\"\n />\n </td>\n </tr>\n ) : (\n filtered.map((user) => {\n const sCfg = statusConfig[user.status];\n const StatusIcon = sCfg.icon;\n return (\n <tr key={user.id} className=\"border-b border-border-seam transition-colors hover:bg-bg-sunken/50\">\n <td className=\"p-3\">\n <input\n type=\"checkbox\"\n aria-label={`Select ${user.name}`}\n checked={selectedIds.has(user.id)}\n onChange={() => toggleSelect(user.id)}\n className=\"rounded\"\n />\n </td>\n <td className=\"p-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-8 w-8 items-center justify-center rounded-full bg-accent-soft text-xs font-semibold text-text-primary\">\n {user.name.charAt(0)}\n </div>\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 </td>\n <td className=\"p-3\">\n <Badge variant={roleConfig[user.role].variant}>\n {roleConfig[user.role].label}\n </Badge>\n </td>\n <td className=\"p-3\">\n <div className=\"flex items-center gap-1\">\n <StatusIcon className={`h-4 w-4 ${sCfg.color}`} />\n <span className={`text-xs font-medium ${sCfg.color}`}>\n {sCfg.label}\n </span>\n </div>\n </td>\n <td className=\"p-3\">\n {user.verified ? (\n <CheckCircle2 className=\"h-4 w-4 text-status-success-text\" />\n ) : (\n <XCircle className=\"h-4 w-4 text-text-muted\" />\n )}\n </td>\n <td className=\"p-3 text-text-secondary\">{user.joinedDate}</td>\n <td className=\"p-3 text-text-secondary\">{user.lastActive}</td>\n <td className=\"p-3 text-right tabular-nums\">{user.bookingsCount}</td>\n <td className=\"p-3 text-right tabular-nums\">\n {user.totalSpent > 0 ? `$${user.totalSpent.toLocaleString()}` : '-'}\n </td>\n <td className=\"p-3\">\n {user.rating ? (\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 fill-status-warning-text text-status-warning-text\" />\n <span className=\"text-xs font-medium tabular-nums\">{user.rating}</span>\n </div>\n ) : (\n <span className=\"text-xs text-text-secondary\">-</span>\n )}\n </td>\n <td className=\"p-3 text-right\">\n <div className=\"flex justify-end gap-1\">\n <Button variant=\"outline\" size=\"sm\" aria-label={`Email ${user.name}`}>\n <Mail className=\"h-3 w-3\" />\n </Button>\n <Button variant=\"outline\" size=\"sm\" aria-label={`More actions for ${user.name}`}>\n <MoreHorizontal className=\"h-3 w-3\" />\n </Button>\n </div>\n </td>\n </tr>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n\n {/* Pagination hint */}\n <div className=\"mt-4 flex items-center justify-between border-t border-border-seam pt-4 text-sm text-text-secondary\">\n <span>\n Showing {filtered.length} of {adminUsers.length} users\n </span>\n <div className=\"flex gap-1\">\n <Button variant=\"outline\" size=\"sm\" disabled>\n Previous\n </Button>\n <Button variant=\"outline\" size=\"sm\" disabled>\n Next\n </Button>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -2,10 +2,10 @@ import { useProServiceWorldStore as e } from "../store/proserviceworldStore.js";
|
|
|
2
2
|
import { useState as t } from "react";
|
|
3
3
|
import { Activity as n, ArrowDownRight as r, ArrowUpRight as i, BarChart3 as a, Calendar as o, DollarSign as s, Download as c, Globe as l, Minus as u, TrendingDown as d, TrendingUp as f, Users as p } from "lucide-react";
|
|
4
4
|
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
5
|
-
import { AuroraBackground as g, Badge as _, Button as v, Card as y, CardContent as b, CardHeader as x, CardTitle as S, EmphasisPanel as C, GlassCard as w, GradientText as T } from "@burdenoff/fe-libs/ui";
|
|
5
|
+
import { AuroraBackground as g, Badge as _, Button as v, Card as y, CardContent as b, CardHeader as x, CardTitle as S, EmphasisPanel as C, GlassCard as w, GradientText as T, NextSteps as E, PagePurpose as D } from "@burdenoff/fe-libs/ui";
|
|
6
6
|
//#region src/proserviceworld/pages/AnalyticsPage.tsx
|
|
7
|
-
function
|
|
8
|
-
let { analyticsMetrics:
|
|
7
|
+
function O() {
|
|
8
|
+
let { analyticsMetrics: O, revenueData: k, clientData: A, servicePerformance: j, geographicData: M } = e(), [N, P] = t("overview"), [F, I] = t("6m"), L = [
|
|
9
9
|
{
|
|
10
10
|
key: "overview",
|
|
11
11
|
label: "Overview",
|
|
@@ -31,7 +31,7 @@ function E() {
|
|
|
31
31
|
label: "Performance",
|
|
32
32
|
icon: f
|
|
33
33
|
}
|
|
34
|
-
],
|
|
34
|
+
], R = (e) => e === "positive" ? /* @__PURE__ */ m(f, { className: "h-4 w-4 text-status-success-text" }) : e === "negative" ? /* @__PURE__ */ m(d, { className: "h-4 w-4 text-status-error-text" }) : /* @__PURE__ */ m(u, { className: "h-4 w-4 text-text-muted" }), z = Math.max(...k.map((e) => e.value)), B = Math.max(...A.map((e) => e.value)), V = Math.max(...j.map((e) => e.value));
|
|
35
35
|
return /* @__PURE__ */ h("div", {
|
|
36
36
|
className: "space-y-6 p-6 bg-bg-canvas min-h-screen",
|
|
37
37
|
children: [
|
|
@@ -55,9 +55,9 @@ function E() {
|
|
|
55
55
|
"6m",
|
|
56
56
|
"1y"
|
|
57
57
|
].map((e) => /* @__PURE__ */ m("button", {
|
|
58
|
-
onClick: () =>
|
|
59
|
-
"aria-pressed":
|
|
60
|
-
className: `px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${
|
|
58
|
+
onClick: () => I(e),
|
|
59
|
+
"aria-pressed": F === e,
|
|
60
|
+
className: `px-3 py-1 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${F === e ? "bg-action-primary-bg text-action-primary-text" : "text-text-secondary hover:bg-bg-sunken/50"}`,
|
|
61
61
|
children: e.toUpperCase()
|
|
62
62
|
}, e))
|
|
63
63
|
}), /* @__PURE__ */ h(v, {
|
|
@@ -68,18 +68,48 @@ function E() {
|
|
|
68
68
|
})]
|
|
69
69
|
})]
|
|
70
70
|
}),
|
|
71
|
+
/* @__PURE__ */ m(D, {
|
|
72
|
+
icon: /* @__PURE__ */ m(a, { className: "h-4 w-4" }),
|
|
73
|
+
children: "Understand how your professional services are performing. Track revenue and client growth over time, see which services and regions drive the most demand, and export the numbers — so you can double down on what works and plan where to grow next."
|
|
74
|
+
}),
|
|
75
|
+
/* @__PURE__ */ m(E, {
|
|
76
|
+
storageKey: "proserviceworld-analytics",
|
|
77
|
+
steps: [
|
|
78
|
+
{
|
|
79
|
+
id: "revenue",
|
|
80
|
+
label: "Inspect revenue performance",
|
|
81
|
+
description: "Open the Revenue tab to see month-over-month growth.",
|
|
82
|
+
icon: /* @__PURE__ */ m(s, { className: "h-4 w-4" }),
|
|
83
|
+
onClick: () => P("revenue")
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "services",
|
|
87
|
+
label: "Find your top services",
|
|
88
|
+
description: "See which services attract the most bookings.",
|
|
89
|
+
icon: /* @__PURE__ */ m(n, { className: "h-4 w-4" }),
|
|
90
|
+
onClick: () => P("services")
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "clients",
|
|
94
|
+
label: "Track client acquisition",
|
|
95
|
+
description: "Review client growth and geographic reach.",
|
|
96
|
+
icon: /* @__PURE__ */ m(p, { className: "h-4 w-4" }),
|
|
97
|
+
onClick: () => P("clients")
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}),
|
|
71
101
|
/* @__PURE__ */ m("div", {
|
|
72
102
|
className: "flex flex-wrap gap-1 border-b border-border-seam",
|
|
73
|
-
children:
|
|
74
|
-
onClick: () =>
|
|
75
|
-
"aria-pressed":
|
|
76
|
-
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 ${
|
|
103
|
+
children: L.map(({ key: e, label: t, icon: n }) => /* @__PURE__ */ h("button", {
|
|
104
|
+
onClick: () => P(e),
|
|
105
|
+
"aria-pressed": N === e,
|
|
106
|
+
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 === e ? "border-action-primary-bg text-text-primary" : "border-transparent text-text-secondary hover:text-text-primary"}`,
|
|
77
107
|
children: [/* @__PURE__ */ m(n, { className: "h-4 w-4" }), t]
|
|
78
108
|
}, e))
|
|
79
109
|
}),
|
|
80
110
|
/* @__PURE__ */ m("div", {
|
|
81
111
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-5",
|
|
82
|
-
children:
|
|
112
|
+
children: O.map((e, t) => /* @__PURE__ */ h(w, {
|
|
83
113
|
treatment: t === 0 ? "glass" : "metric",
|
|
84
114
|
glow: t === 0,
|
|
85
115
|
className: "border-border-seam",
|
|
@@ -89,7 +119,7 @@ function E() {
|
|
|
89
119
|
children: [/* @__PURE__ */ m("p", {
|
|
90
120
|
className: "text-xs font-medium text-text-secondary",
|
|
91
121
|
children: e.label
|
|
92
|
-
}),
|
|
122
|
+
}), R(e.changeType)]
|
|
93
123
|
}),
|
|
94
124
|
/* @__PURE__ */ m("p", {
|
|
95
125
|
className: "mt-1 text-2xl font-bold tabular-nums",
|
|
@@ -102,7 +132,7 @@ function E() {
|
|
|
102
132
|
]
|
|
103
133
|
}, t))
|
|
104
134
|
}),
|
|
105
|
-
|
|
135
|
+
N === "overview" && /* @__PURE__ */ h("div", {
|
|
106
136
|
className: "grid gap-6 lg:grid-cols-2",
|
|
107
137
|
children: [/* @__PURE__ */ h(C, { children: [/* @__PURE__ */ h("div", {
|
|
108
138
|
className: "flex items-center justify-between mb-4",
|
|
@@ -115,7 +145,7 @@ function E() {
|
|
|
115
145
|
})]
|
|
116
146
|
}), /* @__PURE__ */ m("div", {
|
|
117
147
|
className: "space-y-3",
|
|
118
|
-
children:
|
|
148
|
+
children: k.map((e) => /* @__PURE__ */ h("div", {
|
|
119
149
|
className: "flex items-center gap-3",
|
|
120
150
|
children: [
|
|
121
151
|
/* @__PURE__ */ m("span", {
|
|
@@ -126,10 +156,10 @@ function E() {
|
|
|
126
156
|
className: "relative flex-1 h-6 bg-bg-sunken rounded overflow-hidden",
|
|
127
157
|
children: [e.prev !== void 0 && /* @__PURE__ */ m("div", {
|
|
128
158
|
className: "absolute h-6 bg-text-muted/15 rounded",
|
|
129
|
-
style: { width: `${e.prev /
|
|
159
|
+
style: { width: `${e.prev / z * 100}%` }
|
|
130
160
|
}), /* @__PURE__ */ m("div", {
|
|
131
161
|
className: "relative h-6 bg-action-primary-bg rounded shadow-[var(--shadow-glow)]",
|
|
132
|
-
style: { width: `${e.value /
|
|
162
|
+
style: { width: `${e.value / z * 100}%` }
|
|
133
163
|
})]
|
|
134
164
|
}),
|
|
135
165
|
/* @__PURE__ */ h("span", {
|
|
@@ -155,7 +185,7 @@ function E() {
|
|
|
155
185
|
})]
|
|
156
186
|
}) }), /* @__PURE__ */ m(b, {
|
|
157
187
|
className: "space-y-3",
|
|
158
|
-
children:
|
|
188
|
+
children: A.map((e) => /* @__PURE__ */ h("div", {
|
|
159
189
|
className: "flex items-center gap-3",
|
|
160
190
|
children: [
|
|
161
191
|
/* @__PURE__ */ m("span", {
|
|
@@ -166,7 +196,7 @@ function E() {
|
|
|
166
196
|
className: "flex-1 h-6 bg-bg-sunken rounded overflow-hidden",
|
|
167
197
|
children: /* @__PURE__ */ m("div", {
|
|
168
198
|
className: "h-6 bg-accent-blue rounded",
|
|
169
|
-
style: { width: `${e.value /
|
|
199
|
+
style: { width: `${e.value / B * 100}%` }
|
|
170
200
|
})
|
|
171
201
|
}),
|
|
172
202
|
/* @__PURE__ */ m("span", {
|
|
@@ -178,7 +208,7 @@ function E() {
|
|
|
178
208
|
})]
|
|
179
209
|
})]
|
|
180
210
|
}),
|
|
181
|
-
|
|
211
|
+
N === "revenue" && /* @__PURE__ */ m("div", {
|
|
182
212
|
className: "space-y-6",
|
|
183
213
|
children: /* @__PURE__ */ h(y, {
|
|
184
214
|
className: "border-border-seam",
|
|
@@ -187,7 +217,7 @@ function E() {
|
|
|
187
217
|
children: "Revenue Breakdown"
|
|
188
218
|
}) }), /* @__PURE__ */ m(b, { children: /* @__PURE__ */ m("div", {
|
|
189
219
|
className: "space-y-4",
|
|
190
|
-
children:
|
|
220
|
+
children: k.map((e) => {
|
|
191
221
|
let t = e.prev ? ((e.value - e.prev) / e.prev * 100).toFixed(1) : "0", n = e.prev ? e.value >= e.prev : !0;
|
|
192
222
|
return /* @__PURE__ */ h("div", {
|
|
193
223
|
className: "flex items-center justify-between rounded-lg border border-border-seam p-4 transition-shadow hover:shadow-pop",
|
|
@@ -230,7 +260,7 @@ function E() {
|
|
|
230
260
|
}) })]
|
|
231
261
|
})
|
|
232
262
|
}),
|
|
233
|
-
|
|
263
|
+
N === "clients" && /* @__PURE__ */ h("div", {
|
|
234
264
|
className: "space-y-6",
|
|
235
265
|
children: [/* @__PURE__ */ h(y, {
|
|
236
266
|
className: "border-border-seam",
|
|
@@ -239,8 +269,8 @@ function E() {
|
|
|
239
269
|
children: "Client Acquisition"
|
|
240
270
|
}) }), /* @__PURE__ */ m(b, {
|
|
241
271
|
className: "space-y-3",
|
|
242
|
-
children:
|
|
243
|
-
let n = t > 0 ?
|
|
272
|
+
children: A.map((e, t) => {
|
|
273
|
+
let n = t > 0 ? A[t - 1]?.value ?? 0 : 0, r = n > 0 ? ((e.value - n) / n * 100).toFixed(0) : "N/A";
|
|
244
274
|
return /* @__PURE__ */ h("div", {
|
|
245
275
|
className: "flex items-center gap-4 rounded-lg border border-border-seam p-3",
|
|
246
276
|
children: [/* @__PURE__ */ h("div", {
|
|
@@ -258,7 +288,7 @@ function E() {
|
|
|
258
288
|
className: "mt-2 h-2 overflow-hidden rounded-full bg-bg-sunken",
|
|
259
289
|
children: /* @__PURE__ */ m("div", {
|
|
260
290
|
className: "h-2 rounded-full bg-accent-blue",
|
|
261
|
-
style: { width: `${e.value /
|
|
291
|
+
style: { width: `${e.value / B * 100}%` }
|
|
262
292
|
})
|
|
263
293
|
})]
|
|
264
294
|
}), /* @__PURE__ */ m(_, {
|
|
@@ -301,8 +331,8 @@ function E() {
|
|
|
301
331
|
children: "Distribution"
|
|
302
332
|
})
|
|
303
333
|
]
|
|
304
|
-
}) }), /* @__PURE__ */ m("tbody", { children:
|
|
305
|
-
let t =
|
|
334
|
+
}) }), /* @__PURE__ */ m("tbody", { children: M.map((e) => {
|
|
335
|
+
let t = M.reduce((e, t) => e + t.clients, 0), n = Math.round(e.clients / t * 100);
|
|
306
336
|
return /* @__PURE__ */ h("tr", {
|
|
307
337
|
className: "border-b border-border-seam transition-colors hover:bg-bg-sunken/50",
|
|
308
338
|
children: [
|
|
@@ -345,14 +375,14 @@ function E() {
|
|
|
345
375
|
}) })]
|
|
346
376
|
})]
|
|
347
377
|
}),
|
|
348
|
-
|
|
378
|
+
N === "services" && /* @__PURE__ */ h(y, {
|
|
349
379
|
className: "border-border-seam",
|
|
350
380
|
children: [/* @__PURE__ */ m(x, { children: /* @__PURE__ */ m(S, {
|
|
351
381
|
className: "text-base",
|
|
352
382
|
children: "Service Performance (Total Bookings)"
|
|
353
383
|
}) }), /* @__PURE__ */ m(b, {
|
|
354
384
|
className: "space-y-3",
|
|
355
|
-
children: [...
|
|
385
|
+
children: [...j].sort((e, t) => t.value - e.value).map((e, t) => /* @__PURE__ */ h("div", {
|
|
356
386
|
className: "flex items-center gap-4",
|
|
357
387
|
children: [/* @__PURE__ */ h("span", {
|
|
358
388
|
className: "w-6 text-sm font-medium text-text-secondary tabular-nums",
|
|
@@ -372,14 +402,14 @@ function E() {
|
|
|
372
402
|
className: "mt-1 h-3 overflow-hidden rounded-full bg-bg-sunken",
|
|
373
403
|
children: /* @__PURE__ */ m("div", {
|
|
374
404
|
className: "h-3 rounded-full bg-action-primary-bg transition-all",
|
|
375
|
-
style: { width: `${e.value /
|
|
405
|
+
style: { width: `${e.value / V * 100}%` }
|
|
376
406
|
})
|
|
377
407
|
})]
|
|
378
408
|
})]
|
|
379
409
|
}, e.name))
|
|
380
410
|
})]
|
|
381
411
|
}),
|
|
382
|
-
|
|
412
|
+
N === "performance" && /* @__PURE__ */ h("div", {
|
|
383
413
|
className: "grid gap-6 lg:grid-cols-2",
|
|
384
414
|
children: [/* @__PURE__ */ h(y, {
|
|
385
415
|
className: "border-border-seam",
|
|
@@ -388,7 +418,7 @@ function E() {
|
|
|
388
418
|
children: "Monthly Revenue vs Previous"
|
|
389
419
|
}) }), /* @__PURE__ */ h(b, {
|
|
390
420
|
className: "space-y-3",
|
|
391
|
-
children: [
|
|
421
|
+
children: [k.map((e) => /* @__PURE__ */ h("div", {
|
|
392
422
|
className: "space-y-1",
|
|
393
423
|
children: [/* @__PURE__ */ h("div", {
|
|
394
424
|
className: "flex items-center justify-between text-xs",
|
|
@@ -409,10 +439,10 @@ function E() {
|
|
|
409
439
|
className: "flex gap-1",
|
|
410
440
|
children: [/* @__PURE__ */ m("div", {
|
|
411
441
|
className: "h-4 rounded bg-action-primary-bg/30",
|
|
412
|
-
style: { width: `${(e.prev ?? 0) /
|
|
442
|
+
style: { width: `${(e.prev ?? 0) / z * 100}%` }
|
|
413
443
|
}), /* @__PURE__ */ m("div", {
|
|
414
444
|
className: "h-4 rounded bg-action-primary-bg",
|
|
415
|
-
style: { width: `${e.value /
|
|
445
|
+
style: { width: `${e.value / z * 100 - (e.prev ?? 0) / z * 100}%` }
|
|
416
446
|
})]
|
|
417
447
|
})]
|
|
418
448
|
}, e.name)), /* @__PURE__ */ h("div", {
|
|
@@ -433,7 +463,7 @@ function E() {
|
|
|
433
463
|
children: "Top Performing Services"
|
|
434
464
|
}) }), /* @__PURE__ */ m(b, { children: /* @__PURE__ */ m("div", {
|
|
435
465
|
className: "space-y-3",
|
|
436
|
-
children: [...
|
|
466
|
+
children: [...j].sort((e, t) => t.value - e.value).slice(0, 5).map((e, t) => /* @__PURE__ */ h("div", {
|
|
437
467
|
className: "flex items-center justify-between rounded-lg border border-border-seam p-3",
|
|
438
468
|
children: [/* @__PURE__ */ h("div", {
|
|
439
469
|
className: "flex items-center gap-3",
|
|
@@ -456,6 +486,6 @@ function E() {
|
|
|
456
486
|
});
|
|
457
487
|
}
|
|
458
488
|
//#endregion
|
|
459
|
-
export {
|
|
489
|
+
export { O as AnalyticsPage };
|
|
460
490
|
|
|
461
491
|
//# sourceMappingURL=AnalyticsPage.js.map
|