@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
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
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 PagePurpose,\n NextSteps,\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 <PagePurpose icon={<BarChart3 className=\"h-4 w-4\" />}>\n Understand how your professional services are performing. Track revenue and client growth\n over time, see which services and regions drive the most demand, and export the numbers —\n so you can double down on what works and plan where to grow next.\n </PagePurpose>\n\n <NextSteps\n storageKey=\"proserviceworld-analytics\"\n steps={[\n {\n id: 'revenue',\n label: 'Inspect revenue performance',\n description: 'Open the Revenue tab to see month-over-month growth.',\n icon: <DollarSign className=\"h-4 w-4\" />,\n onClick: () => setActiveTab('revenue'),\n },\n {\n id: 'services',\n label: 'Find your top services',\n description: 'See which services attract the most bookings.',\n icon: <Activity className=\"h-4 w-4\" />,\n onClick: () => setActiveTab('services'),\n },\n {\n id: 'clients',\n label: 'Track client acquisition',\n description: 'Review client growth and geographic reach.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => setActiveTab('clients'),\n },\n ]}\n />\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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"}
|
|
@@ -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 { ArrowUpDown as r, Calendar as i, CheckCircle2 as a, ChevronRight as o, Clock as s, DollarSign as c, Filter as l, Loader2 as u, Search as d, User as f, XCircle as p } from "lucide-react";
|
|
4
4
|
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
5
|
-
import { Badge as g, Button as _, Card as v, CardContent as y, GlassCard as b, IllustratedEmptyState as x, Input as S } from "@burdenoff/fe-libs/ui";
|
|
5
|
+
import { Badge as g, Button as _, Card as v, CardContent as y, GlassCard as b, IllustratedEmptyState as x, Input as S, PagePurpose as C } from "@burdenoff/fe-libs/ui";
|
|
6
6
|
//#region src/proserviceworld/pages/BookingsPage.tsx
|
|
7
|
-
var
|
|
7
|
+
var w = {
|
|
8
8
|
pending: {
|
|
9
9
|
label: "Pending",
|
|
10
10
|
color: "text-status-warning-text",
|
|
@@ -30,29 +30,29 @@ var C = {
|
|
|
30
30
|
badgeVariant: "destructive"
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
|
-
function
|
|
34
|
-
let { bookings: s } = e(), [p,
|
|
33
|
+
function T() {
|
|
34
|
+
let { bookings: s } = e(), [p, T] = n(""), [E, D] = n("all"), [O, k] = n("date"), [A, j] = n(!1), [M, N] = n(null), P = t(() => {
|
|
35
35
|
let e = s;
|
|
36
|
-
if (
|
|
36
|
+
if (E !== "all" && (e = e.filter((e) => e.status === E)), p.trim()) {
|
|
37
37
|
let t = p.toLowerCase();
|
|
38
38
|
e = e.filter((e) => e.title.toLowerCase().includes(t) || e.providerName.toLowerCase().includes(t) || e.serviceName.toLowerCase().includes(t));
|
|
39
39
|
}
|
|
40
40
|
return e = [...e].sort((e, t) => {
|
|
41
|
-
let n =
|
|
42
|
-
return
|
|
41
|
+
let n = O === "date" ? new Date(t.createdAt).getTime() - new Date(e.createdAt).getTime() : O === "budget" ? t.budget - e.budget : t.progress - e.progress;
|
|
42
|
+
return A ? -n : n;
|
|
43
43
|
}), e;
|
|
44
44
|
}, [
|
|
45
45
|
s,
|
|
46
46
|
p,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
]),
|
|
47
|
+
E,
|
|
48
|
+
O,
|
|
49
|
+
A
|
|
50
|
+
]), F = M ? s.find((e) => e.id === M) : null, I = t(() => {
|
|
51
51
|
let e = { all: s.length };
|
|
52
52
|
for (let t of s) e[t.status] = (e[t.status] ?? 0) + 1;
|
|
53
53
|
return e;
|
|
54
|
-
}, [s]),
|
|
55
|
-
|
|
54
|
+
}, [s]), L = s.reduce((e, t) => e + t.budget, 0), R = Math.round(s.filter((e) => e.status === "in_progress").reduce((e, t) => e + t.progress, 0) / Math.max(1, s.filter((e) => e.status === "in_progress").length)), z = (e) => {
|
|
55
|
+
O === e ? j(!A) : (k(e), j(!1));
|
|
56
56
|
};
|
|
57
57
|
return /* @__PURE__ */ h("div", {
|
|
58
58
|
className: "space-y-6 p-6 bg-bg-canvas min-h-screen",
|
|
@@ -70,6 +70,10 @@ function w() {
|
|
|
70
70
|
children: [/* @__PURE__ */ m(i, { className: "mr-2 h-4 w-4" }), " New Booking"]
|
|
71
71
|
})]
|
|
72
72
|
}),
|
|
73
|
+
/* @__PURE__ */ m(C, {
|
|
74
|
+
icon: /* @__PURE__ */ m(i, { className: "h-4 w-4" }),
|
|
75
|
+
children: "Every engagement you have booked, in one timeline. Filter by status, search by provider or service, and open any booking to follow its progress, payment status, and key dates — then message the provider or take action right from the detail panel."
|
|
76
|
+
}),
|
|
73
77
|
/* @__PURE__ */ h("div", {
|
|
74
78
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
75
79
|
children: [
|
|
@@ -100,7 +104,7 @@ function w() {
|
|
|
100
104
|
children: /* @__PURE__ */ m(a, { className: "h-5 w-5 text-status-success-text" })
|
|
101
105
|
}), /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("p", {
|
|
102
106
|
className: "text-2xl font-bold tabular-nums",
|
|
103
|
-
children:
|
|
107
|
+
children: I.completed ?? 0
|
|
104
108
|
}), /* @__PURE__ */ m("p", {
|
|
105
109
|
className: "text-xs text-text-secondary",
|
|
106
110
|
children: "Completed"
|
|
@@ -117,7 +121,7 @@ function w() {
|
|
|
117
121
|
children: /* @__PURE__ */ m(u, { className: "h-5 w-5 text-status-warning-text" })
|
|
118
122
|
}), /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ h("p", {
|
|
119
123
|
className: "text-2xl font-bold tabular-nums",
|
|
120
|
-
children: [
|
|
124
|
+
children: [R, "%"]
|
|
121
125
|
}), /* @__PURE__ */ m("p", {
|
|
122
126
|
className: "text-xs text-text-secondary",
|
|
123
127
|
children: "Avg. In-Progress"
|
|
@@ -135,7 +139,7 @@ function w() {
|
|
|
135
139
|
children: /* @__PURE__ */ m(c, { className: "h-5 w-5 text-accent-purple" })
|
|
136
140
|
}), /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ h("p", {
|
|
137
141
|
className: "text-2xl font-bold tabular-nums",
|
|
138
|
-
children: ["$",
|
|
142
|
+
children: ["$", L.toLocaleString()]
|
|
139
143
|
}), /* @__PURE__ */ m("p", {
|
|
140
144
|
className: "text-xs text-text-secondary",
|
|
141
145
|
children: "Total Budget"
|
|
@@ -151,7 +155,7 @@ function w() {
|
|
|
151
155
|
children: [/* @__PURE__ */ m(d, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ m(S, {
|
|
152
156
|
placeholder: "Search bookings by title, provider, or service...",
|
|
153
157
|
value: p,
|
|
154
|
-
onChange: (e) =>
|
|
158
|
+
onChange: (e) => T(e.target.value),
|
|
155
159
|
className: "pl-10"
|
|
156
160
|
})]
|
|
157
161
|
}), /* @__PURE__ */ h("div", {
|
|
@@ -163,14 +167,14 @@ function w() {
|
|
|
163
167
|
"completed",
|
|
164
168
|
"cancelled"
|
|
165
169
|
].map((e) => /* @__PURE__ */ h(_, {
|
|
166
|
-
variant:
|
|
170
|
+
variant: E === e ? "default" : "outline",
|
|
167
171
|
size: "sm",
|
|
168
|
-
onClick: () =>
|
|
169
|
-
children: [e === "all" ? "All" :
|
|
172
|
+
onClick: () => D(e),
|
|
173
|
+
children: [e === "all" ? "All" : w[e].label, /* @__PURE__ */ h("span", {
|
|
170
174
|
className: "ml-1 text-xs opacity-70 tabular-nums",
|
|
171
175
|
children: [
|
|
172
176
|
"(",
|
|
173
|
-
|
|
177
|
+
I[e] ?? 0,
|
|
174
178
|
")"
|
|
175
179
|
]
|
|
176
180
|
})]
|
|
@@ -193,17 +197,17 @@ function w() {
|
|
|
193
197
|
label: "Progress"
|
|
194
198
|
}
|
|
195
199
|
].map(({ field: e, label: t }) => /* @__PURE__ */ h("button", {
|
|
196
|
-
onClick: () =>
|
|
197
|
-
"aria-pressed":
|
|
198
|
-
className: `flex items-center gap-1 rounded border px-3 py-1 text-xs transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${
|
|
200
|
+
onClick: () => z(e),
|
|
201
|
+
"aria-pressed": O === e,
|
|
202
|
+
className: `flex items-center gap-1 rounded border px-3 py-1 text-xs transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${O === e ? "border-border-strong bg-accent-soft text-text-primary" : "border-border-seam text-text-secondary hover:bg-bg-sunken/50"}`,
|
|
199
203
|
children: [/* @__PURE__ */ m(r, { className: "h-3 w-3" }), t]
|
|
200
204
|
}, e))
|
|
201
205
|
}),
|
|
202
206
|
/* @__PURE__ */ h("div", {
|
|
203
207
|
className: "grid gap-6 lg:grid-cols-3",
|
|
204
208
|
children: [/* @__PURE__ */ m("div", {
|
|
205
|
-
className: `space-y-3 ${
|
|
206
|
-
children:
|
|
209
|
+
className: `space-y-3 ${F ? "lg:col-span-2" : "lg:col-span-3"}`,
|
|
210
|
+
children: P.length === 0 ? /* @__PURE__ */ m(v, {
|
|
207
211
|
className: "border-border-seam",
|
|
208
212
|
children: /* @__PURE__ */ m(y, {
|
|
209
213
|
className: "py-4",
|
|
@@ -213,11 +217,11 @@ function w() {
|
|
|
213
217
|
description: "No bookings match your current filters."
|
|
214
218
|
})
|
|
215
219
|
})
|
|
216
|
-
}) :
|
|
217
|
-
let t =
|
|
220
|
+
}) : P.map((e) => {
|
|
221
|
+
let t = w[e.status], n = t.icon;
|
|
218
222
|
return /* @__PURE__ */ m(v, {
|
|
219
|
-
className: `cursor-pointer border-border-seam transition-shadow hover:shadow-pop ${
|
|
220
|
-
onClick: () => M
|
|
223
|
+
className: `cursor-pointer border-border-seam transition-shadow hover:shadow-pop ${M === e.id ? "border-border-strong ring-1 ring-border-focus" : ""}`,
|
|
224
|
+
onClick: () => N(M === e.id ? null : e.id),
|
|
221
225
|
children: /* @__PURE__ */ h(y, {
|
|
222
226
|
className: "pt-4",
|
|
223
227
|
children: [/* @__PURE__ */ h("div", {
|
|
@@ -293,7 +297,7 @@ function w() {
|
|
|
293
297
|
})
|
|
294
298
|
}, e.id);
|
|
295
299
|
})
|
|
296
|
-
}),
|
|
300
|
+
}), F && /* @__PURE__ */ m("div", {
|
|
297
301
|
className: "space-y-4",
|
|
298
302
|
children: /* @__PURE__ */ h(b, {
|
|
299
303
|
treatment: "glass",
|
|
@@ -306,7 +310,7 @@ function w() {
|
|
|
306
310
|
}), /* @__PURE__ */ m(_, {
|
|
307
311
|
variant: "outline",
|
|
308
312
|
size: "sm",
|
|
309
|
-
onClick: () =>
|
|
313
|
+
onClick: () => N(null),
|
|
310
314
|
children: "Close"
|
|
311
315
|
})]
|
|
312
316
|
}), /* @__PURE__ */ h("div", {
|
|
@@ -314,29 +318,29 @@ function w() {
|
|
|
314
318
|
children: [
|
|
315
319
|
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("h3", {
|
|
316
320
|
className: "font-semibold",
|
|
317
|
-
children:
|
|
321
|
+
children: F.title
|
|
318
322
|
}), /* @__PURE__ */ m("p", {
|
|
319
323
|
className: "mt-1 text-sm text-text-secondary",
|
|
320
|
-
children:
|
|
324
|
+
children: F.description
|
|
321
325
|
})] }),
|
|
322
326
|
/* @__PURE__ */ m(g, {
|
|
323
|
-
variant:
|
|
324
|
-
children:
|
|
327
|
+
variant: w[F.status].badgeVariant,
|
|
328
|
+
children: w[F.status].label
|
|
325
329
|
}),
|
|
326
|
-
|
|
330
|
+
F.status === "in_progress" && /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ h("div", {
|
|
327
331
|
className: "flex items-center justify-between text-sm",
|
|
328
332
|
children: [/* @__PURE__ */ m("span", {
|
|
329
333
|
className: "text-text-secondary",
|
|
330
334
|
children: "Progress"
|
|
331
335
|
}), /* @__PURE__ */ h("span", {
|
|
332
336
|
className: "font-medium tabular-nums",
|
|
333
|
-
children: [
|
|
337
|
+
children: [F.progress, "%"]
|
|
334
338
|
})]
|
|
335
339
|
}), /* @__PURE__ */ m("div", {
|
|
336
340
|
className: "mt-1 h-2 w-full overflow-hidden rounded-full bg-bg-sunken",
|
|
337
341
|
children: /* @__PURE__ */ m("div", {
|
|
338
342
|
className: "h-2 rounded-full bg-action-primary-bg",
|
|
339
|
-
style: { width: `${
|
|
343
|
+
style: { width: `${F.progress}%` }
|
|
340
344
|
})
|
|
341
345
|
})] }),
|
|
342
346
|
/* @__PURE__ */ h("div", {
|
|
@@ -349,7 +353,7 @@ function w() {
|
|
|
349
353
|
children: "Service"
|
|
350
354
|
}), /* @__PURE__ */ m("span", {
|
|
351
355
|
className: "font-medium",
|
|
352
|
-
children:
|
|
356
|
+
children: F.serviceName
|
|
353
357
|
})]
|
|
354
358
|
}),
|
|
355
359
|
/* @__PURE__ */ h("div", {
|
|
@@ -359,7 +363,7 @@ function w() {
|
|
|
359
363
|
children: "Provider"
|
|
360
364
|
}), /* @__PURE__ */ m("span", {
|
|
361
365
|
className: "font-medium",
|
|
362
|
-
children:
|
|
366
|
+
children: F.providerName
|
|
363
367
|
})]
|
|
364
368
|
}),
|
|
365
369
|
/* @__PURE__ */ h("div", {
|
|
@@ -369,7 +373,7 @@ function w() {
|
|
|
369
373
|
children: "Budget"
|
|
370
374
|
}), /* @__PURE__ */ h("span", {
|
|
371
375
|
className: "font-medium tabular-nums",
|
|
372
|
-
children: ["$",
|
|
376
|
+
children: ["$", F.budget.toLocaleString()]
|
|
373
377
|
})]
|
|
374
378
|
}),
|
|
375
379
|
/* @__PURE__ */ h("div", {
|
|
@@ -379,7 +383,7 @@ function w() {
|
|
|
379
383
|
children: "Start Date"
|
|
380
384
|
}), /* @__PURE__ */ m("span", {
|
|
381
385
|
className: "font-medium",
|
|
382
|
-
children:
|
|
386
|
+
children: F.startDate
|
|
383
387
|
})]
|
|
384
388
|
}),
|
|
385
389
|
/* @__PURE__ */ h("div", {
|
|
@@ -389,7 +393,7 @@ function w() {
|
|
|
389
393
|
children: "End Date"
|
|
390
394
|
}), /* @__PURE__ */ m("span", {
|
|
391
395
|
className: "font-medium",
|
|
392
|
-
children:
|
|
396
|
+
children: F.endDate
|
|
393
397
|
})]
|
|
394
398
|
}),
|
|
395
399
|
/* @__PURE__ */ h("div", {
|
|
@@ -399,7 +403,7 @@ function w() {
|
|
|
399
403
|
children: "Payment"
|
|
400
404
|
}), /* @__PURE__ */ m(g, {
|
|
401
405
|
variant: "secondary",
|
|
402
|
-
children:
|
|
406
|
+
children: F.paymentStatus
|
|
403
407
|
})]
|
|
404
408
|
}),
|
|
405
409
|
/* @__PURE__ */ h("div", {
|
|
@@ -409,7 +413,7 @@ function w() {
|
|
|
409
413
|
children: "Method"
|
|
410
414
|
}), /* @__PURE__ */ m("span", {
|
|
411
415
|
className: "font-medium",
|
|
412
|
-
children:
|
|
416
|
+
children: F.paymentMethod.replace("_", " ")
|
|
413
417
|
})]
|
|
414
418
|
})
|
|
415
419
|
]
|
|
@@ -421,7 +425,7 @@ function w() {
|
|
|
421
425
|
size: "sm",
|
|
422
426
|
variant: "outline",
|
|
423
427
|
children: "Message Provider"
|
|
424
|
-
}),
|
|
428
|
+
}), F.status === "pending" && /* @__PURE__ */ m(_, {
|
|
425
429
|
className: "flex-1",
|
|
426
430
|
size: "sm",
|
|
427
431
|
variant: "destructive",
|
|
@@ -437,6 +441,6 @@ function w() {
|
|
|
437
441
|
});
|
|
438
442
|
}
|
|
439
443
|
//#endregion
|
|
440
|
-
export {
|
|
444
|
+
export { T as BookingsPage };
|
|
441
445
|
|
|
442
446
|
//# sourceMappingURL=BookingsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BookingsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/BookingsPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n GlassCard,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Search,\n Clock,\n CheckCircle2,\n XCircle,\n Loader2,\n Calendar,\n DollarSign,\n User,\n ChevronRight,\n Filter,\n ArrowUpDown,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\nimport type { BookingStatus } from '../types';\n\nconst statusConfig: Record<\n BookingStatus,\n {\n label: string;\n color: string;\n icon: typeof Clock;\n badgeVariant: 'default' | 'secondary' | 'outline' | 'destructive';\n }\n> = {\n pending: {\n label: 'Pending',\n color: 'text-status-warning-text',\n icon: Clock,\n badgeVariant: 'secondary',\n },\n in_progress: {\n label: 'In Progress',\n color: 'text-accent-blue',\n icon: Loader2,\n badgeVariant: 'default',\n },\n completed: {\n label: 'Completed',\n color: 'text-status-success-text',\n icon: CheckCircle2,\n badgeVariant: 'outline',\n },\n cancelled: {\n label: 'Cancelled',\n color: 'text-status-error-text',\n icon: XCircle,\n badgeVariant: 'destructive',\n },\n};\n\ntype SortField = 'date' | 'budget' | 'progress';\n\nexport function BookingsPage() {\n const { bookings } = useProServiceWorldStore();\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState<BookingStatus | 'all'>('all');\n const [sortField, setSortField] = useState<SortField>('date');\n const [sortAsc, setSortAsc] = useState(false);\n const [selectedBookingId, setSelectedBookingId] = useState<string | null>(null);\n\n const filtered = useMemo(() => {\n let result = bookings;\n\n if (statusFilter !== 'all') {\n result = result.filter((b) => b.status === statusFilter);\n }\n\n if (searchQuery.trim()) {\n const q = searchQuery.toLowerCase();\n result = result.filter(\n (b) =>\n b.title.toLowerCase().includes(q) ||\n b.providerName.toLowerCase().includes(q) ||\n b.serviceName.toLowerCase().includes(q)\n );\n }\n\n result = [...result].sort((a, b) => {\n const cmp =\n sortField === 'date'\n ? new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()\n : sortField === 'budget'\n ? b.budget - a.budget\n : b.progress - a.progress;\n return sortAsc ? -cmp : cmp;\n });\n\n return result;\n }, [bookings, searchQuery, statusFilter, sortField, sortAsc]);\n\n const selectedBooking = selectedBookingId\n ? bookings.find((b) => b.id === selectedBookingId)\n : null;\n\n const statusCounts = useMemo(() => {\n const counts: Record<string, number> = { all: bookings.length };\n for (const b of bookings) {\n counts[b.status] = (counts[b.status] ?? 0) + 1;\n }\n return counts;\n }, [bookings]);\n\n const totalBudget = bookings.reduce((sum, b) => sum + b.budget, 0);\n const avgProgress = Math.round(\n bookings.filter((b) => b.status === 'in_progress').reduce((sum, b) => sum + b.progress, 0) /\n Math.max(1, bookings.filter((b) => b.status === 'in_progress').length)\n );\n\n const handleSort = (field: SortField) => {\n if (sortField === field) setSortAsc(!sortAsc);\n else {\n setSortField(field);\n setSortAsc(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 bg-bg-canvas min-h-screen\">\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 tracking-tight\">Bookings</h1>\n <p className=\"text-sm text-text-secondary\">Manage and track all your service bookings.</p>\n </div>\n <Button size=\"sm\">\n <Calendar className=\"mr-2 h-4 w-4\" /> New Booking\n </Button>\n </div>\n\n {/* Stats */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"metric\" 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 <Calendar className=\"h-5 w-5 text-accent-blue\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{bookings.length}</p>\n <p className=\"text-xs text-text-secondary\">Total Bookings</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 <CheckCircle2 className=\"h-5 w-5 text-status-success-text\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{statusCounts.completed ?? 0}</p>\n <p className=\"text-xs text-text-secondary\">Completed</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 <Loader2 className=\"h-5 w-5 text-status-warning-text\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{avgProgress}%</p>\n <p className=\"text-xs text-text-secondary\">Avg. In-Progress</p>\n </div>\n </div>\n </GlassCard>\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-accent-purple-subtle p-2\">\n <DollarSign className=\"h-5 w-5 text-accent-purple\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">${totalBudget.toLocaleString()}</p>\n <p className=\"text-xs text-text-secondary\">Total Budget</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 bookings by title, provider, or service...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"pl-10\"\n />\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Filter className=\"h-4 w-4 text-text-secondary\" />\n {(['all', 'pending', 'in_progress', 'completed', 'cancelled'] as const).map((status) => (\n <Button\n key={status}\n variant={statusFilter === status ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setStatusFilter(status)}\n >\n {status === 'all' ? 'All' : statusConfig[status].label}\n <span className=\"ml-1 text-xs opacity-70 tabular-nums\">\n ({statusCounts[status] ?? 0})\n </span>\n </Button>\n ))}\n </div>\n </div>\n\n {/* Sort controls */}\n <div className=\"flex flex-wrap gap-2\">\n {[\n { field: 'date' as SortField, label: 'Date' },\n { field: 'budget' as SortField, label: 'Budget' },\n { field: 'progress' as SortField, label: 'Progress' },\n ].map(({ field, label }) => (\n <button\n key={field}\n onClick={() => handleSort(field)}\n aria-pressed={sortField === field}\n className={`flex items-center gap-1 rounded border px-3 py-1 text-xs transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${\n sortField === field\n ? 'border-border-strong bg-accent-soft text-text-primary'\n : 'border-border-seam text-text-secondary hover:bg-bg-sunken/50'\n }`}\n >\n <ArrowUpDown className=\"h-3 w-3\" />\n {label}\n </button>\n ))}\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Booking list */}\n <div className={`space-y-3 ${selectedBooking ? 'lg:col-span-2' : 'lg:col-span-3'}`}>\n {filtered.length === 0 ? (\n <Card className=\"border-border-seam\">\n <CardContent className=\"py-4\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No bookings found\"\n description=\"No bookings match your current filters.\"\n />\n </CardContent>\n </Card>\n ) : (\n filtered.map((booking) => {\n const config = statusConfig[booking.status];\n const StatusIcon = config.icon;\n return (\n <Card\n key={booking.id}\n className={`cursor-pointer border-border-seam transition-shadow hover:shadow-pop ${\n selectedBookingId === booking.id\n ? 'border-border-strong ring-1 ring-border-focus'\n : ''\n }`}\n onClick={() =>\n setSelectedBookingId(selectedBookingId === booking.id ? null : booking.id)\n }\n >\n <CardContent className=\"pt-4\">\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex-1 space-y-2\">\n <div className=\"flex items-center gap-2\">\n <StatusIcon className={`h-4 w-4 ${config.color}`} />\n <h3 className=\"font-medium\">{booking.title}</h3>\n </div>\n <p className=\"text-sm text-text-secondary line-clamp-1\">\n {booking.description}\n </p>\n <div className=\"flex flex-wrap items-center gap-4 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <User className=\"h-3 w-3\" /> {booking.providerName}\n </span>\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" /> {booking.startDate}\n </span>\n <span className=\"flex items-center gap-1 tabular-nums\">\n <DollarSign className=\"h-3 w-3\" /> ${booking.budget.toLocaleString()}\n </span>\n </div>\n </div>\n <div className=\"flex flex-col items-end gap-2\">\n <Badge variant={config.badgeVariant}>{config.label}</Badge>\n {booking.status === 'in_progress' && (\n <span className=\"text-sm font-medium tabular-nums\">\n {booking.progress}%\n </span>\n )}\n <ChevronRight className=\"h-4 w-4 text-text-secondary\" />\n </div>\n </div>\n\n {/* Progress bar for in-progress bookings */}\n {booking.status === 'in_progress' && (\n <div className=\"mt-3\">\n <div className=\"h-2 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg transition-all\"\n style={{ width: `${booking.progress}%` }}\n />\n </div>\n </div>\n )}\n </CardContent>\n </Card>\n );\n })\n )}\n </div>\n\n {/* Detail rail */}\n {selectedBooking && (\n <div className=\"space-y-4\">\n <GlassCard treatment=\"glass\" className=\"border-border-seam lg:sticky lg:top-6\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold tracking-tight\">Booking Details</h2>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setSelectedBookingId(null)}>\n Close\n </Button>\n </div>\n <div className=\"mt-4 space-y-4\">\n <div>\n <h3 className=\"font-semibold\">{selectedBooking.title}</h3>\n <p className=\"mt-1 text-sm text-text-secondary\">{selectedBooking.description}</p>\n </div>\n\n <Badge variant={statusConfig[selectedBooking.status].badgeVariant}>\n {statusConfig[selectedBooking.status].label}\n </Badge>\n\n {selectedBooking.status === 'in_progress' && (\n <div>\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">Progress</span>\n <span className=\"font-medium tabular-nums\">{selectedBooking.progress}%</span>\n </div>\n <div className=\"mt-1 h-2 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg\"\n style={{ width: `${selectedBooking.progress}%` }}\n />\n </div>\n </div>\n )}\n\n <div className=\"space-y-2 border-t border-border-seam pt-3 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">Service</span>\n <span className=\"font-medium\">{selectedBooking.serviceName}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">Provider</span>\n <span className=\"font-medium\">{selectedBooking.providerName}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">Budget</span>\n <span className=\"font-medium tabular-nums\">\n ${selectedBooking.budget.toLocaleString()}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">Start Date</span>\n <span className=\"font-medium\">{selectedBooking.startDate}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">End Date</span>\n <span className=\"font-medium\">{selectedBooking.endDate}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">Payment</span>\n <Badge variant=\"secondary\">{selectedBooking.paymentStatus}</Badge>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">Method</span>\n <span className=\"font-medium\">\n {selectedBooking.paymentMethod.replace('_', ' ')}\n </span>\n </div>\n </div>\n\n <div className=\"flex gap-2 border-t border-border-seam pt-3\">\n <Button className=\"flex-1\" size=\"sm\" variant=\"outline\">\n Message Provider\n </Button>\n {selectedBooking.status === 'pending' && (\n <Button className=\"flex-1\" size=\"sm\" variant=\"destructive\">\n Cancel\n </Button>\n )}\n </div>\n </div>\n </GlassCard>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BookingsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/BookingsPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Input,\n GlassCard,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Search,\n Clock,\n CheckCircle2,\n XCircle,\n Loader2,\n Calendar,\n DollarSign,\n User,\n ChevronRight,\n Filter,\n ArrowUpDown,\n} from 'lucide-react';\nimport { useProServiceWorldStore } from '../store/proserviceworldStore';\nimport type { BookingStatus } from '../types';\n\nconst statusConfig: Record<\n BookingStatus,\n {\n label: string;\n color: string;\n icon: typeof Clock;\n badgeVariant: 'default' | 'secondary' | 'outline' | 'destructive';\n }\n> = {\n pending: {\n label: 'Pending',\n color: 'text-status-warning-text',\n icon: Clock,\n badgeVariant: 'secondary',\n },\n in_progress: {\n label: 'In Progress',\n color: 'text-accent-blue',\n icon: Loader2,\n badgeVariant: 'default',\n },\n completed: {\n label: 'Completed',\n color: 'text-status-success-text',\n icon: CheckCircle2,\n badgeVariant: 'outline',\n },\n cancelled: {\n label: 'Cancelled',\n color: 'text-status-error-text',\n icon: XCircle,\n badgeVariant: 'destructive',\n },\n};\n\ntype SortField = 'date' | 'budget' | 'progress';\n\nexport function BookingsPage() {\n const { bookings } = useProServiceWorldStore();\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState<BookingStatus | 'all'>('all');\n const [sortField, setSortField] = useState<SortField>('date');\n const [sortAsc, setSortAsc] = useState(false);\n const [selectedBookingId, setSelectedBookingId] = useState<string | null>(null);\n\n const filtered = useMemo(() => {\n let result = bookings;\n\n if (statusFilter !== 'all') {\n result = result.filter((b) => b.status === statusFilter);\n }\n\n if (searchQuery.trim()) {\n const q = searchQuery.toLowerCase();\n result = result.filter(\n (b) =>\n b.title.toLowerCase().includes(q) ||\n b.providerName.toLowerCase().includes(q) ||\n b.serviceName.toLowerCase().includes(q)\n );\n }\n\n result = [...result].sort((a, b) => {\n const cmp =\n sortField === 'date'\n ? new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()\n : sortField === 'budget'\n ? b.budget - a.budget\n : b.progress - a.progress;\n return sortAsc ? -cmp : cmp;\n });\n\n return result;\n }, [bookings, searchQuery, statusFilter, sortField, sortAsc]);\n\n const selectedBooking = selectedBookingId\n ? bookings.find((b) => b.id === selectedBookingId)\n : null;\n\n const statusCounts = useMemo(() => {\n const counts: Record<string, number> = { all: bookings.length };\n for (const b of bookings) {\n counts[b.status] = (counts[b.status] ?? 0) + 1;\n }\n return counts;\n }, [bookings]);\n\n const totalBudget = bookings.reduce((sum, b) => sum + b.budget, 0);\n const avgProgress = Math.round(\n bookings.filter((b) => b.status === 'in_progress').reduce((sum, b) => sum + b.progress, 0) /\n Math.max(1, bookings.filter((b) => b.status === 'in_progress').length)\n );\n\n const handleSort = (field: SortField) => {\n if (sortField === field) setSortAsc(!sortAsc);\n else {\n setSortField(field);\n setSortAsc(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 bg-bg-canvas min-h-screen\">\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 tracking-tight\">Bookings</h1>\n <p className=\"text-sm text-text-secondary\">Manage and track all your service bookings.</p>\n </div>\n <Button size=\"sm\">\n <Calendar className=\"mr-2 h-4 w-4\" /> New Booking\n </Button>\n </div>\n\n <PagePurpose icon={<Calendar className=\"h-4 w-4\" />}>\n Every engagement you have booked, in one timeline. Filter by status, search by provider or\n service, and open any booking to follow its progress, payment status, and key dates — then\n message the provider or take action right from the detail panel.\n </PagePurpose>\n\n {/* Stats */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"metric\" 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 <Calendar className=\"h-5 w-5 text-accent-blue\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{bookings.length}</p>\n <p className=\"text-xs text-text-secondary\">Total Bookings</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 <CheckCircle2 className=\"h-5 w-5 text-status-success-text\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{statusCounts.completed ?? 0}</p>\n <p className=\"text-xs text-text-secondary\">Completed</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 <Loader2 className=\"h-5 w-5 text-status-warning-text\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">{avgProgress}%</p>\n <p className=\"text-xs text-text-secondary\">Avg. In-Progress</p>\n </div>\n </div>\n </GlassCard>\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <div className=\"flex items-center gap-3\">\n <div className=\"rounded-lg bg-accent-purple-subtle p-2\">\n <DollarSign className=\"h-5 w-5 text-accent-purple\" />\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums\">${totalBudget.toLocaleString()}</p>\n <p className=\"text-xs text-text-secondary\">Total Budget</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 bookings by title, provider, or service...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"pl-10\"\n />\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Filter className=\"h-4 w-4 text-text-secondary\" />\n {(['all', 'pending', 'in_progress', 'completed', 'cancelled'] as const).map((status) => (\n <Button\n key={status}\n variant={statusFilter === status ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setStatusFilter(status)}\n >\n {status === 'all' ? 'All' : statusConfig[status].label}\n <span className=\"ml-1 text-xs opacity-70 tabular-nums\">\n ({statusCounts[status] ?? 0})\n </span>\n </Button>\n ))}\n </div>\n </div>\n\n {/* Sort controls */}\n <div className=\"flex flex-wrap gap-2\">\n {[\n { field: 'date' as SortField, label: 'Date' },\n { field: 'budget' as SortField, label: 'Budget' },\n { field: 'progress' as SortField, label: 'Progress' },\n ].map(({ field, label }) => (\n <button\n key={field}\n onClick={() => handleSort(field)}\n aria-pressed={sortField === field}\n className={`flex items-center gap-1 rounded border px-3 py-1 text-xs transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ${\n sortField === field\n ? 'border-border-strong bg-accent-soft text-text-primary'\n : 'border-border-seam text-text-secondary hover:bg-bg-sunken/50'\n }`}\n >\n <ArrowUpDown className=\"h-3 w-3\" />\n {label}\n </button>\n ))}\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Booking list */}\n <div className={`space-y-3 ${selectedBooking ? 'lg:col-span-2' : 'lg:col-span-3'}`}>\n {filtered.length === 0 ? (\n <Card className=\"border-border-seam\">\n <CardContent className=\"py-4\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No bookings found\"\n description=\"No bookings match your current filters.\"\n />\n </CardContent>\n </Card>\n ) : (\n filtered.map((booking) => {\n const config = statusConfig[booking.status];\n const StatusIcon = config.icon;\n return (\n <Card\n key={booking.id}\n className={`cursor-pointer border-border-seam transition-shadow hover:shadow-pop ${\n selectedBookingId === booking.id\n ? 'border-border-strong ring-1 ring-border-focus'\n : ''\n }`}\n onClick={() =>\n setSelectedBookingId(selectedBookingId === booking.id ? null : booking.id)\n }\n >\n <CardContent className=\"pt-4\">\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"flex-1 space-y-2\">\n <div className=\"flex items-center gap-2\">\n <StatusIcon className={`h-4 w-4 ${config.color}`} />\n <h3 className=\"font-medium\">{booking.title}</h3>\n </div>\n <p className=\"text-sm text-text-secondary line-clamp-1\">\n {booking.description}\n </p>\n <div className=\"flex flex-wrap items-center gap-4 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <User className=\"h-3 w-3\" /> {booking.providerName}\n </span>\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" /> {booking.startDate}\n </span>\n <span className=\"flex items-center gap-1 tabular-nums\">\n <DollarSign className=\"h-3 w-3\" /> ${booking.budget.toLocaleString()}\n </span>\n </div>\n </div>\n <div className=\"flex flex-col items-end gap-2\">\n <Badge variant={config.badgeVariant}>{config.label}</Badge>\n {booking.status === 'in_progress' && (\n <span className=\"text-sm font-medium tabular-nums\">\n {booking.progress}%\n </span>\n )}\n <ChevronRight className=\"h-4 w-4 text-text-secondary\" />\n </div>\n </div>\n\n {/* Progress bar for in-progress bookings */}\n {booking.status === 'in_progress' && (\n <div className=\"mt-3\">\n <div className=\"h-2 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg transition-all\"\n style={{ width: `${booking.progress}%` }}\n />\n </div>\n </div>\n )}\n </CardContent>\n </Card>\n );\n })\n )}\n </div>\n\n {/* Detail rail */}\n {selectedBooking && (\n <div className=\"space-y-4\">\n <GlassCard treatment=\"glass\" className=\"border-border-seam lg:sticky lg:top-6\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-base font-semibold tracking-tight\">Booking Details</h2>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setSelectedBookingId(null)}>\n Close\n </Button>\n </div>\n <div className=\"mt-4 space-y-4\">\n <div>\n <h3 className=\"font-semibold\">{selectedBooking.title}</h3>\n <p className=\"mt-1 text-sm text-text-secondary\">{selectedBooking.description}</p>\n </div>\n\n <Badge variant={statusConfig[selectedBooking.status].badgeVariant}>\n {statusConfig[selectedBooking.status].label}\n </Badge>\n\n {selectedBooking.status === 'in_progress' && (\n <div>\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">Progress</span>\n <span className=\"font-medium tabular-nums\">{selectedBooking.progress}%</span>\n </div>\n <div className=\"mt-1 h-2 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg\"\n style={{ width: `${selectedBooking.progress}%` }}\n />\n </div>\n </div>\n )}\n\n <div className=\"space-y-2 border-t border-border-seam pt-3 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">Service</span>\n <span className=\"font-medium\">{selectedBooking.serviceName}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">Provider</span>\n <span className=\"font-medium\">{selectedBooking.providerName}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">Budget</span>\n <span className=\"font-medium tabular-nums\">\n ${selectedBooking.budget.toLocaleString()}\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">Start Date</span>\n <span className=\"font-medium\">{selectedBooking.startDate}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">End Date</span>\n <span className=\"font-medium\">{selectedBooking.endDate}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">Payment</span>\n <Badge variant=\"secondary\">{selectedBooking.paymentStatus}</Badge>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">Method</span>\n <span className=\"font-medium\">\n {selectedBooking.paymentMethod.replace('_', ' ')}\n </span>\n </div>\n </div>\n\n <div className=\"flex gap-2 border-t border-border-seam pt-3\">\n <Button className=\"flex-1\" size=\"sm\" variant=\"outline\">\n Message Provider\n </Button>\n {selectedBooking.status === 'pending' && (\n <Button className=\"flex-1\" size=\"sm\" variant=\"destructive\">\n Cancel\n </Button>\n )}\n </div>\n </div>\n </GlassCard>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|