@burdenoff/microfe-movethewheels 2026.703.3 → 2026.703.5

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.
Files changed (173) hide show
  1. package/dist/{AIAssistantPage-C3cPsWt-.js → AIAssistantPage-D2gaEL1U.js} +16 -15
  2. package/dist/AnalyticsPage-DZMlEoBi.js +194 -0
  3. package/dist/CreateOrderPage-CWHx8yys.js +490 -0
  4. package/dist/CustomerDetailsPage-nR4oZ_ju.js +615 -0
  5. package/dist/CustomersPage-Bbvwvn8k.js +205 -0
  6. package/dist/DashboardPage-DrEhNf7N.js +542 -0
  7. package/dist/DataTable-CqMUD4er.js +340 -0
  8. package/dist/DocumentsCard-edDkgnJj.js +169 -0
  9. package/dist/DriverDetailsPage-whEMOWs9.js +490 -0
  10. package/dist/DriversPage-CHF2ZEzd.js +221 -0
  11. package/dist/FinancePage-Dn9GrW-1.js +175 -0
  12. package/dist/{FleetPage-DqkFi3s3.js → FleetPage-CrtGPy-p.js} +36 -35
  13. package/dist/{FormError-BlOjnXOq.js → FormError-BKcb_wT-.js} +1 -1
  14. package/dist/{ImportExportPage-DKJRCezl.js → ImportExportPage-WYdwZomZ.js} +52 -72
  15. package/dist/InventoryPage-l5A1f753.js +259 -0
  16. package/dist/LiveTrackingPage-DhEaNKZ4.js +429 -0
  17. package/dist/MarketplacePage-B9RYOb1Y.js +426 -0
  18. package/dist/MetricCard-y3bWHKUQ.js +191 -0
  19. package/dist/Notice-DOO-tjpV.js +46 -0
  20. package/dist/OrderDetailsPage-CwYHwk1K.js +584 -0
  21. package/dist/OrdersListPage-DhZJoQeI.js +386 -0
  22. package/dist/{PageLayout-D4LDHJJq.js → PageLayout-BniRJaQ6.js} +1 -1
  23. package/dist/{ProductDetailsPage-C9uOiYLp.js → ProductDetailsPage-1Gm9JKy0.js} +56 -56
  24. package/dist/ProductsPage-C8sn-1tN.js +186 -0
  25. package/dist/ReportsPage-D1tb0dcz.js +377 -0
  26. package/dist/RouteDetailsPage-CkMyR9b_.js +610 -0
  27. package/dist/{RoutesPage-DMfoicM1.js → RoutesPage-QkENzsE5.js} +58 -47
  28. package/dist/{SettingsPage-Bn-v0mbQ.js → SettingsPage-DV3_tU_X.js} +16 -38
  29. package/dist/Sparkline-K2RMJ58u.js +16729 -0
  30. package/dist/{StatusBadge-BZurYHMy.js → StatusBadge-BNHXYvZx.js} +7 -7
  31. package/dist/TrackingPage-BZrg4RCu.js +368 -0
  32. package/dist/VehicleDetailsPage-D-8XPZ2G.js +393 -0
  33. package/dist/VehiclesPage-D5BELbtM.js +184 -0
  34. package/dist/{WarehouseDetailsPage-L_6sWigM.js → WarehouseDetailsPage-Cmg-8Frn.js} +70 -70
  35. package/dist/WarehousesPage-C1Y1M867.js +182 -0
  36. package/dist/{arrow-left-n48Y-Wis.js → arrow-left-ClW4Isj7.js} +1 -1
  37. package/dist/arrow-right-CmKOmIkh.js +11 -0
  38. package/dist/arrow-up-BJijRLAZ.js +17 -0
  39. package/dist/{box-D9Pal4HM.js → box-BT3NsRQP.js} +1 -1
  40. package/dist/boxes-B_OGvQiW.js +54 -0
  41. package/dist/{chart-column-BFIJysDR.js → chart-column-DQTTdUPk.js} +1 -1
  42. package/dist/charts-Tj_PPTfA.js +2018 -0
  43. package/dist/chevron-left-B1rZyWWb.js +8 -0
  44. package/dist/{chevron-right-EMIIyyJ1.js → chevron-right-BIcwUJZq.js} +1 -1
  45. package/dist/chunk-BdsCSvBP.js +20 -0
  46. package/dist/{circle-alert-iY24lSlp.js → circle-alert-CNkuDNc6.js} +1 -1
  47. package/dist/circle-check-Cs1OVjtV.js +13 -0
  48. package/dist/{circle-check-big-w0TrLKMF.js → circle-check-big-C-p4eNwn.js} +1 -1
  49. package/dist/{clock-DJZtdl8u.js → clock-dRGG4rkF.js} +1 -1
  50. package/dist/{dollar-sign-CgKQRz_e.js → dollar-sign-B6RoYHHz.js} +1 -1
  51. package/dist/{download-DRXxoiQR.js → download-DP04iWTd.js} +1 -1
  52. package/dist/{file-text-DWWY6_f5.js → file-text-BuDx4hZp.js} +1 -1
  53. package/dist/{filter-DloLC2eE.js → filter-BD483_8g.js} +1 -1
  54. package/dist/{wrench-CANXWxF-.js → fuel-DLH1ag04.js} +3 -6
  55. package/dist/index.css +2 -0
  56. package/dist/index.js +150 -172
  57. package/dist/{map-CkJaULb2.js → map-DNQn9fyY.js} +1 -1
  58. package/dist/{map-pin-BGiCvyCs.js → map-pin-CqN1bbT1.js} +1 -1
  59. package/dist/mapMarkers-NCV9MRKR.js +28 -0
  60. package/dist/maps-yzLqiK_C.js +5070 -0
  61. package/dist/{navigation-D36prnAn.js → navigation-CgGUt2tR.js} +1 -1
  62. package/dist/{package-BChUzpJS.js → package-DmRF7iQD.js} +1 -1
  63. package/dist/{phone-Ac9s3mo_.js → phone-Cl32EXKW.js} +1 -1
  64. package/dist/{plus-Z76q-FZm.js → plus-DlANgOFx.js} +1 -1
  65. package/dist/{refresh-cw-DKt6f5T6.js → refresh-cw-rW59D4b7.js} +1 -1
  66. package/dist/route-CWjPrtAN.js +22 -0
  67. package/dist/{save-CojywNhG.js → save-UDlog7hg.js} +1 -1
  68. package/dist/{search-CDGefqb3.js → search-DNIG5Ck7.js} +1 -1
  69. package/dist/{square-pen-CopE9GmB.js → square-pen-BcfB3Grd.js} +1 -1
  70. package/dist/{star-nHlLjDKR.js → star-CVZHFwDx.js} +1 -1
  71. package/dist/{store-BR9Voi5E.js → store-bwnmCK9f.js} +1 -1
  72. package/dist/{trash-2-BKhar6Hs.js → trash-2-B80G12d4.js} +1 -1
  73. package/dist/{trending-up-Dohgd4t_.js → trending-up-CH4R4KOa.js} +1 -1
  74. package/dist/{triangle-alert-i-XQL9Rd.js → triangle-alert-CmS8AUU6.js} +1 -1
  75. package/dist/{truck-C0v_hbLR.js → truck-9rLDncav.js} +1 -1
  76. package/dist/upload-DanJY1Vo.js +21 -0
  77. package/dist/{useAssistant-D4v3oxD3.js → useAssistant-CnEwkTLm.js} +11 -28
  78. package/dist/{useCustomers-BmTNGxiI.js → useCustomers-ChJao1ET.js} +30 -27
  79. package/dist/{useFleet-DKqXZ16m.js → useFleet-g5Yri0G8.js} +82 -70
  80. package/dist/{useImportExport-DHr-SWSw.js → useImportExport-MYwIT3rE.js} +1 -1
  81. package/dist/{useInventory-DkyiVY_A.js → useInventory-BSOn-R1c.js} +84 -39
  82. package/dist/{useOrders-Cfk1GLiq.js → useOrders-BSiqo61w.js} +59 -48
  83. package/dist/{useReports-CTqZr-AB.js → useReports-fWESfsF9.js} +41 -35
  84. package/dist/{useRoutes-BQWO607N.js → useRoutes-zD8Yp323.js} +50 -42
  85. package/dist/{useSettings-CkDH_-gM.js → useSettings-B1lsWeZ1.js} +6 -6
  86. package/dist/{useTracking-B-va1kO3.js → useTracking-BKG0I2ln.js} +3 -3
  87. package/dist/{user-jyFU3lXk.js → user-DU51el3m.js} +1 -1
  88. package/dist/user-check-IMhk-qkX.js +20 -0
  89. package/dist/{users-DZ-ilS6B.js → users-B8KhJBGg.js} +1 -1
  90. package/dist/{warehouse-6m5kXMLu.js → warehouse-CcMMioeY.js} +1 -1
  91. package/dist/wrench-Dg-sgH2e.js +8 -0
  92. package/dist/x-C4ObLVEa.js +11 -0
  93. package/package.json +5 -1
  94. package/src/movethewheels/components/DataTable.tsx +252 -84
  95. package/src/movethewheels/components/DocumentsCard.tsx +102 -0
  96. package/src/movethewheels/components/GlassPanel.tsx +71 -0
  97. package/src/movethewheels/components/MetricCard.tsx +171 -73
  98. package/src/movethewheels/components/Notice.tsx +61 -0
  99. package/src/movethewheels/components/StatusBadge.tsx +7 -6
  100. package/src/movethewheels/components/charts/ChartShell.tsx +78 -0
  101. package/src/movethewheels/components/charts/Sparkline.tsx +85 -0
  102. package/src/movethewheels/components/charts/StatusDonut.tsx +115 -0
  103. package/src/movethewheels/components/charts/TrendChart.tsx +179 -0
  104. package/src/movethewheels/components/charts/VolumeBar.tsx +97 -0
  105. package/src/movethewheels/components/charts/chartTokens.ts +43 -0
  106. package/src/movethewheels/components/charts/index.ts +9 -0
  107. package/src/movethewheels/components/index.ts +51 -1
  108. package/src/movethewheels/components/maps/LogisticsMap.tsx +94 -0
  109. package/src/movethewheels/components/maps/MapCanvas.tsx +118 -0
  110. package/src/movethewheels/components/maps/RouteMap.tsx +71 -0
  111. package/src/movethewheels/components/maps/TrackingMap.tsx +102 -0
  112. package/src/movethewheels/components/maps/index.ts +16 -0
  113. package/src/movethewheels/components/maps/leaflet-setup.ts +124 -0
  114. package/src/movethewheels/components/maps/map-markers.css +121 -0
  115. package/src/movethewheels/hooks/index.ts +9 -0
  116. package/src/movethewheels/hooks/useAnalytics.ts +109 -4
  117. package/src/movethewheels/hooks/useCustomers.ts +11 -4
  118. package/src/movethewheels/hooks/useEntityDocuments.ts +132 -0
  119. package/src/movethewheels/hooks/useFleet.ts +26 -7
  120. package/src/movethewheels/hooks/useInventory.ts +70 -7
  121. package/src/movethewheels/hooks/useOrders.ts +32 -2
  122. package/src/movethewheels/hooks/useReports.ts +14 -4
  123. package/src/movethewheels/hooks/useRoutes.ts +20 -3
  124. package/src/movethewheels/pages/AnalyticsPage.tsx +93 -73
  125. package/src/movethewheels/pages/CreateOrderPage.tsx +48 -20
  126. package/src/movethewheels/pages/CustomerDetailsPage.tsx +239 -33
  127. package/src/movethewheels/pages/CustomersPage.tsx +53 -28
  128. package/src/movethewheels/pages/DashboardPage.tsx +238 -12
  129. package/src/movethewheels/pages/DriverDetailsPage.tsx +17 -31
  130. package/src/movethewheels/pages/DriversPage.tsx +59 -31
  131. package/src/movethewheels/pages/FinancePage.tsx +103 -82
  132. package/src/movethewheels/pages/InventoryPage.tsx +15 -3
  133. package/src/movethewheels/pages/LiveTrackingPage.tsx +124 -16
  134. package/src/movethewheels/pages/MarketplacePage.tsx +265 -50
  135. package/src/movethewheels/pages/OrderDetailsPage.tsx +214 -10
  136. package/src/movethewheels/pages/OrdersListPage.tsx +118 -47
  137. package/src/movethewheels/pages/ProductsPage.tsx +52 -28
  138. package/src/movethewheels/pages/ReportsPage.tsx +88 -4
  139. package/src/movethewheels/pages/RouteDetailsPage.tsx +119 -19
  140. package/src/movethewheels/pages/RoutesPage.tsx +22 -6
  141. package/src/movethewheels/pages/SettingsPage.tsx +5 -21
  142. package/src/movethewheels/pages/TrackingPage.tsx +76 -6
  143. package/src/movethewheels/pages/VehicleDetailsPage.tsx +4 -34
  144. package/src/movethewheels/pages/VehiclesPage.tsx +58 -27
  145. package/src/movethewheels/pages/WarehouseDetailsPage.tsx +1 -1
  146. package/src/movethewheels/pages/WarehousesPage.tsx +47 -36
  147. package/src/movethewheels/types/index.ts +9 -1
  148. package/src/movethewheels/utils/download.ts +62 -0
  149. package/src/movethewheels/utils/mapMarkers.ts +58 -0
  150. package/dist/AnalyticsPage-Dz6g2MvK.js +0 -215
  151. package/dist/CreateOrderPage-C5iwnjp4.js +0 -479
  152. package/dist/CustomerDetailsPage-B6nwFyLO.js +0 -406
  153. package/dist/CustomersPage-BKxMS1AV.js +0 -161
  154. package/dist/DashboardPage-BVjQi0EB.js +0 -387
  155. package/dist/DataTable-DmciIKKM.js +0 -249
  156. package/dist/DriverDetailsPage-CN3WQ0B9.js +0 -507
  157. package/dist/DriversPage-CH9rjQC-.js +0 -165
  158. package/dist/FinancePage-DEDykSOP.js +0 -158
  159. package/dist/InventoryPage-e0ze63iX.js +0 -253
  160. package/dist/LiveTrackingPage-DCX-K-LR.js +0 -379
  161. package/dist/MarketplacePage-CIQ97_zp.js +0 -247
  162. package/dist/MetricCard-BY0QrMD4.js +0 -146
  163. package/dist/OrderDetailsPage-DYiZ-9UK.js +0 -429
  164. package/dist/OrdersListPage-Cn90P1Xx.js +0 -307
  165. package/dist/ProductsPage-QJfHHpqB.js +0 -169
  166. package/dist/ReportsPage-Zp53q3_8.js +0 -284
  167. package/dist/RouteDetailsPage-DUStXSf9.js +0 -531
  168. package/dist/TrackingPage-W-jtdM3Y.js +0 -325
  169. package/dist/VehicleDetailsPage-Bceo5oz3.js +0 -410
  170. package/dist/VehiclesPage-ApKThYeG.js +0 -165
  171. package/dist/WarehousesPage-DCbXXXWE.js +0 -162
  172. package/dist/useAnalytics-DS8n0xgg.js +0 -297
  173. /package/dist/{formatters-CF2S2l1m.js → formatters-FWJDwTFv.js} +0 -0
