@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.
Files changed (111) hide show
  1. package/dist/esm/components/ui/Chart/Chart.js +1 -0
  2. package/dist/esm/components/ui/Chart/components/BaseChartWrapper.js +7 -32
  3. package/dist/esm/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.js +21 -0
  4. package/dist/esm/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl.js +7 -0
  5. package/dist/esm/components/ui/Chart/tools/chartPlotGeometry.js +65 -0
  6. package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.js +37 -1
  7. package/dist/esm/components/ui/ChartAreaInteractive/ChartAreaInteractive.js +5 -2
  8. package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.js +205 -0
  9. package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.js +7 -0
  10. package/dist/esm/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.js +37 -0
  11. package/dist/esm/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.js +1 -0
  12. package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.js +7 -60
  13. package/dist/esm/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.js +2 -2
  14. package/dist/esm/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.js +1 -0
  15. package/dist/esm/components/ui/ChartAreaInteractive/overlays/useChartYRange.js +2 -4
  16. package/dist/esm/components/ui/TimeRangeControls/TimeRangeControls.js +7 -2
  17. package/dist/esm/components/ui/WorldMap/WorldMap.js +11 -0
  18. package/dist/esm/components/ui/WorldMap/WorldMap.styl.js +7 -0
  19. package/dist/esm/components/widgets/DriverCard/DriverCard.js +89 -0
  20. package/dist/esm/components/widgets/DriverCard/DriverCard.styl.js +7 -0
  21. package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.js +79 -0
  22. package/dist/esm/components/widgets/DriverCard/DriverPerformanceChart.styl.js +7 -0
  23. package/dist/esm/components/widgets/DriverCard/driverPerformanceChartData.js +50 -0
  24. package/dist/esm/components/widgets/DriverMap/DriverMap.js +2 -2
  25. package/dist/esm/components/widgets/DriverMap/DriverMap.styl.js +2 -2
  26. package/dist/esm/index.js +3 -2
  27. package/dist/esm/types/src/components/ui/Chart/Chart.d.ts +1 -0
  28. package/dist/esm/types/src/components/ui/Chart/components/BaseChartWrapper.d.ts +2 -1
  29. package/dist/esm/types/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.d.ts +14 -0
  30. package/dist/esm/types/src/components/ui/Chart/tools/chartPlotGeometry.d.ts +30 -0
  31. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.d.ts +1 -1
  32. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.d.ts +11 -2
  33. package/dist/esm/types/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.d.ts +2 -2
  34. package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.d.ts +15 -0
  35. package/dist/esm/types/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.d.ts +14 -0
  36. package/dist/esm/types/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.d.ts +1 -1
  37. package/dist/esm/types/src/components/ui/Page/PageColumns/PageColumns.d.ts +1 -1
  38. package/dist/esm/types/src/components/ui/TimeRangeControls/TimeRangeControls.d.ts +5 -7
  39. package/dist/esm/types/src/components/ui/WorldMap/WorldMap.d.ts +4 -0
  40. package/dist/esm/types/src/components/ui/WorldMap/index.d.ts +2 -0
  41. package/dist/esm/types/src/components/widgets/DriverCard/DriverCard.d.ts +9 -0
  42. package/dist/esm/types/src/components/widgets/DriverCard/DriverPerformanceChart.d.ts +5 -0
  43. package/dist/esm/types/src/components/widgets/DriverCard/driverPerformanceChartData.d.ts +7 -0
  44. package/dist/esm/types/src/components/widgets/DriverCard/index.d.ts +1 -0
  45. package/dist/esm/types/src/components/widgets/DriverMap/index.d.ts +0 -2
  46. package/dist/esm/types/src/docs/pages/PageColumnsPage.d.ts +1 -0
  47. package/dist/esm/types/src/docs/pages/WorldMapPage.d.ts +1 -0
  48. package/dist/esm/types/src/index.d.ts +2 -0
  49. package/package.json +1 -1
  50. package/src/components/ui/Chart/Chart.tsx +5 -0
  51. package/src/components/ui/Chart/components/BaseChartWrapper.tsx +8 -41
  52. package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl +60 -0
  53. package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.styl.d.ts +15 -0
  54. package/src/components/ui/Chart/components/ChartEmptyState/ChartEmptyState.tsx +66 -0
  55. package/src/components/ui/Chart/tools/chartPlotGeometry.ts +89 -0
  56. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.helpers.ts +44 -2
  57. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.tsx +14 -1
  58. package/src/components/ui/ChartAreaInteractive/ChartAreaInteractive.types.ts +2 -3
  59. package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.styl +21 -0
  60. package/src/components/{widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.d.ts → ui/ChartAreaInteractive/TimeRangeBrushLayer.styl.d.ts} +3 -3
  61. package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayer.tsx +285 -0
  62. package/src/components/ui/ChartAreaInteractive/TimeRangeBrushLayout.helpers.ts +55 -0
  63. package/src/components/ui/ChartAreaInteractive/overlays/IntervalsOverlay/IntervalsOverlay.hooks.ts +1 -0
  64. package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl +2 -7
  65. package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.styl.d.ts +0 -1
  66. package/src/components/ui/ChartAreaInteractive/overlays/PinOverlay/PinOverlay.tsx +7 -71
  67. package/src/components/ui/ChartAreaInteractive/overlays/ThresholdsOverlay/ThresholdsOverlay.hooks.ts +1 -0
  68. package/src/components/ui/ChartAreaInteractive/overlays/useChartYRange.ts +2 -3
  69. package/src/components/ui/Page/PageColumns/PageColumns.tsx +1 -1
  70. package/src/components/ui/TimeRangeControls/TimeRangeControls.tsx +16 -17
  71. package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl → ui/WorldMap/WorldMap.styl} +1 -3
  72. package/src/components/{widgets/DriverMap/MapBackground/MapBackground.styl.d.ts → ui/WorldMap/WorldMap.styl.d.ts} +1 -1
  73. package/src/components/ui/WorldMap/WorldMap.tsx +22 -0
  74. package/src/components/ui/WorldMap/index.ts +2 -0
  75. package/src/components/widgets/DriverCard/DriverCard.styl +169 -0
  76. package/src/components/widgets/DriverCard/DriverCard.styl.d.ts +40 -0
  77. package/src/components/widgets/DriverCard/DriverCard.tsx +219 -0
  78. package/src/components/widgets/DriverCard/DriverPerformanceChart.styl +43 -0
  79. package/src/components/widgets/DriverCard/DriverPerformanceChart.styl.d.ts +13 -0
  80. package/src/components/widgets/DriverCard/DriverPerformanceChart.tsx +150 -0
  81. package/src/components/widgets/DriverCard/driverPerformanceChartData.ts +64 -0
  82. package/src/components/widgets/DriverCard/index.ts +1 -0
  83. package/src/components/widgets/DriverMap/DriverIcon/DriverIcon.tsx +0 -2
  84. package/src/components/widgets/DriverMap/DriverMap.styl +6 -1
  85. package/src/components/widgets/DriverMap/DriverMap.styl.d.ts +1 -0
  86. package/src/components/widgets/DriverMap/DriverMap.tsx +2 -4
  87. package/src/components/widgets/DriverMap/driverCategoryIcon.tsx +0 -2
  88. package/src/components/widgets/DriverMap/index.ts +0 -2
  89. package/src/docs/config/webpack.config.js +1 -1
  90. package/src/docs/pages/ChartAreaInteractivePage.tsx +2 -3
  91. package/src/docs/pages/DriverMapPage.tsx +214 -60
  92. package/src/docs/pages/PageColumnsPage.tsx +92 -0
  93. package/src/docs/pages/TimeRangeControlsPage.tsx +2 -3
  94. package/src/docs/pages/WorldMapPage.styl +14 -0
  95. package/src/docs/pages/WorldMapPage.styl.d.ts +8 -0
  96. package/src/docs/pages/WorldMapPage.tsx +26 -0
  97. package/src/docs/registry.ts +13 -1
  98. package/src/index.ts +2 -0
  99. package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.js +0 -8
  100. package/dist/esm/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl.js +0 -7
  101. package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.js +0 -10
  102. package/dist/esm/components/widgets/DriverMap/MapBackground/MapBackground.styl.js +0 -7
  103. package/dist/esm/types/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.d.ts +0 -1
  104. package/dist/esm/types/src/components/widgets/DriverMap/MapBackground/MapBackground.d.ts +0 -1
  105. package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.styl +0 -24
  106. package/src/components/widgets/DriverMap/LoadingSpinner/LoadingSpinner.tsx +0 -11
  107. package/src/components/widgets/DriverMap/MapBackground/MapBackground.tsx +0 -18
  108. /package/dist/esm/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg.js +0 -0
  109. /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/map.svg +0 -0
  110. /package/src/components/{widgets/DriverMap/MapBackground → ui/WorldMap}/mapAspect.mixin.styl +0 -0
  111. /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,2 @@
1
+ export { WorldMap } from './WorldMap';
2
+ export type { WorldMapProps } from './WorldMap';
@@ -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
+ }