@burdenoff/microfe-proserviceworld 2026.624.1 → 2026.625.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/proserviceworld/pages/AdminReportsPage.js +365 -332
- package/dist/proserviceworld/pages/AdminReportsPage.js.map +1 -1
- package/dist/proserviceworld/pages/AdminUsersPage.js +257 -237
- package/dist/proserviceworld/pages/AdminUsersPage.js.map +1 -1
- package/dist/proserviceworld/pages/AnalyticsPage.js +343 -315
- package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
- package/dist/proserviceworld/pages/BookingsPage.js +191 -174
- package/dist/proserviceworld/pages/BookingsPage.js.map +1 -1
- package/dist/proserviceworld/pages/DashboardPage.js +383 -346
- package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
- package/dist/proserviceworld/pages/DocumentsPage.js +383 -318
- package/dist/proserviceworld/pages/DocumentsPage.js.map +1 -1
- package/dist/proserviceworld/pages/MessagesPage.js +70 -75
- package/dist/proserviceworld/pages/MessagesPage.js.map +1 -1
- package/dist/proserviceworld/pages/PaymentsPage.js +367 -340
- package/dist/proserviceworld/pages/PaymentsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ProjectsPage.js +428 -381
- package/dist/proserviceworld/pages/ProjectsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ProviderDetailPage.js +390 -346
- package/dist/proserviceworld/pages/ProviderDetailPage.js.map +1 -1
- package/dist/proserviceworld/pages/ProvidersPage.js +245 -244
- package/dist/proserviceworld/pages/ProvidersPage.js.map +1 -1
- package/dist/proserviceworld/pages/ReviewsPage.js +14 -14
- package/dist/proserviceworld/pages/ReviewsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ServiceDetailsPage.js +338 -305
- package/dist/proserviceworld/pages/ServiceDetailsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ServicesPage.js +156 -153
- package/dist/proserviceworld/pages/ServicesPage.js.map +1 -1
- package/dist/proserviceworld/pages/VerificationPage.js +260 -251
- package/dist/proserviceworld/pages/VerificationPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/AnalyticsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Badge, Button, Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport {\n TrendingUp,\n TrendingDown,\n Minus,\n DollarSign,\n Users,\n BarChart3,\n Activity,\n Globe,\n Calendar,\n ArrowUpRight,\n ArrowDownRight,\n Download,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\n\ntype AnalyticsTab = 'overview' | 'revenue' | 'clients' | 'services' | 'performance';\n\nexport function AnalyticsPage() {\n const { analyticsMetrics, revenueData, clientData, servicePerformance, geographicData } =\n useProServiceWorldStore();\n const [activeTab, setActiveTab] = useState<AnalyticsTab>('overview');\n const [timeRange, setTimeRange] = useState('6m');\n\n const tabs: { key: AnalyticsTab; label: string; icon: typeof BarChart3 }[] = [\n { key: 'overview', label: 'Overview', icon: BarChart3 },\n { key: 'revenue', label: 'Revenue', icon: DollarSign },\n { key: 'clients', label: 'Clients', icon: Users },\n { key: 'services', label: 'Services', icon: Activity },\n { key: 'performance', label: 'Performance', icon: TrendingUp },\n ];\n\n const changeIcon = (type: string) => {\n if (type === 'positive') return <TrendingUp className=\"h-4 w-4 text-green-500\" />;\n if (type === 'negative') return <TrendingDown className=\"h-4 w-4 text-red-500\" />;\n return <Minus className=\"h-4 w-4 text-gray-400\" />;\n };\n\n const maxRevenue = Math.max(...revenueData.map((d) => d.value));\n const maxClients = Math.max(...clientData.map((d) => d.value));\n const maxServiceBookings = Math.max(...servicePerformance.map((d) => d.value));\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\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\">Analytics</h1>\n <p className=\"text-sm text-text-secondary\">\n Insights and performance metrics for your professional services.\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex rounded border\">\n {['1m', '3m', '6m', '1y'].map((range) => (\n <button\n key={range}\n onClick={() => setTimeRange(range)}\n className={`px-3 py-1 text-xs font-medium ${\n timeRange === range\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'text-text-secondary hover:bg-bg-sunken/50'\n }`}\n >\n {range.toUpperCase()}\n </button>\n ))}\n </div>\n <Button variant=\"outline\" size=\"sm\">\n <Download className=\"mr-2 h-4 w-4\" /> Export\n </Button>\n </div>\n </div>\n\n {/* Tabs */}\n <div className=\"flex gap-1 border-b\">\n {tabs.map(({ key, label, icon: Icon }) => (\n <button\n key={key}\n onClick={() => setActiveTab(key)}\n className={`flex items-center gap-2 border-b-2 px-4 py-2 text-sm font-medium transition-colors ${\n activeTab === key\n ? 'border-primary text-primary'\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\n {/* Metric Cards (shown on all tabs) */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-5\">\n {analyticsMetrics.map((metric, i) => (\n <Card key={i}>\n <CardContent className=\"pt-4\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-xs font-medium text-text-secondary\">{metric.label}</p>\n {changeIcon(metric.changeType)}\n </div>\n <p className=\"mt-1 text-2xl font-bold\">{metric.value}</p>\n <p\n className={`mt-0.5 text-xs ${\n metric.changeType === 'positive'\n ? 'text-green-600'\n : metric.changeType === 'negative'\n ? 'text-red-600'\n : 'text-gray-500'\n }`}\n >\n {metric.change} from last period\n </p>\n </CardContent>\n </Card>\n ))}\n </div>\n\n {/* Overview Tab */}\n {activeTab === 'overview' && (\n <div className=\"grid gap-6 lg:grid-cols-2\">\n {/* Revenue chart */}\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">Revenue Trend</CardTitle>\n <Badge variant=\"secondary\">Last 6 months</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=\"relative flex-1 h-6 bg-bg-sunken rounded overflow-hidden\">\n {point.prev !== undefined && (\n <div\n className=\"absolute h-6 bg-muted-foreground/10 rounded\"\n style={{ width: `${(point.prev / maxRevenue) * 100}%` }}\n />\n )}\n <div\n className=\"relative h-6 bg-action-primary-bg rounded\"\n style={{ width: `${(point.value / maxRevenue) * 100}%` }}\n />\n </div>\n <span className=\"w-20 text-right text-xs font-medium\">\n ${(point.value / 1000).toFixed(1)}k\n </span>\n </div>\n ))}\n </CardContent>\n </Card>\n\n {/* Client growth */}\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">Client Growth</CardTitle>\n <Badge variant=\"secondary\">Monthly</Badge>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {clientData.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-6 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-6 bg-blue-500 rounded\"\n style={{ width: `${(point.value / maxClients) * 100}%` }}\n />\n </div>\n <span className=\"w-12 text-right text-xs font-medium\">{point.value}</span>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n )}\n\n {/* Revenue Tab */}\n {activeTab === 'revenue' && (\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Revenue Breakdown</CardTitle>\n </CardHeader>\n <CardContent>\n <div 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\n key={point.name}\n className=\"flex items-center justify-between rounded-lg border p-4\"\n >\n <div className=\"flex items-center gap-4\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10\">\n <Calendar className=\"h-5 w-5 text-primary\" />\n </div>\n <div>\n <p className=\"font-medium\">{point.name} 2025</p>\n <p className=\"text-sm text-text-secondary\">\n Previous: ${((point.prev ?? 0) / 1000).toFixed(1)}k\n </p>\n </div>\n </div>\n <div className=\"text-right\">\n <p className=\"text-lg font-bold\">${(point.value / 1000).toFixed(1)}k</p>\n <p\n className={`flex items-center gap-1 text-xs ${isPositive ? 'text-green-600' : 'text-red-600'}`}\n >\n {isPositive ? (\n <ArrowUpRight className=\"h-3 w-3\" />\n ) : (\n <ArrowDownRight className=\"h-3 w-3\" />\n )}\n {growth}%\n </p>\n </div>\n </div>\n );\n })}\n </div>\n </CardContent>\n </Card>\n </div>\n )}\n\n {/* Clients Tab */}\n {activeTab === 'clients' && (\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Client Acquisition</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {clientData.map((point, idx) => {\n const prev = idx > 0 ? (clientData[idx - 1]?.value ?? 0) : 0;\n const growth = prev > 0 ? (((point.value - prev) / prev) * 100).toFixed(0) : 'N/A';\n return (\n <div key={point.name} className=\"flex items-center gap-4 rounded-lg border p-3\">\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between\">\n <span className=\"font-medium\">{point.name}</span>\n <span className=\"text-lg font-bold\">{point.value} clients</span>\n </div>\n <div className=\"mt-2 h-2 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-blue-500\"\n style={{ width: `${(point.value / maxClients) * 100}%` }}\n />\n </div>\n </div>\n <Badge variant=\"secondary\" className=\"text-xs\">\n {typeof growth === 'string' ? growth : `+${growth}%`}\n </Badge>\n </div>\n );\n })}\n </CardContent>\n </Card>\n\n {/* Geographic data */}\n <Card>\n <CardHeader>\n <div className=\"flex items-center gap-2\">\n <Globe className=\"h-5 w-5 text-text-secondary\" />\n <CardTitle className=\"text-base\">Geographic Distribution</CardTitle>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b text-left text-text-secondary\">\n <th className=\"p-3\">Country</th>\n <th className=\"p-3 text-right\">Clients</th>\n <th className=\"p-3 text-right\">Revenue</th>\n <th className=\"p-3\">Distribution</th>\n </tr>\n </thead>\n <tbody>\n {geographicData.map((geo) => {\n const totalClients = geographicData.reduce((s, g) => s + g.clients, 0);\n const pct = Math.round((geo.clients / totalClients) * 100);\n return (\n <tr key={geo.country} className=\"border-b hover:bg-bg-sunken/50\">\n <td className=\"p-3 font-medium\">{geo.country}</td>\n <td className=\"p-3 text-right\">{geo.clients}</td>\n <td className=\"p-3 text-right\">${(geo.revenue / 1000).toFixed(0)}k</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-action-primary-bg\"\n style={{ width: `${pct}%` }}\n />\n </div>\n <span className=\"w-10 text-right text-xs text-text-secondary\">\n {pct}%\n </span>\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </CardContent>\n </Card>\n </div>\n )}\n\n {/* Services Tab */}\n {activeTab === 'services' && (\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Service Performance (Total Bookings)</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-4\">\n <span className=\"w-6 text-sm font-medium text-text-secondary\">#{idx + 1}</span>\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium\">{service.name}</span>\n <span className=\"text-sm font-bold\">{service.value}</span>\n </div>\n <div className=\"mt-1 h-3 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-3 rounded-full bg-action-primary-bg transition-all\"\n style={{ width: `${(service.value / maxServiceBookings) * 100}%` }}\n />\n </div>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n )}\n\n {/* Performance Tab */}\n {activeTab === 'performance' && (\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Monthly Revenue vs Previous</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {revenueData.map((point) => (\n <div key={point.name} className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"font-medium\">{point.name}</span>\n <span className=\"text-text-secondary\">\n ${(point.value / 1000).toFixed(1)}k vs $\n {((point.prev ?? 0) / 1000).toFixed(1)}k\n </span>\n </div>\n <div className=\"flex gap-1\">\n <div\n className=\"h-4 rounded bg-action-primary-bg/30\"\n style={{ width: `${((point.prev ?? 0) / maxRevenue) * 100}%` }}\n />\n <div\n className=\"h-4 rounded bg-action-primary-bg\"\n style={{\n width: `${(point.value / maxRevenue) * 100 - ((point.prev ?? 0) / maxRevenue) * 100}%`,\n }}\n />\n </div>\n </div>\n ))}\n <div className=\"mt-4 flex items-center gap-4 text-xs\">\n <span className=\"flex items-center gap-1\">\n <div className=\"h-3 w-3 rounded bg-action-primary-bg/30\" /> Previous\n </span>\n <span className=\"flex items-center gap-1\">\n <div className=\"h-3 w-3 rounded bg-action-primary-bg\" /> Current\n </span>\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Top Performing Services</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {[...servicePerformance]\n .sort((a, b) => b.value - a.value)\n .slice(0, 5)\n .map((service, idx) => (\n <div\n key={service.name}\n className=\"flex items-center justify-between rounded-lg border p-3\"\n >\n <div className=\"flex items-center gap-3\">\n <div\n className={`flex h-8 w-8 items-center justify-center rounded-full ${\n idx === 0\n ? 'bg-yellow-100 text-yellow-700'\n : idx === 1\n ? 'bg-gray-100 text-gray-600'\n : idx === 2\n ? 'bg-orange-100 text-orange-600'\n : 'bg-bg-sunken text-text-secondary'\n } text-sm font-bold`}\n >\n {idx + 1}\n </div>\n <span className=\"font-medium text-sm\">{service.name}</span>\n </div>\n <Badge variant=\"secondary\">{service.value} bookings</Badge>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/AnalyticsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n EmphasisPanel,\n AuroraBackground,\n GradientText,\n} from '@burdenoff/fe-libs/ui';\nimport {\n TrendingUp,\n TrendingDown,\n Minus,\n DollarSign,\n Users,\n BarChart3,\n Activity,\n Globe,\n Calendar,\n ArrowUpRight,\n ArrowDownRight,\n Download,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\n\ntype AnalyticsTab = 'overview' | 'revenue' | 'clients' | 'services' | 'performance';\n\nexport function AnalyticsPage() {\n const { analyticsMetrics, revenueData, clientData, servicePerformance, geographicData } =\n useProServiceWorldStore();\n const [activeTab, setActiveTab] = useState<AnalyticsTab>('overview');\n const [timeRange, setTimeRange] = useState('6m');\n\n const tabs: { key: AnalyticsTab; label: string; icon: typeof BarChart3 }[] = [\n { key: 'overview', label: 'Overview', icon: BarChart3 },\n { key: 'revenue', label: 'Revenue', icon: DollarSign },\n { key: 'clients', label: 'Clients', icon: Users },\n { key: 'services', label: 'Services', icon: Activity },\n { key: 'performance', label: 'Performance', icon: TrendingUp },\n ];\n\n const changeIcon = (type: string) => {\n if (type === 'positive') return <TrendingUp className=\"h-4 w-4 text-status-success-text\" />;\n if (type === 'negative') return <TrendingDown className=\"h-4 w-4 text-status-error-text\" />;\n return <Minus className=\"h-4 w-4 text-text-muted\" />;\n };\n\n const maxRevenue = Math.max(...revenueData.map((d) => d.value));\n const maxClients = Math.max(...clientData.map((d) => d.value));\n const maxServiceBookings = Math.max(...servicePerformance.map((d) => d.value));\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\">\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>Analytics</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Insights and performance metrics for your professional services.\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex rounded border border-border-seam overflow-hidden\">\n {['1m', '3m', '6m', '1y'].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 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${\n timeRange === range\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'text-text-secondary hover:bg-bg-sunken/50'\n }`}\n >\n {range.toUpperCase()}\n </button>\n ))}\n </div>\n <Button variant=\"outline\" size=\"sm\">\n <Download className=\"mr-2 h-4 w-4\" /> Export\n </Button>\n </div>\n </div>\n </div>\n\n {/* Tabs */}\n <div className=\"flex flex-wrap 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-action-primary-bg text-text-primary'\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\n {/* Metric Cards (shown on all tabs) */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-5\">\n {analyticsMetrics.map((metric, i) => (\n <GlassCard key={i} treatment={i === 0 ? 'glass' : 'metric'} glow={i === 0} className=\"border-border-seam\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-xs font-medium text-text-secondary\">{metric.label}</p>\n {changeIcon(metric.changeType)}\n </div>\n <p className=\"mt-1 text-2xl font-bold tabular-nums\">{metric.value}</p>\n <p\n className={`mt-0.5 text-xs ${\n metric.changeType === 'positive'\n ? 'text-status-success-text'\n : metric.changeType === 'negative'\n ? 'text-status-error-text'\n : 'text-text-muted'\n }`}\n >\n {metric.change} from last period\n </p>\n </GlassCard>\n ))}\n </div>\n\n {/* Overview Tab */}\n {activeTab === 'overview' && (\n <div className=\"grid gap-6 lg:grid-cols-2\">\n {/* Revenue chart — the single emphasised insight zone */}\n <EmphasisPanel>\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-base font-semibold tracking-tight\">Revenue Trend</h2>\n <Badge variant=\"secondary\">Last 6 months</Badge>\n </div>\n <div 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=\"relative flex-1 h-6 bg-bg-sunken rounded overflow-hidden\">\n {point.prev !== undefined && (\n <div\n className=\"absolute h-6 bg-text-muted/15 rounded\"\n style={{ width: `${(point.prev / maxRevenue) * 100}%` }}\n />\n )}\n <div\n className=\"relative h-6 bg-action-primary-bg rounded shadow-[var(--shadow-glow)]\"\n style={{ width: `${(point.value / maxRevenue) * 100}%` }}\n />\n </div>\n <span className=\"w-20 text-right text-xs font-medium tabular-nums\">\n ${(point.value / 1000).toFixed(1)}k\n </span>\n </div>\n ))}\n </div>\n </EmphasisPanel>\n\n {/* Client growth */}\n <Card className=\"border-border-seam\">\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">Client Growth</CardTitle>\n <Badge variant=\"secondary\">Monthly</Badge>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {clientData.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-6 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-6 bg-accent-blue rounded\"\n style={{ width: `${(point.value / maxClients) * 100}%` }}\n />\n </div>\n <span className=\"w-12 text-right text-xs font-medium tabular-nums\">\n {point.value}\n </span>\n </div>\n ))}\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\">Revenue Breakdown</CardTitle>\n </CardHeader>\n <CardContent>\n <div 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\n key={point.name}\n className=\"flex items-center justify-between rounded-lg border border-border-seam p-4 transition-shadow hover:shadow-pop\"\n >\n <div className=\"flex items-center gap-4\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-accent-soft\">\n <Calendar className=\"h-5 w-5 text-text-primary\" />\n </div>\n <div>\n <p className=\"font-medium\">{point.name} 2025</p>\n <p className=\"text-sm text-text-secondary tabular-nums\">\n Previous: ${((point.prev ?? 0) / 1000).toFixed(1)}k\n </p>\n </div>\n </div>\n <div className=\"text-right\">\n <p className=\"text-lg font-bold tabular-nums\">\n ${(point.value / 1000).toFixed(1)}k\n </p>\n <p\n className={`flex items-center justify-end gap-1 text-xs tabular-nums ${isPositive ? 'text-status-success-text' : 'text-status-error-text'}`}\n >\n {isPositive ? (\n <ArrowUpRight className=\"h-3 w-3\" />\n ) : (\n <ArrowDownRight className=\"h-3 w-3\" />\n )}\n {growth}%\n </p>\n </div>\n </div>\n );\n })}\n </div>\n </CardContent>\n </Card>\n </div>\n )}\n\n {/* Clients Tab */}\n {activeTab === 'clients' && (\n <div className=\"space-y-6\">\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Client Acquisition</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {clientData.map((point, idx) => {\n const prev = idx > 0 ? (clientData[idx - 1]?.value ?? 0) : 0;\n const growth = prev > 0 ? (((point.value - prev) / prev) * 100).toFixed(0) : 'N/A';\n return (\n <div\n key={point.name}\n className=\"flex items-center gap-4 rounded-lg border border-border-seam p-3\"\n >\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between\">\n <span className=\"font-medium\">{point.name}</span>\n <span className=\"text-lg font-bold tabular-nums\">{point.value} clients</span>\n </div>\n <div className=\"mt-2 h-2 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-accent-blue\"\n style={{ width: `${(point.value / maxClients) * 100}%` }}\n />\n </div>\n </div>\n <Badge variant=\"secondary\" className=\"text-xs tabular-nums\">\n {typeof growth === 'string' ? growth : `+${growth}%`}\n </Badge>\n </div>\n );\n })}\n </CardContent>\n </Card>\n\n {/* Geographic data */}\n <Card className=\"border-border-seam\">\n <CardHeader>\n <div className=\"flex items-center gap-2\">\n <Globe className=\"h-5 w-5 text-text-secondary\" />\n <CardTitle className=\"text-base\">Geographic Distribution</CardTitle>\n </div>\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 font-medium\">Country</th>\n <th className=\"p-3 text-right font-medium\">Clients</th>\n <th className=\"p-3 text-right font-medium\">Revenue</th>\n <th className=\"p-3 font-medium\">Distribution</th>\n </tr>\n </thead>\n <tbody>\n {geographicData.map((geo) => {\n const totalClients = geographicData.reduce((s, g) => s + g.clients, 0);\n const pct = Math.round((geo.clients / totalClients) * 100);\n return (\n <tr\n key={geo.country}\n className=\"border-b border-border-seam transition-colors hover:bg-bg-sunken/50\"\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 tabular-nums\">\n ${(geo.revenue / 1000).toFixed(0)}k\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-action-primary-bg\"\n style={{ width: `${pct}%` }}\n />\n </div>\n <span className=\"w-10 text-right text-xs text-text-secondary tabular-nums\">\n {pct}%\n </span>\n </div>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </CardContent>\n </Card>\n </div>\n )}\n\n {/* Services Tab */}\n {activeTab === 'services' && (\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Service Performance (Total Bookings)</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-4\">\n <span className=\"w-6 text-sm font-medium text-text-secondary tabular-nums\">\n #{idx + 1}\n </span>\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium\">{service.name}</span>\n <span className=\"text-sm font-bold tabular-nums\">{service.value}</span>\n </div>\n <div className=\"mt-1 h-3 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-3 rounded-full bg-action-primary-bg transition-all\"\n style={{ width: `${(service.value / maxServiceBookings) * 100}%` }}\n />\n </div>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n )}\n\n {/* Performance Tab */}\n {activeTab === 'performance' && (\n <div className=\"grid gap-6 lg:grid-cols-2\">\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Monthly Revenue vs Previous</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {revenueData.map((point) => (\n <div key={point.name} className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"font-medium\">{point.name}</span>\n <span className=\"text-text-secondary tabular-nums\">\n ${(point.value / 1000).toFixed(1)}k vs $\n {((point.prev ?? 0) / 1000).toFixed(1)}k\n </span>\n </div>\n <div className=\"flex gap-1\">\n <div\n className=\"h-4 rounded bg-action-primary-bg/30\"\n style={{ width: `${((point.prev ?? 0) / maxRevenue) * 100}%` }}\n />\n <div\n className=\"h-4 rounded bg-action-primary-bg\"\n style={{\n width: `${(point.value / maxRevenue) * 100 - ((point.prev ?? 0) / maxRevenue) * 100}%`,\n }}\n />\n </div>\n </div>\n ))}\n <div className=\"mt-4 flex items-center gap-4 text-xs\">\n <span className=\"flex items-center gap-1\">\n <div className=\"h-3 w-3 rounded bg-action-primary-bg/30\" /> Previous\n </span>\n <span className=\"flex items-center gap-1\">\n <div className=\"h-3 w-3 rounded bg-action-primary-bg\" /> Current\n </span>\n </div>\n </CardContent>\n </Card>\n\n <Card className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"text-base\">Top Performing Services</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {[...servicePerformance]\n .sort((a, b) => b.value - a.value)\n .slice(0, 5)\n .map((service, idx) => (\n <div\n key={service.name}\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 <div\n className={`flex h-8 w-8 items-center justify-center rounded-full text-sm font-bold tabular-nums ${\n idx === 0\n ? 'bg-status-warning-bg-subtle text-status-warning-text'\n : idx === 1\n ? 'bg-bg-sunken text-text-secondary'\n : idx === 2\n ? 'bg-accent-soft text-text-primary'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n {idx + 1}\n </div>\n <span className=\"font-medium text-sm\">{service.name}</span>\n </div>\n <Badge variant=\"secondary\">{service.value} bookings</Badge>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|