@sybilion/uilib 1.2.26 → 1.3.0
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 +1 -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/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/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 +79 -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 +3 -2
- package/dist/esm/types/src/components/ui/Chart/Chart.d.ts +1 -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/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/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 +1 -1
- package/src/components/ui/Chart/Chart.tsx +5 -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/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/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/docs/config/webpack.config.js +1 -1
- package/src/docs/pages/ChartAreaInteractivePage.tsx +2 -3
- package/src/docs/pages/DriverMapPage.tsx +214 -60
- 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 +13 -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,22 @@
|
|
|
1
|
+
import cn from 'classnames';
|
|
2
|
+
|
|
3
|
+
import S from './WorldMap.styl';
|
|
4
|
+
import mapBgUrl from './map.svg';
|
|
5
|
+
|
|
6
|
+
export type WorldMapProps = {
|
|
7
|
+
className?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function WorldMap({ className }: WorldMapProps) {
|
|
11
|
+
const src = mapBgUrl as unknown as string;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<img
|
|
15
|
+
alt=""
|
|
16
|
+
className={cn(S.worldMap, className)}
|
|
17
|
+
decoding="async"
|
|
18
|
+
draggable={false}
|
|
19
|
+
src={src}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
@import '../../../lib/theme.styl';
|
|
2
|
+
|
|
3
|
+
.root
|
|
4
|
+
width 100%
|
|
5
|
+
height auto
|
|
6
|
+
min-height 500px
|
|
7
|
+
|
|
8
|
+
@media (min-width 640px)
|
|
9
|
+
min-height 25rem
|
|
10
|
+
|
|
11
|
+
@media (min-width 768px)
|
|
12
|
+
min-height 31.25rem
|
|
13
|
+
// width 20rem
|
|
14
|
+
|
|
15
|
+
.cardContent
|
|
16
|
+
display flex
|
|
17
|
+
flex-direction column
|
|
18
|
+
gap 0.5rem
|
|
19
|
+
|
|
20
|
+
.noDriverSelected
|
|
21
|
+
color var(--muted-foreground)
|
|
22
|
+
|
|
23
|
+
.driverHeader
|
|
24
|
+
display flex
|
|
25
|
+
flex-direction column
|
|
26
|
+
align-items flex-start
|
|
27
|
+
gap 0.75rem
|
|
28
|
+
padding-bottom 0.25rem
|
|
29
|
+
|
|
30
|
+
.headerContent
|
|
31
|
+
flex-grow 1
|
|
32
|
+
display flex
|
|
33
|
+
flex-direction column
|
|
34
|
+
width 100%
|
|
35
|
+
gap 0.5rem
|
|
36
|
+
|
|
37
|
+
.topHeader
|
|
38
|
+
display flex
|
|
39
|
+
align-items center
|
|
40
|
+
justify-content space-between
|
|
41
|
+
min-height 36px
|
|
42
|
+
margin-top -12px
|
|
43
|
+
|
|
44
|
+
.categoryInfo
|
|
45
|
+
font-size 0.75rem
|
|
46
|
+
color var(--muted-foreground)
|
|
47
|
+
display flex
|
|
48
|
+
align-items center
|
|
49
|
+
gap 0.25rem
|
|
50
|
+
|
|
51
|
+
.categoryIcon
|
|
52
|
+
height 0.75rem
|
|
53
|
+
width 0.75rem
|
|
54
|
+
color var(--muted-foreground)
|
|
55
|
+
|
|
56
|
+
.categoryText
|
|
57
|
+
line-clamp(1)
|
|
58
|
+
overflow hidden
|
|
59
|
+
text-transform lowercase
|
|
60
|
+
&::first-letter
|
|
61
|
+
text-transform uppercase
|
|
62
|
+
|
|
63
|
+
.driverTitle
|
|
64
|
+
font-weight 500
|
|
65
|
+
color var(--foreground)
|
|
66
|
+
font-size 1.1rem
|
|
67
|
+
line-height 1.4
|
|
68
|
+
user-select text
|
|
69
|
+
|
|
70
|
+
&.truncated
|
|
71
|
+
line-clamp(2)
|
|
72
|
+
overflow hidden
|
|
73
|
+
cursor help
|
|
74
|
+
|
|
75
|
+
.regionDisplay
|
|
76
|
+
font-size 0.75rem
|
|
77
|
+
color var(--muted-foreground)
|
|
78
|
+
|
|
79
|
+
.metricsSection
|
|
80
|
+
text-align left
|
|
81
|
+
margin-bottom 0.5rem
|
|
82
|
+
|
|
83
|
+
.importanceScore
|
|
84
|
+
font-size 2.25rem
|
|
85
|
+
font-weight 400
|
|
86
|
+
color var(--foreground)
|
|
87
|
+
display flex
|
|
88
|
+
align-items center
|
|
89
|
+
line-height 1
|
|
90
|
+
height auto
|
|
91
|
+
user-select text
|
|
92
|
+
|
|
93
|
+
.directionLagSection
|
|
94
|
+
display flex
|
|
95
|
+
align-items center
|
|
96
|
+
justify-content space-between
|
|
97
|
+
|
|
98
|
+
.directionBadge
|
|
99
|
+
display flex
|
|
100
|
+
align-items center
|
|
101
|
+
gap 0.5rem
|
|
102
|
+
|
|
103
|
+
.trendIcon
|
|
104
|
+
height 0.75rem
|
|
105
|
+
width 0.75rem
|
|
106
|
+
|
|
107
|
+
.lagInfo
|
|
108
|
+
font-size 0.75rem
|
|
109
|
+
color var(--muted-foreground)
|
|
110
|
+
|
|
111
|
+
.description
|
|
112
|
+
font-size 0.875rem
|
|
113
|
+
line-height 1.625
|
|
114
|
+
user-select text
|
|
115
|
+
|
|
116
|
+
.skeletonContainer
|
|
117
|
+
display flex
|
|
118
|
+
flex-direction column
|
|
119
|
+
gap 1rem
|
|
120
|
+
|
|
121
|
+
.skeletonGroup
|
|
122
|
+
display flex
|
|
123
|
+
flex-direction column
|
|
124
|
+
gap 0.5rem
|
|
125
|
+
|
|
126
|
+
.skeletonItem
|
|
127
|
+
&.hxs
|
|
128
|
+
height 0.75rem
|
|
129
|
+
&.hs
|
|
130
|
+
height 1rem
|
|
131
|
+
&.hm
|
|
132
|
+
height 2rem
|
|
133
|
+
&.hl
|
|
134
|
+
height 8rem
|
|
135
|
+
|
|
136
|
+
&.w33
|
|
137
|
+
width 33.333333%
|
|
138
|
+
&.w50
|
|
139
|
+
width 50%
|
|
140
|
+
&.w75
|
|
141
|
+
width 75%
|
|
142
|
+
&.w80
|
|
143
|
+
width 80%
|
|
144
|
+
&.wFull
|
|
145
|
+
width 100%
|
|
146
|
+
|
|
147
|
+
.tooltipContent
|
|
148
|
+
max-width 20rem
|
|
149
|
+
font-size 0.75rem
|
|
150
|
+
|
|
151
|
+
.tooltipTitle
|
|
152
|
+
font-weight normal
|
|
153
|
+
font-family var(--font-family-body)
|
|
154
|
+
|
|
155
|
+
.queueMessage
|
|
156
|
+
display flex
|
|
157
|
+
flex-direction column
|
|
158
|
+
align-items center
|
|
159
|
+
gap var(--p-4)
|
|
160
|
+
padding var(--p-8) var(--p-4)
|
|
161
|
+
color var(--muted-foreground)
|
|
162
|
+
font-size 0.875rem
|
|
163
|
+
line-height 1.5
|
|
164
|
+
text-align center
|
|
165
|
+
|
|
166
|
+
svg
|
|
167
|
+
flex-shrink 0
|
|
168
|
+
color var(--muted-foreground)
|
|
169
|
+
opacity 0.8
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// This file is automatically generated.
|
|
2
|
+
// Please do not change this file!
|
|
3
|
+
interface CssExports {
|
|
4
|
+
'cardContent': string;
|
|
5
|
+
'categoryIcon': string;
|
|
6
|
+
'categoryInfo': string;
|
|
7
|
+
'categoryText': string;
|
|
8
|
+
'description': string;
|
|
9
|
+
'directionBadge': string;
|
|
10
|
+
'directionLagSection': string;
|
|
11
|
+
'driverHeader': string;
|
|
12
|
+
'driverTitle': string;
|
|
13
|
+
'headerContent': string;
|
|
14
|
+
'hl': string;
|
|
15
|
+
'hm': string;
|
|
16
|
+
'hs': string;
|
|
17
|
+
'hxs': string;
|
|
18
|
+
'importanceScore': string;
|
|
19
|
+
'lagInfo': string;
|
|
20
|
+
'metricsSection': string;
|
|
21
|
+
'noDriverSelected': string;
|
|
22
|
+
'queueMessage': string;
|
|
23
|
+
'regionDisplay': string;
|
|
24
|
+
'root': string;
|
|
25
|
+
'skeletonContainer': string;
|
|
26
|
+
'skeletonGroup': string;
|
|
27
|
+
'skeletonItem': string;
|
|
28
|
+
'tooltipContent': string;
|
|
29
|
+
'tooltipTitle': string;
|
|
30
|
+
'topHeader': string;
|
|
31
|
+
'trendIcon': string;
|
|
32
|
+
'truncated': string;
|
|
33
|
+
'w33': string;
|
|
34
|
+
'w50': string;
|
|
35
|
+
'w75': string;
|
|
36
|
+
'w80': string;
|
|
37
|
+
'wFull': string;
|
|
38
|
+
}
|
|
39
|
+
export const cssExports: CssExports;
|
|
40
|
+
export default cssExports;
|
|
@@ -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
|
+
}
|