@sybilion/uilib 1.2.26 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/ui/Chart/Chart.js +5 -0
- package/dist/esm/components/ui/Chart/components/BaseChartWrapper.js +7 -32
- package/dist/esm/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.js +21 -0
- package/dist/esm/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl.js +7 -0
- package/dist/esm/components/ui/Chart/lightweight/LightweightForecastChart.js +460 -0
- package/dist/esm/components/ui/Chart/lightweight/LightweightForecastChart.styl.js +7 -0
- package/dist/esm/components/ui/Chart/lightweight/chartTime.js +16 -0
- package/dist/esm/components/ui/Chart/lightweight/lightweightForecastChart.helpers.js +114 -0
- package/dist/esm/components/ui/Chart/lightweight/quantileBandCustomSeries.js +147 -0
- package/dist/esm/components/ui/Chart/quantileBandConeChartData.js +131 -0
- package/dist/esm/components/ui/Chart/tools/chartPlotGeometry.js +65 -0
- package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.js +37 -1
- package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.js +5 -2
- package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.js +205 -0
- package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.js +7 -0
- package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.js +37 -0
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.js +1 -0
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.js +7 -60
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.js +2 -2
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.js +1 -0
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/useChartYRange.js +2 -4
- package/dist/esm/components/ui/ChartAreaInteractive/overlays/useQuantileBands.js +4 -102
- package/dist/esm/components/ui/TimeRangeControls/TimeRangeControls.js +7 -2
- package/dist/esm/components/ui/WorldMap/WorldMap.js +11 -0
- package/dist/esm/components/ui/WorldMap/WorldMap.styl.js +7 -0
- package/dist/esm/components/widgets/DriverCard/DriverCard.js +89 -0
- package/dist/esm/components/widgets/DriverCard/DriverCard.styl.js +7 -0
- package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.js +83 -0
- package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.styl.js +7 -0
- package/dist/esm/components/widgets/DriverCard/driverPerformanceChartData.js +50 -0
- package/dist/esm/components/widgets/DriverMap/DriverMap.js +2 -2
- package/dist/esm/components/widgets/DriverMap/DriverMap.styl.js +2 -2
- package/dist/esm/index.js +4 -2
- package/dist/esm/types/src/components/ui/Chart/Chart.d.ts +2 -0
- package/dist/esm/types/src/components/ui/Chart/components/BaseChartWrapper.d.ts +2 -1
- package/dist/esm/types/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.d.ts +14 -0
- package/dist/esm/types/src/components/ui/Chart/lightweight/LightweightForecastChart.d.ts +26 -0
- package/dist/esm/types/src/components/ui/Chart/lightweight/chartTime.d.ts +5 -0
- package/dist/esm/types/src/components/ui/Chart/lightweight/lightweightForecastChart.helpers.d.ts +13 -0
- package/dist/esm/types/src/components/ui/Chart/lightweight/quantileBandCustomSeries.d.ts +24 -0
- package/dist/esm/types/src/components/ui/Chart/quantileBandConeChartData.d.ts +7 -0
- package/dist/esm/types/src/components/ui/Chart/tools/chartPlotGeometry.d.ts +30 -0
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.d.ts +1 -1
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.d.ts +11 -2
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.d.ts +2 -2
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.d.ts +15 -0
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.d.ts +14 -0
- package/dist/esm/types/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Page/PageColumns/PageColumns.d.ts +1 -1
- package/dist/esm/types/src/components/ui/TimeRangeControls/TimeRangeControls.d.ts +5 -7
- package/dist/esm/types/src/components/ui/WorldMap/WorldMap.d.ts +4 -0
- package/dist/esm/types/src/components/ui/WorldMap/index.d.ts +2 -0
- package/dist/esm/types/src/components/widgets/DriverCard/DriverCard.d.ts +9 -0
- package/dist/esm/types/src/components/widgets/DriverCard/DriverPerformanceChart.d.ts +5 -0
- package/dist/esm/types/src/components/widgets/DriverCard/driverPerformanceChartData.d.ts +7 -0
- package/dist/esm/types/src/components/widgets/DriverCard/index.d.ts +1 -0
- package/dist/esm/types/src/components/widgets/DriverMap/index.d.ts +0 -2
- package/dist/esm/types/src/docs/pages/LightweightChartPage.d.ts +1 -0
- package/dist/esm/types/src/docs/pages/PageColumnsPage.d.ts +1 -0
- package/dist/esm/types/src/docs/pages/WorldMapPage.d.ts +1 -0
- package/dist/esm/types/src/index.d.ts +2 -0
- package/package.json +3 -2
- package/src/components/ui/Chart/Chart.tsx +9 -0
- package/src/components/ui/Chart/components/BaseChartWrapper.tsx +8 -41
- package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl +60 -0
- package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl.d.ts +15 -0
- package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.tsx +66 -0
- package/src/components/ui/Chart/lightweight/LightweightForecastChart.styl +25 -0
- package/src/components/ui/Chart/lightweight/LightweightForecastChart.styl.d.ts +11 -0
- package/src/components/ui/Chart/lightweight/LightweightForecastChart.tsx +721 -0
- package/src/components/ui/Chart/lightweight/chartTime.ts +18 -0
- package/src/components/ui/Chart/lightweight/lightweightForecastChart.helpers.ts +141 -0
- package/src/components/ui/Chart/lightweight/quantileBandCustomSeries.ts +215 -0
- package/src/components/ui/Chart/quantileBandConeChartData.ts +171 -0
- package/src/components/ui/Chart/tools/chartPlotGeometry.ts +89 -0
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.ts +44 -2
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.tsx +14 -1
- package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.ts +2 -3
- package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl +21 -0
- package/src/components/{widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.d.ts → ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.d.ts} +3 -3
- package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.tsx +285 -0
- package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.ts +55 -0
- package/src/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.ts +1 -0
- package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl +2 -7
- package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.d.ts +0 -1
- package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.tsx +7 -71
- package/src/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.ts +1 -0
- package/src/components/ui/ChartAreaInteractive/overlays/useChartYRange.ts +2 -3
- package/src/components/ui/ChartAreaInteractive/overlays/useQuantileBands.ts +5 -131
- package/src/components/ui/Page/PageColumns/PageColumns.tsx +1 -1
- package/src/components/ui/TimeRangeControls/TimeRangeControls.tsx +16 -17
- package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl → ui/WorldMap/WorldMap.styl} +1 -3
- package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl.d.ts → ui/WorldMap/WorldMap.styl.d.ts} +1 -1
- package/src/components/ui/WorldMap/WorldMap.tsx +22 -0
- package/src/components/ui/WorldMap/index.ts +2 -0
- package/src/components/widgets/DriverCard/DriverCard.styl +169 -0
- package/src/components/widgets/DriverCard/DriverCard.styl.d.ts +40 -0
- package/src/components/widgets/DriverCard/DriverCard.tsx +219 -0
- package/src/components/widgets/DriverCard/DriverPerformanceChart.styl +43 -0
- package/src/components/widgets/DriverCard/DriverPerformanceChart.styl.d.ts +13 -0
- package/src/components/widgets/DriverCard/DriverPerformanceChart.tsx +150 -0
- package/src/components/widgets/DriverCard/driverPerformanceChartData.ts +64 -0
- package/src/components/widgets/DriverCard/index.ts +1 -0
- package/src/components/widgets/DriverMap/DriverIcon/DriverIcon.tsx +0 -2
- package/src/components/widgets/DriverMap/DriverMap.styl +6 -1
- package/src/components/widgets/DriverMap/DriverMap.styl.d.ts +1 -0
- package/src/components/widgets/DriverMap/DriverMap.tsx +2 -4
- package/src/components/widgets/DriverMap/driverCategoryIcon.tsx +0 -2
- package/src/components/widgets/DriverMap/index.ts +0 -2
- package/src/declarations.d.ts +2 -0
- package/src/docs/config/webpack.config.js +26 -3
- package/src/docs/index.tsx +1 -1
- package/src/docs/pages/ChartAreaInteractivePage.tsx +2 -3
- package/src/docs/pages/DriverMapPage.tsx +214 -60
- package/src/docs/pages/LightweightChartPage.styl +18 -0
- package/src/docs/pages/LightweightChartPage.styl.d.ts +10 -0
- package/src/docs/pages/LightweightChartPage.tsx +195 -0
- package/src/docs/pages/PageColumnsPage.tsx +92 -0
- package/src/docs/pages/TimeRangeControlsPage.tsx +2 -3
- package/src/docs/pages/WorldMapPage.styl +14 -0
- package/src/docs/pages/WorldMapPage.styl.d.ts +8 -0
- package/src/docs/pages/WorldMapPage.tsx +26 -0
- package/src/docs/registry.ts +19 -1
- package/src/index.ts +2 -0
- package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.js +0 -8
- package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.js +0 -7
- package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.js +0 -10
- package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.styl.js +0 -7
- package/dist/esm/types/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.d.ts +0 -1
- package/dist/esm/types/src/components/widgets/DriverMap/MapBackground/MapBackground.d.ts +0 -1
- package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl +0 -24
- package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.tsx +0 -11
- package/src/components/widgets/DriverMap/MapBackground/MapBackground.tsx +0 -18
- /package/dist/esm/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg.js +0 -0
- /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg +0 -0
- /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/mapAspect.mixin.styl +0 -0
- /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/mapAspect.mixin.styl.d.ts +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { CoffeeIcon, TrendDownIcon, TrendUpIcon } from '@phosphor-icons/react';
|
|
4
|
+
|
|
5
|
+
import { Badge } from '../../ui/Badge';
|
|
6
|
+
import { Card, CardContent } from '../../ui/Card';
|
|
7
|
+
import { LabelWithId } from '../../ui/LabelWithId';
|
|
8
|
+
import { Skeleton } from '../../ui/Skeleton';
|
|
9
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/Tooltip';
|
|
10
|
+
import { getCategoryIcon } from '../DriverMap/driverCategoryIcon';
|
|
11
|
+
import type { DriverData } from '../DriverMap/driverMapGeography';
|
|
12
|
+
import S from './DriverCard.styl';
|
|
13
|
+
import { DriverPerformanceChart } from './DriverPerformanceChart';
|
|
14
|
+
|
|
15
|
+
export interface DriverCardProps {
|
|
16
|
+
selectedDriver: DriverData | null;
|
|
17
|
+
isLoading: boolean;
|
|
18
|
+
inQueue?: boolean;
|
|
19
|
+
driverSelector?: React.ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function DriverCard({
|
|
23
|
+
selectedDriver,
|
|
24
|
+
isLoading,
|
|
25
|
+
inQueue = false,
|
|
26
|
+
driverSelector,
|
|
27
|
+
}: DriverCardProps) {
|
|
28
|
+
const importanceDisplay = useMemo(() => {
|
|
29
|
+
if (!selectedDriver) return '';
|
|
30
|
+
|
|
31
|
+
let importance = 0;
|
|
32
|
+
if (
|
|
33
|
+
selectedDriver.rawImportance &&
|
|
34
|
+
typeof selectedDriver.rawImportance === 'object'
|
|
35
|
+
) {
|
|
36
|
+
const importanceObj = selectedDriver.rawImportance as any;
|
|
37
|
+
if (importanceObj.overall && typeof importanceObj.overall === 'object') {
|
|
38
|
+
const overall = importanceObj.overall as any;
|
|
39
|
+
if (typeof overall.mean === 'number') {
|
|
40
|
+
importance = Math.round(overall.mean);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return importance > 0
|
|
46
|
+
? `${importance}%`
|
|
47
|
+
: `${selectedDriver.importance.toFixed(1)}%`;
|
|
48
|
+
}, [selectedDriver]);
|
|
49
|
+
|
|
50
|
+
const regionDisplay = React.useMemo(() => {
|
|
51
|
+
if (!selectedDriver) return '';
|
|
52
|
+
|
|
53
|
+
// Check for src_region and tgt_region properties first
|
|
54
|
+
const driver = selectedDriver as any;
|
|
55
|
+
const srcRegion = driver.src_region;
|
|
56
|
+
const tgtRegion = driver.tgt_region;
|
|
57
|
+
const srcName = srcRegion?.name;
|
|
58
|
+
const tgtName = tgtRegion?.name;
|
|
59
|
+
|
|
60
|
+
// If both src_region and tgt_region have valid names
|
|
61
|
+
if (srcName && tgtName) {
|
|
62
|
+
// If source and target are the same, just show one
|
|
63
|
+
if (srcName === tgtName) {
|
|
64
|
+
return srcName;
|
|
65
|
+
}
|
|
66
|
+
return `${srcName} → ${tgtName}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// If only src_region has a valid name
|
|
70
|
+
if (srcName) {
|
|
71
|
+
if (srcName === 'World') {
|
|
72
|
+
return 'World';
|
|
73
|
+
}
|
|
74
|
+
return `${srcName} → World`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// If only tgt_region has a valid name
|
|
78
|
+
if (tgtName) {
|
|
79
|
+
if (tgtName === 'World') {
|
|
80
|
+
return 'World';
|
|
81
|
+
}
|
|
82
|
+
return `World → ${tgtName}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (selectedDriver.region.length === 1) return selectedDriver.region[0];
|
|
86
|
+
|
|
87
|
+
if (selectedDriver.region.length > 1) {
|
|
88
|
+
return (
|
|
89
|
+
selectedDriver.region
|
|
90
|
+
// .filter(region => region !== 'World')
|
|
91
|
+
.join(' → ')
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Fallback to "World" when no region information is available
|
|
96
|
+
return 'World';
|
|
97
|
+
}, [selectedDriver]);
|
|
98
|
+
|
|
99
|
+
if (inQueue) {
|
|
100
|
+
return (
|
|
101
|
+
<Card className={S.root}>
|
|
102
|
+
<CardContent centered fullHeight noScroll>
|
|
103
|
+
<div className={S.queueMessage}>
|
|
104
|
+
<CoffeeIcon size={24} weight="regular" />
|
|
105
|
+
<div>
|
|
106
|
+
<p>Retrieving drivers data.</p>
|
|
107
|
+
<p>This may take a while.</p>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</CardContent>
|
|
111
|
+
</Card>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (isLoading) {
|
|
116
|
+
return (
|
|
117
|
+
<Card className={S.root}>
|
|
118
|
+
<CardContent noScroll>
|
|
119
|
+
<div className={S.skeletonContainer}>
|
|
120
|
+
<Skeleton className={`${S.skeletonItem} ${S.hs} ${S.w75}`} />
|
|
121
|
+
<Skeleton className={`${S.skeletonItem} ${S.hm} ${S.w50}`} />
|
|
122
|
+
<Skeleton className={`${S.skeletonItem} ${S.hs} ${S.w33}`} />
|
|
123
|
+
<div className={S.skeletonGroup}>
|
|
124
|
+
<Skeleton className={`${S.skeletonItem} ${S.hs} ${S.wFull}`} />
|
|
125
|
+
<Skeleton className={`${S.skeletonItem} ${S.hs} ${S.w75}`} />
|
|
126
|
+
<Skeleton className={`${S.skeletonItem} ${S.hs} ${S.w50}`} />
|
|
127
|
+
</div>
|
|
128
|
+
<Skeleton className={`${S.skeletonItem} ${S.hl} ${S.wFull}`} />
|
|
129
|
+
<div className={S.skeletonGroup}>
|
|
130
|
+
<Skeleton className={`${S.skeletonItem} ${S.hs} ${S.wFull}`} />
|
|
131
|
+
<Skeleton className={`${S.skeletonItem} ${S.hs} ${S.w75}`} />
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</CardContent>
|
|
135
|
+
</Card>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!selectedDriver) {
|
|
140
|
+
return (
|
|
141
|
+
<Card className={S.root}>
|
|
142
|
+
<CardContent centered fullHeight noScroll>
|
|
143
|
+
<span className={S.noDriverSelected}>No driver selected</span>
|
|
144
|
+
</CardContent>
|
|
145
|
+
</Card>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const { id, name, category, direction, lag, summary } = selectedDriver;
|
|
150
|
+
const directionText = direction > 0 ? 'Positive' : 'Negative';
|
|
151
|
+
const DirectionIcon = direction > 0 ? TrendUpIcon : TrendDownIcon;
|
|
152
|
+
|
|
153
|
+
const nameElem = (
|
|
154
|
+
<h4 className={`${S.driverTitle} ${S.truncated}`}>{name}</h4>
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<Card className={S.root} paddingSize="l">
|
|
159
|
+
<CardContent noScroll>
|
|
160
|
+
<div className={S.cardContent}>
|
|
161
|
+
{/* Driver Header */}
|
|
162
|
+
<div className={S.driverHeader}>
|
|
163
|
+
<div className={S.headerContent}>
|
|
164
|
+
<div className={S.topHeader}>
|
|
165
|
+
<p className={S.categoryInfo}>
|
|
166
|
+
<span className={S.categoryIcon}>
|
|
167
|
+
{getCategoryIcon(category)}
|
|
168
|
+
</span>
|
|
169
|
+
<span className={S.categoryText}>{category}</span>
|
|
170
|
+
</p>
|
|
171
|
+
|
|
172
|
+
{driverSelector}
|
|
173
|
+
</div>
|
|
174
|
+
{name.length > 60 ? (
|
|
175
|
+
<Tooltip>
|
|
176
|
+
<LabelWithId
|
|
177
|
+
id={id}
|
|
178
|
+
label={<TooltipTrigger asChild>{nameElem}</TooltipTrigger>}
|
|
179
|
+
/>
|
|
180
|
+
<TooltipContent side="left" className={S.tooltipContent}>
|
|
181
|
+
<div className={S.tooltipTitle}>{name}</div>
|
|
182
|
+
</TooltipContent>
|
|
183
|
+
</Tooltip>
|
|
184
|
+
) : (
|
|
185
|
+
<LabelWithId id={id} label={nameElem} />
|
|
186
|
+
)}
|
|
187
|
+
|
|
188
|
+
<p className={S.regionDisplay}>{regionDisplay}</p>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
{/* Key Metrics */}
|
|
193
|
+
<div className={S.metricsSection}>
|
|
194
|
+
<div className={S.importanceScore}>{importanceDisplay}</div>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
{/* Direction and Lag Information */}
|
|
198
|
+
<div className={S.directionLagSection}>
|
|
199
|
+
<Badge
|
|
200
|
+
variant={direction > 0 ? 'green' : 'red'}
|
|
201
|
+
className={S.directionBadge}
|
|
202
|
+
>
|
|
203
|
+
<DirectionIcon className={S.trendIcon} />
|
|
204
|
+
{directionText} correlation
|
|
205
|
+
</Badge>
|
|
206
|
+
|
|
207
|
+
<span className={S.lagInfo}>Lag: {lag}</span>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
{/* Driver Performance Chart */}
|
|
211
|
+
<DriverPerformanceChart driver={selectedDriver} />
|
|
212
|
+
|
|
213
|
+
{/* Description */}
|
|
214
|
+
<p className={S.description}>{summary ?? ''}</p>
|
|
215
|
+
</div>
|
|
216
|
+
</CardContent>
|
|
217
|
+
</Card>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Driver performance chart (normalized series)
|
|
2
|
+
.driverChartContainer
|
|
3
|
+
width 100%
|
|
4
|
+
position relative
|
|
5
|
+
|
|
6
|
+
svg
|
|
7
|
+
overflow visible
|
|
8
|
+
|
|
9
|
+
.chartContainer
|
|
10
|
+
height 214px
|
|
11
|
+
width 100%
|
|
12
|
+
margin-left -0.5rem
|
|
13
|
+
padding var(--p-4) 0
|
|
14
|
+
|
|
15
|
+
.chartContainerDisabled
|
|
16
|
+
opacity 0.1
|
|
17
|
+
|
|
18
|
+
.noDataMessage
|
|
19
|
+
position absolute
|
|
20
|
+
top 50%
|
|
21
|
+
left 50%
|
|
22
|
+
transform translate(-50%, -50%)
|
|
23
|
+
font-size 0.875rem
|
|
24
|
+
font-weight 500
|
|
25
|
+
color var(--foreground)
|
|
26
|
+
pointer-events none
|
|
27
|
+
z-index 10
|
|
28
|
+
|
|
29
|
+
.chartTooltip
|
|
30
|
+
background-color var(--popover)
|
|
31
|
+
border 1px solid var(--border)
|
|
32
|
+
border-radius 0.5rem
|
|
33
|
+
padding 0.75rem
|
|
34
|
+
box-shadow 0 10px 15px -3px rgba(0, 0, 0, 0.1)
|
|
35
|
+
|
|
36
|
+
.tooltipDate
|
|
37
|
+
font-size 0.875rem
|
|
38
|
+
font-weight 500
|
|
39
|
+
color var(--popover-foreground)
|
|
40
|
+
|
|
41
|
+
.tooltipPerformance
|
|
42
|
+
font-size 0.875rem
|
|
43
|
+
color var(--muted-foreground)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// This file is automatically generated.
|
|
2
|
+
// Please do not change this file!
|
|
3
|
+
interface CssExports {
|
|
4
|
+
'chartContainer': string;
|
|
5
|
+
'chartContainerDisabled': string;
|
|
6
|
+
'chartTooltip': string;
|
|
7
|
+
'driverChartContainer': string;
|
|
8
|
+
'noDataMessage': string;
|
|
9
|
+
'tooltipDate': string;
|
|
10
|
+
'tooltipPerformance': string;
|
|
11
|
+
}
|
|
12
|
+
export const cssExports: CssExports;
|
|
13
|
+
export default cssExports;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CartesianGrid,
|
|
6
|
+
Line,
|
|
7
|
+
LineChart,
|
|
8
|
+
Tooltip as RechartsTooltip,
|
|
9
|
+
type TooltipContentProps,
|
|
10
|
+
XAxis,
|
|
11
|
+
YAxis,
|
|
12
|
+
} from 'recharts';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
type ChartConfig,
|
|
16
|
+
ChartContainer,
|
|
17
|
+
ChartEmptyState,
|
|
18
|
+
} from '../../ui/Chart/Chart';
|
|
19
|
+
import type { DriverData } from '../DriverMap/driverMapGeography';
|
|
20
|
+
import S from './DriverPerformanceChart.styl';
|
|
21
|
+
import { generateDriverChartData } from './driverPerformanceChartData';
|
|
22
|
+
|
|
23
|
+
const driverChartConfig = {
|
|
24
|
+
value: {
|
|
25
|
+
label: 'Performance',
|
|
26
|
+
color: 'var(--primary)',
|
|
27
|
+
},
|
|
28
|
+
} satisfies ChartConfig;
|
|
29
|
+
|
|
30
|
+
export interface DriverPerformanceChartProps {
|
|
31
|
+
driver: DriverData;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function DriverPerformanceChart({
|
|
35
|
+
driver,
|
|
36
|
+
}: DriverPerformanceChartProps) {
|
|
37
|
+
const precision = useMemo(() => {
|
|
38
|
+
if (!driver.normalized_series) return 7;
|
|
39
|
+
|
|
40
|
+
const values = Object.values(driver.normalized_series).filter(
|
|
41
|
+
(v): v is number => typeof v === 'number' && v !== null,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (values.length === 0) return 7;
|
|
45
|
+
|
|
46
|
+
const absoluteValues = values.map(Math.abs).filter(v => v > 0);
|
|
47
|
+
if (absoluteValues.length === 0) return 7;
|
|
48
|
+
|
|
49
|
+
const minAbs = Math.min(...absoluteValues);
|
|
50
|
+
|
|
51
|
+
if (minAbs < 0.0001) return 7;
|
|
52
|
+
if (minAbs < 0.001) return 6;
|
|
53
|
+
if (minAbs < 0.01) return 5;
|
|
54
|
+
if (minAbs < 0.1) return 4;
|
|
55
|
+
if (minAbs < 1) return 3;
|
|
56
|
+
return 2;
|
|
57
|
+
}, [driver.normalized_series]);
|
|
58
|
+
|
|
59
|
+
const chartData = useMemo(
|
|
60
|
+
() => generateDriverChartData(driver, precision),
|
|
61
|
+
[driver, precision],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const noSeriesData = useMemo(() => {
|
|
65
|
+
if (!driver.normalized_series) return false;
|
|
66
|
+
return Object.values(driver.normalized_series).every(
|
|
67
|
+
value => value === null,
|
|
68
|
+
);
|
|
69
|
+
}, [driver.normalized_series]);
|
|
70
|
+
|
|
71
|
+
const renderTooltipContent = ({
|
|
72
|
+
active,
|
|
73
|
+
payload,
|
|
74
|
+
}: TooltipContentProps<number, string>) => {
|
|
75
|
+
if (active && payload?.length) {
|
|
76
|
+
const dataPoint = payload[0];
|
|
77
|
+
const row = dataPoint?.payload as
|
|
78
|
+
| { date?: Date; value?: number }
|
|
79
|
+
| undefined;
|
|
80
|
+
const date = row?.date;
|
|
81
|
+
const value = dataPoint?.value;
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div className={S.chartTooltip}>
|
|
85
|
+
<div className={S.tooltipDate}>
|
|
86
|
+
{date instanceof Date
|
|
87
|
+
? date.toLocaleDateString('en-US', {
|
|
88
|
+
month: 'long',
|
|
89
|
+
year: 'numeric',
|
|
90
|
+
})
|
|
91
|
+
: 'Unknown Date'}
|
|
92
|
+
</div>
|
|
93
|
+
<div className={S.tooltipPerformance}>
|
|
94
|
+
Performance:{' '}
|
|
95
|
+
{typeof value === 'number'
|
|
96
|
+
? value.toFixed(precision)
|
|
97
|
+
: String(value ?? '')}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className={S.driverChartContainer}>
|
|
107
|
+
<ChartContainer
|
|
108
|
+
config={driverChartConfig}
|
|
109
|
+
className={cn(
|
|
110
|
+
S.chartContainer,
|
|
111
|
+
noSeriesData && S.chartContainerDisabled,
|
|
112
|
+
)}
|
|
113
|
+
>
|
|
114
|
+
<LineChart
|
|
115
|
+
data={chartData}
|
|
116
|
+
margin={{ left: 8, right: 8, top: 8, bottom: -20 }}
|
|
117
|
+
>
|
|
118
|
+
<CartesianGrid vertical={false} />
|
|
119
|
+
<XAxis
|
|
120
|
+
dataKey="date"
|
|
121
|
+
tickLine={false}
|
|
122
|
+
axisLine={false}
|
|
123
|
+
tick={false}
|
|
124
|
+
tickMargin={0}
|
|
125
|
+
minTickGap={0}
|
|
126
|
+
/>
|
|
127
|
+
<YAxis
|
|
128
|
+
tickLine={false}
|
|
129
|
+
axisLine={false}
|
|
130
|
+
tick={false}
|
|
131
|
+
tickMargin={0}
|
|
132
|
+
minTickGap={0}
|
|
133
|
+
width={0}
|
|
134
|
+
domain={['dataMin', 'dataMax']}
|
|
135
|
+
/>
|
|
136
|
+
<RechartsTooltip cursor={false} content={renderTooltipContent} />
|
|
137
|
+
<Line dataKey="value" type="natural" stroke="var(--color-value)" />
|
|
138
|
+
</LineChart>
|
|
139
|
+
</ChartContainer>
|
|
140
|
+
{noSeriesData && (
|
|
141
|
+
<ChartEmptyState
|
|
142
|
+
variant="inline"
|
|
143
|
+
align="center"
|
|
144
|
+
status="No series data"
|
|
145
|
+
className={S.noDataMessage}
|
|
146
|
+
/>
|
|
147
|
+
)}
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { DriverData } from '../DriverMap/driverMapGeography';
|
|
2
|
+
|
|
3
|
+
export type DriverPerformanceChartPoint = {
|
|
4
|
+
date: Date;
|
|
5
|
+
value: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/** Last non-null points from `normalized_series`, or deterministic fallback sample. */
|
|
9
|
+
export function generateDriverChartData(
|
|
10
|
+
driver: DriverData,
|
|
11
|
+
precision: number = 7,
|
|
12
|
+
): DriverPerformanceChartPoint[] {
|
|
13
|
+
if (driver.normalized_series) {
|
|
14
|
+
const entries = Object.entries(driver.normalized_series);
|
|
15
|
+
|
|
16
|
+
const seriesData = entries
|
|
17
|
+
.filter(([, value]) => value !== null)
|
|
18
|
+
.map(([date, value]) => {
|
|
19
|
+
let parsedDate: Date;
|
|
20
|
+
try {
|
|
21
|
+
const dateStr = date.toString();
|
|
22
|
+
|
|
23
|
+
if (dateStr.includes('-')) {
|
|
24
|
+
parsedDate = new Date(`${dateStr}T00:00:00`);
|
|
25
|
+
} else {
|
|
26
|
+
parsedDate = new Date(dateStr);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (Number.isNaN(parsedDate.getTime())) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const numericValue = parseFloat((value as number).toFixed(precision));
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
date: parsedDate,
|
|
40
|
+
value: numericValue,
|
|
41
|
+
};
|
|
42
|
+
})
|
|
43
|
+
.filter((item): item is DriverPerformanceChartPoint => item !== null)
|
|
44
|
+
.sort((a, b) => a.date.getTime() - b.date.getTime())
|
|
45
|
+
.slice(-24);
|
|
46
|
+
|
|
47
|
+
if (seriesData.length > 0) {
|
|
48
|
+
return seriesData;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const data: DriverPerformanceChartPoint[] = [];
|
|
53
|
+
const baseValue = driver.importance / 100;
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < 12; i++) {
|
|
56
|
+
const randomVariation = (Math.random() - 0.5) * 0.2;
|
|
57
|
+
const value = Math.max(0, Math.min(1, baseValue + randomVariation));
|
|
58
|
+
data.push({
|
|
59
|
+
date: new Date(2023, i, 1),
|
|
60
|
+
value: parseFloat(value.toFixed(precision)),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return data;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DriverCard, type DriverCardProps } from './DriverCard';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
@import '../../../lib/theme.styl'
|
|
2
|
-
@import '
|
|
2
|
+
@import '../../ui/WorldMap/mapAspect.mixin.styl'
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
.root
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
border-radius var(--p-4)
|
|
23
23
|
overflow hidden
|
|
24
24
|
|
|
25
|
+
.mapWorld
|
|
26
|
+
position absolute
|
|
27
|
+
inset 0
|
|
28
|
+
box-sizing border-box
|
|
29
|
+
|
|
25
30
|
.shimmerOverlay
|
|
26
31
|
z-index 20
|
|
27
32
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
1
|
import cn from 'classnames';
|
|
4
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
3
|
|
|
4
|
+
import { WorldMap } from '#uilib/components/ui/WorldMap';
|
|
6
5
|
import useEvent from '#uilib/hooks/useEvent';
|
|
7
6
|
import { Shimmer } from '@homecode/ui';
|
|
8
7
|
|
|
@@ -15,7 +14,6 @@ import {
|
|
|
15
14
|
hasValidCoords,
|
|
16
15
|
} from './DriverMap.helpers';
|
|
17
16
|
import S from './DriverMap.styl';
|
|
18
|
-
import { MapBackground } from './MapBackground/MapBackground';
|
|
19
17
|
import type { DriverData } from './driverMapGeography';
|
|
20
18
|
import { getHighestImportanceDriver } from './driverMapSelection';
|
|
21
19
|
|
|
@@ -172,7 +170,7 @@ export function DriverMap({
|
|
|
172
170
|
return (
|
|
173
171
|
<div className={cn(S.root, isTransitioning && S.inTransition)}>
|
|
174
172
|
<div className={S.mapInner}>
|
|
175
|
-
<
|
|
173
|
+
<WorldMap className={S.mapWorld} />
|
|
176
174
|
{isLoading && <Shimmer className={S.shimmerOverlay} size="l" />}
|
|
177
175
|
{otherDrivers.map(driver => (
|
|
178
176
|
<DriverIcon
|
|
@@ -13,6 +13,4 @@ export {
|
|
|
13
13
|
getResponsiveCoordinates,
|
|
14
14
|
svgToPercentage,
|
|
15
15
|
} from './driverMapGeography';
|
|
16
|
-
export { MapBackground } from './MapBackground/MapBackground';
|
|
17
|
-
export { LoadingSpinner } from './LoadingSpinner/LoadingSpinner';
|
|
18
16
|
export type { BadgeSize } from './DriverIcon/DriverIcon';
|
package/src/declarations.d.ts
CHANGED
|
@@ -22,9 +22,27 @@ const pkg = require('../../../package.json');
|
|
|
22
22
|
const themeStyl = pathResolve(paths.src, 'theme.styl');
|
|
23
23
|
const logoSvgPath = pathResolve(paths.assets, 'logo.svg');
|
|
24
24
|
|
|
25
|
+
/** GitHub Pages project sites live at /<repo>/; set PUBLIC_PATH=/repo-name/ for production deploy. */
|
|
26
|
+
function normalizePublicPath(raw) {
|
|
27
|
+
const v = (raw && String(raw).trim()) || '/';
|
|
28
|
+
if (v === '/') {
|
|
29
|
+
return '/';
|
|
30
|
+
}
|
|
31
|
+
const withLeading = v.startsWith('/') ? v : `/${v}`;
|
|
32
|
+
return withLeading.endsWith('/') ? withLeading : `${withLeading}/`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function routerBasenameFromPublicPath(publicPath) {
|
|
36
|
+
if (publicPath === '/') {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
return publicPath.replace(/\/$/, '');
|
|
40
|
+
}
|
|
41
|
+
|
|
25
42
|
export default (env, argv) => {
|
|
26
43
|
const isDev = argv.mode === 'development';
|
|
27
|
-
const
|
|
44
|
+
const publicPath = normalizePublicPath(process.env.PUBLIC_PATH);
|
|
45
|
+
const docsRouterBasename = routerBasenameFromPublicPath(publicPath);
|
|
28
46
|
const alias = {
|
|
29
47
|
'#uilib': paths.src,
|
|
30
48
|
uilib: paths.src,
|
|
@@ -38,6 +56,7 @@ export default (env, argv) => {
|
|
|
38
56
|
entry: [paths.docs],
|
|
39
57
|
output: {
|
|
40
58
|
path: paths.build,
|
|
59
|
+
publicPath,
|
|
41
60
|
},
|
|
42
61
|
|
|
43
62
|
resolve: {
|
|
@@ -104,7 +123,7 @@ export default (env, argv) => {
|
|
|
104
123
|
type: 'asset/resource',
|
|
105
124
|
},
|
|
106
125
|
{
|
|
107
|
-
include: /
|
|
126
|
+
include: /components[/\\]ui[/\\]WorldMap[/\\]map\.svg$/,
|
|
108
127
|
type: 'asset/resource',
|
|
109
128
|
},
|
|
110
129
|
{
|
|
@@ -152,6 +171,7 @@ export default (env, argv) => {
|
|
|
152
171
|
new webpack.DefinePlugin({
|
|
153
172
|
isDEV: JSON.stringify(isDev),
|
|
154
173
|
VERSION: JSON.stringify(pkg.version),
|
|
174
|
+
DOCS_ROUTER_BASENAME: JSON.stringify(docsRouterBasename),
|
|
155
175
|
}),
|
|
156
176
|
new CopyWebpackPlugin({
|
|
157
177
|
patterns: [
|
|
@@ -174,7 +194,7 @@ export default (env, argv) => {
|
|
|
174
194
|
|
|
175
195
|
new HtmlWebpackPlugin({
|
|
176
196
|
lang: 'en',
|
|
177
|
-
baseUrl:
|
|
197
|
+
baseUrl: publicPath,
|
|
178
198
|
filename: 'index.html',
|
|
179
199
|
template: `${paths.assets}/index.html`,
|
|
180
200
|
minify: isDev
|
|
@@ -221,6 +241,9 @@ export default (env, argv) => {
|
|
|
221
241
|
hot: true,
|
|
222
242
|
port: process.env.PORT || 8181,
|
|
223
243
|
historyApiFallback: true,
|
|
244
|
+
...(publicPath !== '/' && {
|
|
245
|
+
devMiddleware: { publicPath },
|
|
246
|
+
}),
|
|
224
247
|
},
|
|
225
248
|
});
|
|
226
249
|
}
|
package/src/docs/index.tsx
CHANGED
|
@@ -10,7 +10,7 @@ const elem = document.getElementById('app-root') as HTMLElement;
|
|
|
10
10
|
const root = createRoot(elem);
|
|
11
11
|
|
|
12
12
|
root.render(
|
|
13
|
-
<BrowserRouter>
|
|
13
|
+
<BrowserRouter basename={DOCS_ROUTER_BASENAME || undefined}>
|
|
14
14
|
<ThemeProvider activeColor={DEFAULT_THEME_ACTIVE_COLOR}>
|
|
15
15
|
<App />
|
|
16
16
|
</ThemeProvider>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useCallback, useMemo, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { ChartAreaInteractive } from '#uilib/components/ui/ChartAreaInteractive';
|
|
4
|
-
import type { TimeRange } from '#uilib/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers';
|
|
5
4
|
import type {
|
|
6
5
|
ChartDataPoint,
|
|
7
6
|
OverlayMode,
|
|
@@ -91,7 +90,7 @@ type DemoMode = 'none' | OverlayMode;
|
|
|
91
90
|
|
|
92
91
|
export default function ChartAreaInteractivePage() {
|
|
93
92
|
const { isDarkMode } = useTheme();
|
|
94
|
-
const [timeRange, setTimeRange] = useState<
|
|
93
|
+
const [timeRange, setTimeRange] = useState<string>('1y');
|
|
95
94
|
const [pinMonth, setPinMonth] = useState<string | undefined>(undefined);
|
|
96
95
|
const [demoMode, setDemoMode] = useState<DemoMode>('none');
|
|
97
96
|
const [chartData] = useState<ChartDataPoint[]>(INITIAL_CHART);
|
|
@@ -158,7 +157,7 @@ export default function ChartAreaInteractivePage() {
|
|
|
158
157
|
</Tabs>
|
|
159
158
|
<ChartAreaInteractive
|
|
160
159
|
timeRange={timeRange}
|
|
161
|
-
onTimeRangeChange={
|
|
160
|
+
onTimeRangeChange={setTimeRange}
|
|
162
161
|
pinMonth={pinMonth}
|
|
163
162
|
onPinMonthChange={setPinMonth}
|
|
164
163
|
mode={mode}
|