@@ -0,0 +1,115 @@
1
+ import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
2
+ import { cn } from '../../utils/cn';
3
+ import { seriesColor } from './chartTokens';
4
+ import { ChartEmpty, ChartTooltip } from './ChartShell';
5
+
6
+ export interface DonutSlice {
7
+ name: string;
8
+ value: number;
9
+ /** Optional explicit color (design-token string). */
10
+ color?: string;
11
+ }
12
+
13
+ interface StatusDonutProps {
14
+ data: DonutSlice[];
15
+ height?: number;
16
+ className?: string;
17
+ /** Big number rendered in the donut hole (e.g. a total). */
18
+ centerLabel?: string | number;
19
+ centerSubLabel?: string;
20
+ showLegend?: boolean;
21
+ emptyMessage?: string;
22
+ valueFormatter?: (value: number) => string;
23
+ }
24
+
25
+ /**
26
+ * StatusDonut — donut chart with a centered total and a token-styled legend.
27
+ * Responsive; renders an honest empty state when every slice is zero/absent.
28
+ */
29
+ export function StatusDonut({
30
+ data,
31
+ height = 240,
32
+ className,
33
+ centerLabel,
34
+ centerSubLabel,
35
+ showLegend = true,
36
+ emptyMessage = 'No breakdown yet',
37
+ valueFormatter,
38
+ }: StatusDonutProps) {
39
+ const slices = (data ?? []).filter((d) => d.value > 0);
40
+ const total = slices.reduce((sum, d) => sum + d.value, 0);
41
+
42
+ if (slices.length === 0 || total === 0) {
43
+ return <ChartEmpty message={emptyMessage} height={height} className={className} />;
44
+ }
45
+
46
+ const colorOf = (slice: DonutSlice, i: number) => slice.color ?? seriesColor(i);
47
+
48
+ return (
49
+ <div className={cn('flex flex-col gap-4 sm:flex-row sm:items-center', className)}>
50
+ <div className="relative mx-auto" style={{ width: height, height, maxWidth: '100%' }}>
51
+ <ResponsiveContainer width="100%" height="100%">
52
+ <PieChart>
53
+ <Pie
54
+ data={slices}
55
+ dataKey="value"
56
+ nameKey="name"
57
+ innerRadius="62%"
58
+ outerRadius="100%"
59
+ paddingAngle={2}
60
+ stroke="none"
61
+ isAnimationActive={false}
62
+ >
63
+ {slices.map((slice, i) => (
64
+ <Cell key={slice.name} fill={colorOf(slice, i)} />
65
+ ))}
66
+ </Pie>
67
+ <Tooltip
68
+ content={
69
+ <ChartTooltip
70
+ formatter={(v) =>
71
+ valueFormatter ? valueFormatter(Number(v)) : String(v)
72
+ }
73
+ />
74
+ }
75
+ />
76
+ </PieChart>
77
+ </ResponsiveContainer>
78
+ {centerLabel != null && (
79
+ <div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center">
80
+ <span className="text-2xl font-bold tabular-nums text-text-primary">
81
+ {centerLabel}
82
+ </span>
83
+ {centerSubLabel && (
84
+ <span className="text-xs text-text-secondary">{centerSubLabel}</span>
85
+ )}
86
+ </div>
87
+ )}
88
+ </div>
89
+
90
+ {showLegend && (
91
+ <ul className="flex flex-1 flex-col gap-2">
92
+ {slices.map((slice, i) => {
93
+ const pct = total > 0 ? Math.round((slice.value / total) * 100) : 0;
94
+ return (
95
+ <li key={slice.name} className="flex items-center gap-2 text-sm">
96
+ <span
97
+ aria-hidden="true"
98
+ className="h-2.5 w-2.5 shrink-0 rounded-full"
99
+ style={{ background: colorOf(slice, i) }}
100
+ />
101
+ <span className="text-text-secondary">{slice.name}</span>
102
+ <span className="ml-auto font-semibold tabular-nums text-text-primary">
103
+ {valueFormatter ? valueFormatter(slice.value) : slice.value}
104
+ </span>
105
+ <span className="w-10 text-right text-xs tabular-nums text-text-secondary">
106
+ {pct}%
107
+ </span>
108
+ </li>
109
+ );
110
+ })}
111
+ </ul>
112
+ )}
113
+ </div>
114
+ );
115
+ }
@@ -0,0 +1,179 @@
1
+ import { useId } from 'react';
2
+ import {
3
+ Area,
4
+ AreaChart,
5
+ CartesianGrid,
6
+ Legend,
7
+ Line,
8
+ LineChart,
9
+ ResponsiveContainer,
10
+ Tooltip,
11
+ XAxis,
12
+ YAxis,
13
+ } from 'recharts';
14
+ import { chartTokens, seriesColor } from './chartTokens';
15
+ import { ChartEmpty, ChartTooltip } from './ChartShell';
16
+
17
+ export interface TrendSeries {
18
+ /** Key into each data row. */
19
+ dataKey: string;
20
+ /** Legend / tooltip label. */
21
+ name: string;
22
+ /** Explicit color (design-token string). Defaults to the categorical palette. */
23
+ color?: string;
24
+ }
25
+
26
+ interface TrendChartProps {
27
+ /** Row objects keyed by `xKey` + each series `dataKey`. */
28
+ data: Array<Record<string, string | number>>;
29
+ series: TrendSeries[];
30
+ /** Row key for the X axis (e.g. "date"). */
31
+ xKey: string;
32
+ variant?: 'area' | 'line';
33
+ height?: number;
34
+ className?: string;
35
+ showLegend?: boolean;
36
+ showGrid?: boolean;
37
+ emptyMessage?: string;
38
+ /** Optional Y/tooltip value formatter. */
39
+ valueFormatter?: (value: number | string, name: string) => string;
40
+ }
41
+
42
+ /**
43
+ * TrendChart — multi-series area/line chart for trends over time. Fully
44
+ * responsive (ResponsiveContainer), token-colored, with an honest empty state.
45
+ */
46
+ export function TrendChart({
47
+ data,
48
+ series,
49
+ xKey,
50
+ variant = 'area',
51
+ height = 300,
52
+ className,
53
+ showLegend = true,
54
+ showGrid = true,
55
+ emptyMessage = 'No trend data yet',
56
+ valueFormatter,
57
+ }: TrendChartProps) {
58
+ const rawId = useId().replace(/:/g, '');
59
+
60
+ if (!data || data.length === 0 || series.length === 0) {
61
+ return <ChartEmpty message={emptyMessage} height={height} className={className} />;
62
+ }
63
+
64
+ const axisTick = { fill: chartTokens.axis, fontSize: 12 };
65
+ const colorFor = (s: TrendSeries, i: number) => s.color ?? seriesColor(i);
66
+
67
+ return (
68
+ <div className={className} style={{ width: '100%', height }}>
69
+ <ResponsiveContainer width="100%" height="100%">
70
+ {variant === 'area' ? (
71
+ <AreaChart data={data} margin={{ top: 8, right: 12, bottom: 4, left: -8 }}>
72
+ <defs>
73
+ {series.map((s, i) => (
74
+ <linearGradient
75
+ key={s.dataKey}
76
+ id={`${rawId}-${s.dataKey}`}
77
+ x1="0"
78
+ y1="0"
79
+ x2="0"
80
+ y2="1"
81
+ >
82
+ <stop offset="0%" stopColor={colorFor(s, i)} stopOpacity={0.28} />
83
+ <stop offset="100%" stopColor={colorFor(s, i)} stopOpacity={0.02} />
84
+ </linearGradient>
85
+ ))}
86
+ </defs>
87
+ {showGrid && (
88
+ <CartesianGrid strokeDasharray="3 3" stroke={chartTokens.grid} vertical={false} />
89
+ )}
90
+ <XAxis
91
+ dataKey={xKey}
92
+ tick={axisTick}
93
+ tickLine={false}
94
+ axisLine={{ stroke: chartTokens.grid }}
95
+ minTickGap={24}
96
+ />
97
+ <YAxis
98
+ tick={axisTick}
99
+ tickLine={false}
100
+ axisLine={false}
101
+ width={44}
102
+ tickFormatter={
103
+ valueFormatter ? (v: number) => valueFormatter(v, '') : undefined
104
+ }
105
+ />
106
+ <Tooltip
107
+ cursor={{ stroke: chartTokens.axis, strokeOpacity: 0.3 }}
108
+ content={<ChartTooltip formatter={valueFormatter} />}
109
+ />
110
+ {showLegend && (
111
+ <Legend
112
+ iconType="circle"
113
+ wrapperStyle={{ fontSize: 12, color: chartTokens.textSecondary }}
114
+ />
115
+ )}
116
+ {series.map((s, i) => (
117
+ <Area
118
+ key={s.dataKey}
119
+ type="monotone"
120
+ dataKey={s.dataKey}
121
+ name={s.name}
122
+ stroke={colorFor(s, i)}
123
+ strokeWidth={2.5}
124
+ fill={`url(#${rawId}-${s.dataKey})`}
125
+ isAnimationActive={false}
126
+ activeDot={{ r: 4, strokeWidth: 0 }}
127
+ />
128
+ ))}
129
+ </AreaChart>
130
+ ) : (
131
+ <LineChart data={data} margin={{ top: 8, right: 12, bottom: 4, left: -8 }}>
132
+ {showGrid && (
133
+ <CartesianGrid strokeDasharray="3 3" stroke={chartTokens.grid} vertical={false} />
134
+ )}
135
+ <XAxis
136
+ dataKey={xKey}
137
+ tick={axisTick}
138
+ tickLine={false}
139
+ axisLine={{ stroke: chartTokens.grid }}
140
+ minTickGap={24}
141
+ />
142
+ <YAxis
143
+ tick={axisTick}
144
+ tickLine={false}
145
+ axisLine={false}
146
+ width={44}
147
+ tickFormatter={
148
+ valueFormatter ? (v: number) => valueFormatter(v, '') : undefined
149
+ }
150
+ />
151
+ <Tooltip
152
+ cursor={{ stroke: chartTokens.axis, strokeOpacity: 0.3 }}
153
+ content={<ChartTooltip formatter={valueFormatter} />}
154
+ />
155
+ {showLegend && (
156
+ <Legend
157
+ iconType="circle"
158
+ wrapperStyle={{ fontSize: 12, color: chartTokens.textSecondary }}
159
+ />
160
+ )}
161
+ {series.map((s, i) => (
162
+ <Line
163
+ key={s.dataKey}
164
+ type="monotone"
165
+ dataKey={s.dataKey}
166
+ name={s.name}
167
+ stroke={colorFor(s, i)}
168
+ strokeWidth={2.5}
169
+ dot={false}
170
+ isAnimationActive={false}
171
+ activeDot={{ r: 4, strokeWidth: 0 }}
172
+ />
173
+ ))}
174
+ </LineChart>
175
+ )}
176
+ </ResponsiveContainer>
177
+ </div>
178
+ );
179
+ }
@@ -0,0 +1,97 @@
1
+ import {
2
+ Bar,
3
+ BarChart,
4
+ CartesianGrid,
5
+ Legend,
6
+ ResponsiveContainer,
7
+ Tooltip,
8
+ XAxis,
9
+ YAxis,
10
+ } from 'recharts';
11
+ import { chartTokens, seriesColor } from './chartTokens';
12
+ import { ChartEmpty, ChartTooltip } from './ChartShell';
13
+
14
+ export interface VolumeSeries {
15
+ dataKey: string;
16
+ name: string;
17
+ color?: string;
18
+ }
19
+
20
+ interface VolumeBarProps {
21
+ data: Array<Record<string, string | number>>;
22
+ series: VolumeSeries[];
23
+ xKey: string;
24
+ height?: number;
25
+ className?: string;
26
+ showLegend?: boolean;
27
+ stacked?: boolean;
28
+ emptyMessage?: string;
29
+ valueFormatter?: (value: number | string, name: string) => string;
30
+ }
31
+
32
+ /**
33
+ * VolumeBar — grouped/stacked bar chart (e.g. weekly order volume, revenue by
34
+ * month). Responsive, token-colored, rounded bars, honest empty state.
35
+ */
36
+ export function VolumeBar({
37
+ data,
38
+ series,
39
+ xKey,
40
+ height = 280,
41
+ className,
42
+ showLegend = true,
43
+ stacked = false,
44
+ emptyMessage = 'No volume data yet',
45
+ valueFormatter,
46
+ }: VolumeBarProps) {
47
+ if (!data || data.length === 0 || series.length === 0) {
48
+ return <ChartEmpty message={emptyMessage} height={height} className={className} />;
49
+ }
50
+
51
+ const axisTick = { fill: chartTokens.axis, fontSize: 12 };
52
+
53
+ return (
54
+ <div className={className} style={{ width: '100%', height }}>
55
+ <ResponsiveContainer width="100%" height="100%">
56
+ <BarChart data={data} margin={{ top: 8, right: 12, bottom: 4, left: -8 }} barGap={4}>
57
+ <CartesianGrid strokeDasharray="3 3" stroke={chartTokens.grid} vertical={false} />
58
+ <XAxis
59
+ dataKey={xKey}
60
+ tick={axisTick}
61
+ tickLine={false}
62
+ axisLine={{ stroke: chartTokens.grid }}
63
+ />
64
+ <YAxis
65
+ tick={axisTick}
66
+ tickLine={false}
67
+ axisLine={false}
68
+ width={44}
69
+ tickFormatter={valueFormatter ? (v: number) => valueFormatter(v, '') : undefined}
70
+ />
71
+ <Tooltip
72
+ cursor={{ fill: chartTokens.axis, fillOpacity: 0.08 }}
73
+ content={<ChartTooltip formatter={valueFormatter} />}
74
+ />
75
+ {showLegend && (
76
+ <Legend
77
+ iconType="circle"
78
+ wrapperStyle={{ fontSize: 12, color: chartTokens.textSecondary }}
79
+ />
80
+ )}
81
+ {series.map((s, i) => (
82
+ <Bar
83
+ key={s.dataKey}
84
+ dataKey={s.dataKey}
85
+ name={s.name}
86
+ fill={s.color ?? seriesColor(i)}
87
+ radius={stacked ? [0, 0, 0, 0] : [4, 4, 0, 0]}
88
+ stackId={stacked ? 'stack' : undefined}
89
+ maxBarSize={48}
90
+ isAnimationActive={false}
91
+ />
92
+ ))}
93
+ </BarChart>
94
+ </ResponsiveContainer>
95
+ </div>
96
+ );
97
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Chart color tokens.
3
+ *
4
+ * Recharts needs explicit color strings on its series props (fill/stroke) where
5
+ * Tailwind token classes cannot reach the SVG. We source every one of those
6
+ * colors from the fe-libs design tokens via `var(--color-*)` so charts stay
7
+ * theme-aware (light/dark + product themes) and never introduce raw palette
8
+ * colors. The CSS variables resolve to full color values in the app-shell.
9
+ */
10
+
11
+ export const chartTokens = {
12
+ /** Product brand orange — primary accent / first data series. */
13
+ brandPrimary: 'var(--color-brand-primary)',
14
+ /** Brand secondary (orange-hover) — used sparingly for gradients. */
15
+ brandSecondary: 'var(--color-brand-secondary)',
16
+ /** Brand blue — the natural counterpart series to the orange. */
17
+ brandBlue: 'var(--color-chart-1)',
18
+ /** Categorical series palette (theme-aware). */
19
+ series: [
20
+ 'var(--color-brand-primary)',
21
+ 'var(--color-chart-1)',
22
+ 'var(--color-chart-2)',
23
+ 'var(--color-chart-3)',
24
+ 'var(--color-chart-4)',
25
+ 'var(--color-chart-5)',
26
+ 'var(--color-chart-6)',
27
+ 'var(--color-chart-7)',
28
+ 'var(--color-chart-8)',
29
+ ],
30
+ grid: 'var(--color-chart-grid)',
31
+ axis: 'var(--color-chart-axis)',
32
+ positive: 'var(--color-chart-positive)',
33
+ negative: 'var(--color-chart-negative)',
34
+ surface: 'var(--color-bg-surface)',
35
+ border: 'var(--color-border-seam)',
36
+ textSecondary: 'var(--color-text-secondary)',
37
+ textPrimary: 'var(--color-text-primary)',
38
+ } as const;
39
+
40
+ /** Resolve a categorical color by index, wrapping around the palette. */
41
+ export function seriesColor(index: number): string {
42
+ return chartTokens.series[index % chartTokens.series.length];
43
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Chart primitives (recharts) — token-colored, responsive, honest empty states.
3
+ */
4
+ export { Sparkline, type SparklinePoint } from './Sparkline';
5
+ export { TrendChart, type TrendSeries } from './TrendChart';
6
+ export { VolumeBar, type VolumeSeries } from './VolumeBar';
7
+ export { StatusDonut, type DonutSlice } from './StatusDonut';
8
+ export { ChartEmpty, ChartTooltip } from './ChartShell';
9
+ export { chartTokens, seriesColor } from './chartTokens';
@@ -11,7 +11,50 @@ export * from './ui';
11
11
  export { PageLayout, PageSection, EmptyState, LoadingState, ErrorState } from './PageLayout';
12
12
 
13
13
  // Metric components
14
- export { MetricCard, MetricCardSkeleton, MetricGrid } from './MetricCard';
14
+ export {
15
+ MetricCard,
16
+ MetricCardSkeleton,
17
+ MetricGrid,
18
+ type MetricAccent,
19
+ type MetricDelta,
20
+ } from './MetricCard';
21
+
22
+ // Surface primitive
23
+ export { GlassPanel } from './GlassPanel';
24
+
25
+ // Charts (recharts)
26
+ export {
27
+ Sparkline,
28
+ TrendChart,
29
+ VolumeBar,
30
+ StatusDonut,
31
+ ChartEmpty,
32
+ ChartTooltip,
33
+ chartTokens,
34
+ seriesColor,
35
+ type SparklinePoint,
36
+ type TrendSeries,
37
+ type VolumeSeries,
38
+ type DonutSlice,
39
+ } from './charts';
40
+
41
+ // Maps (react-leaflet + OpenStreetMap)
42
+ export {
43
+ LogisticsMap,
44
+ RouteMap,
45
+ TrackingMap,
46
+ MapCanvas,
47
+ createPinIcon,
48
+ createStopIcon,
49
+ createCurrentIcon,
50
+ isRealCoord,
51
+ type LogisticsMarker,
52
+ type LogisticsRoute,
53
+ type RouteStop,
54
+ type TrackPoint,
55
+ type TrackEndpoint,
56
+ type MapMarkerStatus,
57
+ } from './maps';
15
58
 
16
59
  // Status components
17
60
  export {
@@ -24,3 +67,10 @@ export {
24
67
 
25
68
  // Table components
26
69
  export { DataTable, type Column } from './DataTable';
70
+
71
+ // Feedback components
72
+ export { FormError } from './FormError';
73
+ export { Notice, useNotice } from './Notice';
74
+
75
+ // Documents (reuse of wspace-files-svc)
76
+ export { DocumentsCard } from './DocumentsCard';
@@ -0,0 +1,94 @@
1
+ import { useMemo } from 'react';
2
+ import { Marker, Polyline, Popup } from 'react-leaflet';
3
+ import { MapCanvas } from './MapCanvas';
4
+ import { createPinIcon, isRealCoord, type MapMarkerStatus } from './leaflet-setup';
5
+
6
+ export interface LogisticsMarker {
7
+ id: string;
8
+ lat: number;
9
+ lng: number;
10
+ label: string;
11
+ status?: MapMarkerStatus;
12
+ sublabel?: string;
13
+ }
14
+
15
+ export interface LogisticsRoute {
16
+ points: Array<[number, number]>;
17
+ color?: string;
18
+ }
19
+
20
+ interface LogisticsMapProps {
21
+ markers: LogisticsMarker[];
22
+ routes?: LogisticsRoute[];
23
+ height?: number | string;
24
+ className?: string;
25
+ fitToMarkers?: boolean;
26
+ emptyMessage?: string;
27
+ }
28
+
29
+ /**
30
+ * LogisticsMap — an OSM map of brand-styled vehicle/asset markers plus optional
31
+ * route polylines. Auto-fits to the visible markers. Coordinates at null-island
32
+ * (0,0) or non-finite values are treated as "no location" and filtered out, so
33
+ * the map is never dumped in the ocean. Empty markers → honest empty state.
34
+ */
35
+ export function LogisticsMap({
36
+ markers,
37
+ routes = [],
38
+ height = 420,
39
+ className,
40
+ fitToMarkers = true,
41
+ emptyMessage = 'No live locations yet',
42
+ }: LogisticsMapProps) {
43
+ const validMarkers = useMemo(
44
+ () => markers.filter((m) => isRealCoord(m.lat, m.lng)),
45
+ [markers]
46
+ );
47
+
48
+ const validRoutes = useMemo(
49
+ () =>
50
+ routes
51
+ .map((r) => ({
52
+ ...r,
53
+ points: r.points.filter(([lat, lng]) => isRealCoord(lat, lng)),
54
+ }))
55
+ .filter((r) => r.points.length >= 2),
56
+ [routes]
57
+ );
58
+
59
+ const fitPoints = useMemo<Array<[number, number]>>(() => {
60
+ const pts: Array<[number, number]> = validMarkers.map((m) => [m.lat, m.lng]);
61
+ validRoutes.forEach((r) => pts.push(...r.points));
62
+ return pts;
63
+ }, [validMarkers, validRoutes]);
64
+
65
+ return (
66
+ <MapCanvas
67
+ points={fitPoints}
68
+ height={height}
69
+ className={className}
70
+ fitToMarkers={fitToMarkers}
71
+ emptyState={<p className="text-sm text-text-secondary">{emptyMessage}</p>}
72
+ >
73
+ {validRoutes.map((route, i) => (
74
+ <Polyline
75
+ key={i}
76
+ positions={route.points}
77
+ pathOptions={{
78
+ color: route.color ?? 'var(--color-chart-1)',
79
+ weight: 4,
80
+ opacity: 0.85,
81
+ }}
82
+ />
83
+ ))}
84
+ {validMarkers.map((m) => (
85
+ <Marker key={m.id} position={[m.lat, m.lng]} icon={createPinIcon(m.status ?? 'default')}>
86
+ <Popup>
87
+ <span className="font-semibold">{m.label}</span>
88
+ {m.sublabel && <span className="block text-text-secondary">{m.sublabel}</span>}
89
+ </Popup>
90
+ </Marker>
91
+ ))}
92
+ </MapCanvas>
93
+ );
94
+ }