@handled-ai/design-system 0.8.0 → 0.9.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 (336) hide show
  1. package/README.md +14 -4
  2. package/dist/charts/bar-chart-component.d.ts +24 -0
  3. package/dist/charts/bar-chart-component.js +123 -0
  4. package/dist/charts/bar-chart-component.js.map +1 -0
  5. package/dist/charts/chart-tooltip.d.ts +26 -0
  6. package/dist/charts/chart-tooltip.js +69 -0
  7. package/dist/charts/chart-tooltip.js.map +1 -0
  8. package/dist/charts/chart.d.ts +64 -0
  9. package/dist/charts/chart.js +285 -0
  10. package/dist/charts/chart.js.map +1 -0
  11. package/dist/charts/donut-chart.d.ts +21 -0
  12. package/dist/charts/donut-chart.js +96 -0
  13. package/dist/charts/donut-chart.js.map +1 -0
  14. package/dist/charts/index.d.ts +11 -0
  15. package/dist/charts/index.js +10 -0
  16. package/dist/charts/index.js.map +1 -0
  17. package/dist/charts/pipeline-overview.d.ts +76 -0
  18. package/dist/charts/pipeline-overview.js +372 -0
  19. package/dist/charts/pipeline-overview.js.map +1 -0
  20. package/dist/charts/sankey-chart.d.ts +52 -0
  21. package/dist/charts/sankey-chart.js +219 -0
  22. package/dist/charts/sankey-chart.js.map +1 -0
  23. package/dist/charts/top-line-metrics.d.ts +26 -0
  24. package/dist/charts/top-line-metrics.js +224 -0
  25. package/dist/charts/top-line-metrics.js.map +1 -0
  26. package/dist/charts/trend-area-chart.d.ts +21 -0
  27. package/dist/charts/trend-area-chart.js +150 -0
  28. package/dist/charts/trend-area-chart.js.map +1 -0
  29. package/dist/charts/volume-analysis-chart.d.ts +19 -0
  30. package/dist/charts/volume-analysis-chart.js +121 -0
  31. package/dist/charts/volume-analysis-chart.js.map +1 -0
  32. package/dist/components/activity-detail.d.ts +38 -0
  33. package/dist/components/activity-detail.js +163 -0
  34. package/dist/components/activity-detail.js.map +1 -0
  35. package/dist/components/activity-log.d.ts +21 -0
  36. package/dist/components/activity-log.js +61 -0
  37. package/dist/components/activity-log.js.map +1 -0
  38. package/dist/components/agent-popover.d.ts +71 -0
  39. package/dist/components/agent-popover.js +282 -0
  40. package/dist/components/agent-popover.js.map +1 -0
  41. package/dist/components/agent-widget.d.ts +24 -0
  42. package/dist/components/agent-widget.js +117 -0
  43. package/dist/components/agent-widget.js.map +1 -0
  44. package/dist/components/avatar.d.ts +13 -0
  45. package/dist/components/avatar.js +140 -0
  46. package/dist/components/avatar.js.map +1 -0
  47. package/dist/components/badge.d.ts +12 -0
  48. package/dist/components/badge.js +75 -0
  49. package/dist/components/badge.js.map +1 -0
  50. package/dist/components/button.d.ts +13 -0
  51. package/dist/components/button.js +83 -0
  52. package/dist/components/button.js.map +1 -0
  53. package/dist/components/card.d.ts +11 -0
  54. package/dist/components/card.js +119 -0
  55. package/dist/components/card.js.map +1 -0
  56. package/dist/components/contact-list.d.ts +34 -0
  57. package/dist/components/contact-list.js +84 -0
  58. package/dist/components/contact-list.js.map +1 -0
  59. package/dist/components/dashboard-cards.d.ts +10 -0
  60. package/dist/components/dashboard-cards.js +164 -0
  61. package/dist/components/dashboard-cards.js.map +1 -0
  62. package/dist/components/data-table-display.d.ts +19 -0
  63. package/dist/components/data-table-display.js +109 -0
  64. package/dist/components/data-table-display.js.map +1 -0
  65. package/dist/components/data-table-filter.d.ts +18 -0
  66. package/dist/components/data-table-filter.js +107 -0
  67. package/dist/components/data-table-filter.js.map +1 -0
  68. package/dist/components/data-table-quick-views.d.ts +13 -0
  69. package/dist/components/data-table-quick-views.js +90 -0
  70. package/dist/components/data-table-quick-views.js.map +1 -0
  71. package/dist/components/data-table-toolbar.d.ts +18 -0
  72. package/dist/components/data-table-toolbar.js +45 -0
  73. package/dist/components/data-table-toolbar.js.map +1 -0
  74. package/dist/components/data-table.d.ts +39 -0
  75. package/dist/components/data-table.js +821 -0
  76. package/dist/components/data-table.js.map +1 -0
  77. package/dist/components/detail-view.d.ts +44 -0
  78. package/dist/components/detail-view.js +165 -0
  79. package/dist/components/detail-view.js.map +1 -0
  80. package/dist/components/dialog.d.ts +19 -0
  81. package/dist/components/dialog.js +188 -0
  82. package/dist/components/dialog.js.map +1 -0
  83. package/dist/components/dropdown-menu.d.ts +27 -0
  84. package/dist/components/dropdown-menu.js +279 -0
  85. package/dist/components/dropdown-menu.js.map +1 -0
  86. package/dist/components/entity-panel.d.ts +69 -0
  87. package/dist/components/entity-panel.js +584 -0
  88. package/dist/components/entity-panel.js.map +1 -0
  89. package/dist/components/inbox-row.d.ts +27 -0
  90. package/dist/components/inbox-row.js +139 -0
  91. package/dist/components/inbox-row.js.map +1 -0
  92. package/dist/components/inbox-toolbar.d.ts +21 -0
  93. package/dist/components/inbox-toolbar.js +203 -0
  94. package/dist/components/inbox-toolbar.js.map +1 -0
  95. package/dist/components/input.d.ts +5 -0
  96. package/dist/components/input.js +50 -0
  97. package/dist/components/input.js.map +1 -0
  98. package/dist/components/insights-filter-bar.d.ts +21 -0
  99. package/dist/components/insights-filter-bar.js +99 -0
  100. package/dist/components/insights-filter-bar.js.map +1 -0
  101. package/dist/components/item-list-display.d.ts +22 -0
  102. package/dist/components/item-list-display.js +240 -0
  103. package/dist/components/item-list-display.js.map +1 -0
  104. package/dist/components/item-list-filter.d.ts +16 -0
  105. package/dist/components/item-list-filter.js +87 -0
  106. package/dist/components/item-list-filter.js.map +1 -0
  107. package/dist/components/item-list-toolbar.d.ts +25 -0
  108. package/dist/components/item-list-toolbar.js +79 -0
  109. package/dist/components/item-list-toolbar.js.map +1 -0
  110. package/dist/components/item-list.d.ts +20 -0
  111. package/dist/components/item-list.js +702 -0
  112. package/dist/components/item-list.js.map +1 -0
  113. package/dist/components/label.d.ts +6 -0
  114. package/dist/components/label.js +55 -0
  115. package/dist/components/label.js.map +1 -0
  116. package/dist/components/message.d.ts +23 -0
  117. package/dist/components/message.js +117 -0
  118. package/dist/components/message.js.map +1 -0
  119. package/dist/components/metric-card.d.ts +25 -0
  120. package/dist/components/metric-card.js +107 -0
  121. package/dist/components/metric-card.js.map +1 -0
  122. package/dist/components/performance-metrics-table.d.ts +38 -0
  123. package/dist/components/performance-metrics-table.js +342 -0
  124. package/dist/components/performance-metrics-table.js.map +1 -0
  125. package/dist/components/preview-list.d.ts +14 -0
  126. package/dist/components/preview-list.js +83 -0
  127. package/dist/components/preview-list.js.map +1 -0
  128. package/dist/components/progress.d.ts +6 -0
  129. package/dist/components/progress.js +69 -0
  130. package/dist/components/progress.js.map +1 -0
  131. package/dist/components/quick-action-chat-area.d.ts +24 -0
  132. package/dist/components/quick-action-chat-area.js +178 -0
  133. package/dist/components/quick-action-chat-area.js.map +1 -0
  134. package/dist/components/quick-action-modal.d.ts +30 -0
  135. package/dist/components/quick-action-modal.js +288 -0
  136. package/dist/components/quick-action-modal.js.map +1 -0
  137. package/dist/components/quick-action-sidebar-nav.d.ts +51 -0
  138. package/dist/components/quick-action-sidebar-nav.js +528 -0
  139. package/dist/components/quick-action-sidebar-nav.js.map +1 -0
  140. package/dist/components/recommended-actions-section.d.ts +23 -0
  141. package/dist/components/recommended-actions-section.js +215 -0
  142. package/dist/components/recommended-actions-section.js.map +1 -0
  143. package/dist/components/report-card.d.ts +26 -0
  144. package/dist/components/report-card.js +69 -0
  145. package/dist/components/report-card.js.map +1 -0
  146. package/dist/components/score-analysis-modal.d.ts +26 -0
  147. package/dist/components/score-analysis-modal.js +141 -0
  148. package/dist/components/score-analysis-modal.js.map +1 -0
  149. package/dist/components/score-breakdown.d.ts +17 -0
  150. package/dist/components/score-breakdown.js +162 -0
  151. package/dist/components/score-breakdown.js.map +1 -0
  152. package/dist/components/score-feedback.d.ts +40 -0
  153. package/dist/components/score-feedback.js +209 -0
  154. package/dist/components/score-feedback.js.map +1 -0
  155. package/dist/components/score-ring.d.ts +14 -0
  156. package/dist/components/score-ring.js +79 -0
  157. package/dist/components/score-ring.js.map +1 -0
  158. package/dist/components/scroll-area.d.ts +7 -0
  159. package/dist/components/scroll-area.js +101 -0
  160. package/dist/components/scroll-area.js.map +1 -0
  161. package/dist/components/select.d.ts +17 -0
  162. package/dist/components/select.js +228 -0
  163. package/dist/components/select.js.map +1 -0
  164. package/dist/components/separator.d.ts +6 -0
  165. package/dist/components/separator.js +61 -0
  166. package/dist/components/separator.js.map +1 -0
  167. package/dist/components/sheet.d.ts +16 -0
  168. package/dist/components/sheet.js +168 -0
  169. package/dist/components/sheet.js.map +1 -0
  170. package/dist/components/sidebar.d.ts +73 -0
  171. package/dist/components/sidebar.js +723 -0
  172. package/dist/components/sidebar.js.map +1 -0
  173. package/dist/components/signal-feedback-inline.d.ts +51 -0
  174. package/dist/components/signal-feedback-inline.js +548 -0
  175. package/dist/components/signal-feedback-inline.js.map +1 -0
  176. package/dist/components/simple-data-table.d.ts +15 -0
  177. package/dist/components/simple-data-table.js +91 -0
  178. package/dist/components/simple-data-table.js.map +1 -0
  179. package/dist/components/skeleton.d.ts +5 -0
  180. package/dist/components/skeleton.js +44 -0
  181. package/dist/components/skeleton.js.map +1 -0
  182. package/dist/components/status-badge.d.ts +10 -0
  183. package/dist/components/status-badge.js +82 -0
  184. package/dist/components/status-badge.js.map +1 -0
  185. package/dist/components/styled-bar-list.d.ts +20 -0
  186. package/dist/components/styled-bar-list.js +59 -0
  187. package/dist/components/styled-bar-list.js.map +1 -0
  188. package/dist/components/suggested-actions.d.ts +110 -0
  189. package/dist/components/suggested-actions.js +1538 -0
  190. package/dist/components/suggested-actions.js.map +1 -0
  191. package/dist/components/table.d.ts +12 -0
  192. package/dist/components/table.js +147 -0
  193. package/dist/components/table.js.map +1 -0
  194. package/dist/components/tabs.d.ts +14 -0
  195. package/dist/components/tabs.js +129 -0
  196. package/dist/components/tabs.js.map +1 -0
  197. package/dist/components/textarea.d.ts +5 -0
  198. package/dist/components/textarea.js +47 -0
  199. package/dist/components/textarea.js.map +1 -0
  200. package/dist/components/timeline-activity.d.ts +34 -0
  201. package/dist/components/timeline-activity.js +181 -0
  202. package/dist/components/timeline-activity.js.map +1 -0
  203. package/dist/components/tooltip.d.ts +9 -0
  204. package/dist/components/tooltip.js +93 -0
  205. package/dist/components/tooltip.js.map +1 -0
  206. package/dist/components/view-mode-toggle.d.ts +16 -0
  207. package/dist/components/view-mode-toggle.js +24 -0
  208. package/dist/components/view-mode-toggle.js.map +1 -0
  209. package/dist/hooks/use-mobile.d.ts +3 -0
  210. package/dist/hooks/use-mobile.js +21 -0
  211. package/dist/hooks/use-mobile.js.map +1 -0
  212. package/dist/index.d.ts +68 -1878
  213. package/dist/index.js +69 -10918
  214. package/dist/index.js.map +1 -1
  215. package/dist/lib/icons.d.ts +18 -0
  216. package/dist/lib/icons.js +21 -0
  217. package/dist/lib/icons.js.map +1 -0
  218. package/dist/lib/utils.d.ts +5 -0
  219. package/dist/lib/utils.js +9 -0
  220. package/dist/lib/utils.js.map +1 -0
  221. package/dist/prototype/index.d.ts +20 -0
  222. package/dist/prototype/index.js +8 -0
  223. package/dist/prototype/index.js.map +1 -0
  224. package/dist/prototype/prototype-accounts-view.d.ts +22 -0
  225. package/dist/prototype/prototype-accounts-view.js +70 -0
  226. package/dist/prototype/prototype-accounts-view.js.map +1 -0
  227. package/dist/prototype/prototype-admin-view.d.ts +21 -0
  228. package/dist/prototype/prototype-admin-view.js +53 -0
  229. package/dist/prototype/prototype-admin-view.js.map +1 -0
  230. package/dist/prototype/prototype-config.d.ts +226 -0
  231. package/dist/prototype/prototype-config.js +1 -0
  232. package/dist/prototype/prototype-config.js.map +1 -0
  233. package/dist/prototype/prototype-inbox-view.d.ts +48 -0
  234. package/dist/prototype/prototype-inbox-view.js +701 -0
  235. package/dist/prototype/prototype-inbox-view.js.map +1 -0
  236. package/dist/prototype/prototype-insights-view.d.ts +23 -0
  237. package/dist/prototype/prototype-insights-view.js +335 -0
  238. package/dist/prototype/prototype-insights-view.js.map +1 -0
  239. package/dist/prototype/prototype-shell.d.ts +40 -0
  240. package/dist/prototype/prototype-shell.js +190 -0
  241. package/dist/prototype/prototype-shell.js.map +1 -0
  242. package/dist/prototype/prototype-work-queue-view.d.ts +8 -0
  243. package/dist/prototype/prototype-work-queue-view.js +17 -0
  244. package/dist/prototype/prototype-work-queue-view.js.map +1 -0
  245. package/dist/three/agent-orb.d.ts +39 -0
  246. package/dist/three/agent-orb.js +500 -0
  247. package/dist/three/agent-orb.js.map +1 -0
  248. package/dist/three/index.d.ts +2 -0
  249. package/dist/three/index.js +2 -0
  250. package/dist/three/index.js.map +1 -0
  251. package/package.json +98 -17
  252. package/src/charts/bar-chart-component.tsx +150 -0
  253. package/src/charts/chart-tooltip.tsx +86 -0
  254. package/src/charts/chart.tsx +371 -0
  255. package/src/charts/donut-chart.tsx +112 -0
  256. package/src/charts/index.ts +13 -0
  257. package/src/charts/pipeline-overview.tsx +476 -0
  258. package/src/charts/sankey-chart.tsx +290 -0
  259. package/src/charts/top-line-metrics.tsx +261 -0
  260. package/src/charts/trend-area-chart.tsx +150 -0
  261. package/src/charts/volume-analysis-chart.tsx +124 -0
  262. package/src/components/activity-detail.tsx +233 -0
  263. package/src/components/activity-log.tsx +89 -0
  264. package/src/components/agent-popover.tsx +373 -0
  265. package/src/components/agent-widget.tsx +163 -0
  266. package/src/components/avatar.tsx +109 -0
  267. package/src/components/badge.tsx +48 -0
  268. package/src/components/button.tsx +59 -0
  269. package/src/components/card.tsx +92 -0
  270. package/src/components/contact-list.tsx +121 -0
  271. package/src/components/dashboard-cards.tsx +170 -0
  272. package/src/components/data-table-display.tsx +139 -0
  273. package/src/components/data-table-filter.tsx +138 -0
  274. package/src/components/data-table-quick-views.tsx +103 -0
  275. package/src/components/data-table-toolbar.tsx +56 -0
  276. package/src/components/data-table.tsx +915 -0
  277. package/src/components/detail-view.tsx +237 -0
  278. package/src/components/dialog.tsx +158 -0
  279. package/src/components/dropdown-menu.tsx +257 -0
  280. package/src/components/entity-panel.tsx +767 -0
  281. package/src/components/inbox-row.tsx +132 -0
  282. package/src/components/inbox-toolbar.tsx +213 -0
  283. package/src/components/input.tsx +21 -0
  284. package/src/components/insights-filter-bar.tsx +132 -0
  285. package/src/components/item-list-display.tsx +278 -0
  286. package/src/components/item-list-filter.tsx +118 -0
  287. package/src/components/item-list-toolbar.tsx +97 -0
  288. package/src/components/item-list.tsx +843 -0
  289. package/src/components/label.tsx +24 -0
  290. package/src/components/message.tsx +83 -0
  291. package/src/components/metric-card.tsx +178 -0
  292. package/src/components/performance-metrics-table.tsx +442 -0
  293. package/src/components/preview-list.tsx +62 -0
  294. package/src/components/progress.tsx +31 -0
  295. package/src/components/quick-action-chat-area.tsx +156 -0
  296. package/src/components/quick-action-modal.tsx +331 -0
  297. package/src/components/quick-action-sidebar-nav.tsx +592 -0
  298. package/src/components/recommended-actions-section.tsx +258 -0
  299. package/src/components/report-card.tsx +106 -0
  300. package/src/components/score-analysis-modal.tsx +172 -0
  301. package/src/components/score-breakdown.tsx +179 -0
  302. package/src/components/score-feedback.tsx +288 -0
  303. package/src/components/score-ring.tsx +87 -0
  304. package/src/components/scroll-area.tsx +58 -0
  305. package/src/components/select.tsx +190 -0
  306. package/src/components/separator.tsx +28 -0
  307. package/src/components/sheet.tsx +143 -0
  308. package/src/components/sidebar.tsx +726 -0
  309. package/src/components/signal-feedback-inline.tsx +591 -0
  310. package/src/components/simple-data-table.tsx +124 -0
  311. package/src/components/skeleton.tsx +15 -0
  312. package/src/components/status-badge.tsx +63 -0
  313. package/src/components/styled-bar-list.tsx +70 -0
  314. package/src/components/suggested-actions.tsx +1985 -0
  315. package/src/components/table.tsx +116 -0
  316. package/src/components/tabs.tsx +91 -0
  317. package/src/components/textarea.tsx +18 -0
  318. package/src/components/timeline-activity.tsx +234 -0
  319. package/src/components/tooltip.tsx +57 -0
  320. package/src/components/view-mode-toggle.tsx +39 -0
  321. package/src/hooks/use-mobile.ts +21 -0
  322. package/src/index.ts +77 -0
  323. package/src/lib/icons.ts +18 -0
  324. package/src/lib/utils.ts +6 -0
  325. package/src/prototype/index.ts +11 -0
  326. package/src/prototype/prototype-accounts-view.tsx +112 -0
  327. package/src/prototype/prototype-admin-view.tsx +67 -0
  328. package/src/prototype/prototype-config.ts +243 -0
  329. package/src/prototype/prototype-inbox-view.tsx +810 -0
  330. package/src/prototype/prototype-insights-view.tsx +379 -0
  331. package/src/prototype/prototype-shell.tsx +219 -0
  332. package/src/prototype/prototype-work-queue-view.tsx +30 -0
  333. package/src/styles/globals.css +299 -0
  334. package/src/three/agent-orb.tsx +557 -0
  335. package/src/three/index.ts +5 -0
  336. package/src/types/r3f.d.ts +8 -0
@@ -0,0 +1,290 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ResponsiveSankey } from "@nivo/sankey"
5
+
6
+ import { cn } from "../lib/utils"
7
+
8
+ export interface SankeyNode {
9
+ id: string
10
+ nodeColor?: string
11
+ }
12
+
13
+ export interface SankeyLink {
14
+ source: string
15
+ target: string
16
+ value: number
17
+ }
18
+
19
+ export interface SankeyData {
20
+ nodes: SankeyNode[]
21
+ links: SankeyLink[]
22
+ }
23
+
24
+ export interface SankeyDropOff {
25
+ reason: string
26
+ count: number
27
+ pct?: string
28
+ }
29
+
30
+ export interface SankeyStageMetrics {
31
+ conversion: string
32
+ medianTime: string
33
+ avgTime: string
34
+ }
35
+
36
+ export interface SankeyHoverCardData {
37
+ title: string
38
+ count: number | string
39
+ metrics?: SankeyStageMetrics
40
+ dropOffs?: SankeyDropOff[]
41
+ }
42
+
43
+ interface HoveredNodeState {
44
+ id: string
45
+ x: number
46
+ y: number
47
+ data: SankeyHoverCardData
48
+ pinned?: boolean
49
+ }
50
+
51
+ interface SankeyChartProps {
52
+ data: SankeyData
53
+ height?: number
54
+ nodeOpacity?: number
55
+ nodeThickness?: number
56
+ nodeBorderWidth?: number
57
+ linkOpacity?: number
58
+ linkHoverOpacity?: number
59
+ enableLabels?: boolean
60
+ labelTextColor?: string
61
+ stageMetrics?: Record<string, { metrics: SankeyStageMetrics; dropOffs: SankeyDropOff[] }>
62
+ onDropOffClick?: (reason: string) => void
63
+ onViewDetails?: (nodeId: string) => void
64
+ className?: string
65
+ }
66
+
67
+ function HoverCard({
68
+ data,
69
+ position,
70
+ onDropOffClick,
71
+ onViewDetails,
72
+ }: {
73
+ data: SankeyHoverCardData
74
+ position: { x: number; y: number }
75
+ onDropOffClick?: (reason: string) => void
76
+ onViewDetails?: () => void
77
+ }) {
78
+ return (
79
+ <div
80
+ className="pointer-events-auto absolute z-50 w-[260px] overflow-hidden rounded-lg border border-border bg-card font-sans text-left shadow-xl"
81
+ style={{ left: position.x + 10, top: position.y - 40 }}
82
+ >
83
+ <div className="border-b border-border p-3">
84
+ <div className="mb-0.5 text-xs font-medium text-muted-foreground">
85
+ {data.title}
86
+ </div>
87
+ <div className="text-2xl font-bold text-foreground">{data.count}</div>
88
+ </div>
89
+
90
+ {data.metrics ? (
91
+ <div className="grid grid-cols-3 gap-2 border-b border-border bg-muted/30 px-3 py-2">
92
+ <div>
93
+ <div className="text-[9px] font-semibold uppercase tracking-wider text-muted-foreground/70">
94
+ Conversion
95
+ </div>
96
+ <div className="text-xs font-bold text-emerald-600">
97
+ {data.metrics.conversion}
98
+ </div>
99
+ </div>
100
+ <div className="border-l border-border pl-2">
101
+ <div className="text-[9px] font-semibold uppercase tracking-wider text-muted-foreground/70">
102
+ Median
103
+ </div>
104
+ <div className="text-xs font-bold text-foreground">
105
+ {data.metrics.medianTime}
106
+ </div>
107
+ </div>
108
+ <div className="border-l border-border pl-2">
109
+ <div className="text-[9px] font-semibold uppercase tracking-wider text-muted-foreground/70">
110
+ Average
111
+ </div>
112
+ <div className="text-xs font-bold text-foreground">
113
+ {data.metrics.avgTime}
114
+ </div>
115
+ </div>
116
+ </div>
117
+ ) : null}
118
+
119
+ {data.dropOffs && data.dropOffs.length > 0 ? (
120
+ <div className="p-2">
121
+ <div className="mb-1.5 px-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">
122
+ Drop-off Reasons
123
+ </div>
124
+ <div className="space-y-0.5">
125
+ {data.dropOffs.map((drop, i) => (
126
+ <div
127
+ key={i}
128
+ onClick={(e) => {
129
+ e.stopPropagation()
130
+ onDropOffClick?.(drop.reason)
131
+ }}
132
+ className="group flex cursor-pointer items-center justify-between rounded p-1.5 text-xs transition-colors hover:bg-muted"
133
+ >
134
+ <span className="truncate pr-2 text-muted-foreground group-hover:text-foreground">
135
+ {drop.reason}
136
+ </span>
137
+ <div className="flex items-center gap-1.5">
138
+ {drop.pct ? (
139
+ <span className="text-[10px] text-muted-foreground/70">
140
+ {drop.pct}
141
+ </span>
142
+ ) : null}
143
+ <span className="inline-flex h-4 min-w-[20px] items-center justify-center rounded border border-border bg-muted px-1 text-[9px] font-semibold text-muted-foreground group-hover:bg-muted group-hover:text-foreground">
144
+ {drop.count}
145
+ </span>
146
+ </div>
147
+ </div>
148
+ ))}
149
+ </div>
150
+ </div>
151
+ ) : null}
152
+
153
+ {onViewDetails ? (
154
+ <div
155
+ onClick={(e) => {
156
+ e.stopPropagation()
157
+ onViewDetails()
158
+ }}
159
+ className="flex cursor-pointer items-center justify-between border-t border-border bg-muted px-3 py-2 text-xs font-medium text-blue-600 transition-colors hover:bg-muted hover:text-blue-700"
160
+ >
161
+ View Details
162
+ <svg
163
+ className="h-3 w-3"
164
+ fill="none"
165
+ viewBox="0 0 24 24"
166
+ stroke="currentColor"
167
+ strokeWidth={2}
168
+ >
169
+ <path
170
+ strokeLinecap="round"
171
+ strokeLinejoin="round"
172
+ d="M9 5l7 7-7 7"
173
+ />
174
+ </svg>
175
+ </div>
176
+ ) : null}
177
+ </div>
178
+ )
179
+ }
180
+
181
+ export function SankeyChart({
182
+ data,
183
+ height = 400,
184
+ nodeOpacity = 1,
185
+ nodeThickness = 18,
186
+ nodeBorderWidth = 0,
187
+ linkOpacity = 0.25,
188
+ linkHoverOpacity = 0.5,
189
+ enableLabels = true,
190
+ labelTextColor = "#334155",
191
+ stageMetrics,
192
+ onDropOffClick,
193
+ onViewDetails,
194
+ className,
195
+ }: SankeyChartProps) {
196
+ const [hoveredNode, setHoveredNode] = React.useState<HoveredNodeState | null>(
197
+ null,
198
+ )
199
+ const containerRef = React.useRef<HTMLDivElement>(null)
200
+
201
+ const handleNodeHover = React.useCallback(
202
+ (node: { id: string; value?: number }, event: React.MouseEvent) => {
203
+ if (hoveredNode?.pinned) return
204
+
205
+ const containerRect = containerRef.current?.getBoundingClientRect()
206
+ if (!containerRect) return
207
+
208
+ const nodeId = node.id as string
209
+ const stageKey = nodeId.toLowerCase().replace(/[^a-z]/g, "")
210
+ const meta = stageMetrics?.[stageKey] || stageMetrics?.[nodeId]
211
+
212
+ const hoverData: SankeyHoverCardData = {
213
+ title: nodeId,
214
+ count: node.value ?? 0,
215
+ metrics: meta?.metrics,
216
+ dropOffs: meta?.dropOffs,
217
+ }
218
+
219
+ setHoveredNode({
220
+ id: nodeId,
221
+ x: event.clientX - containerRect.left,
222
+ y: event.clientY - containerRect.top,
223
+ data: hoverData,
224
+ })
225
+ },
226
+ [hoveredNode?.pinned, stageMetrics],
227
+ )
228
+
229
+ const handleNodeLeave = React.useCallback(() => {
230
+ if (!hoveredNode?.pinned) {
231
+ setHoveredNode(null)
232
+ }
233
+ }, [hoveredNode?.pinned])
234
+
235
+ const handleClick = React.useCallback(() => {
236
+ if (hoveredNode?.pinned) {
237
+ setHoveredNode(null)
238
+ } else if (hoveredNode) {
239
+ setHoveredNode((prev) => (prev ? { ...prev, pinned: true } : null))
240
+ }
241
+ }, [hoveredNode])
242
+
243
+ return (
244
+ <div
245
+ ref={containerRef}
246
+ className={cn("relative", className)}
247
+ style={{ height }}
248
+ onClick={handleClick}
249
+ >
250
+ <ResponsiveSankey
251
+ data={data}
252
+ margin={{ top: 20, right: 160, bottom: 20, left: 20 }}
253
+ align="justify"
254
+ colors={(node: { nodeColor?: string }) => node.nodeColor || "#94a3b8"}
255
+ nodeOpacity={nodeOpacity}
256
+ nodeHoverOpacity={1}
257
+ nodeThickness={nodeThickness}
258
+ nodeSpacing={24}
259
+ nodeBorderWidth={nodeBorderWidth}
260
+ nodeBorderColor={{ from: "color", modifiers: [["darker", 0.8]] }}
261
+ nodeBorderRadius={3}
262
+ linkOpacity={linkOpacity}
263
+ linkHoverOpacity={linkHoverOpacity}
264
+ linkHoverOthersOpacity={0.1}
265
+ linkContract={3}
266
+ enableLinkGradient
267
+ labelPosition="outside"
268
+ labelOrientation="horizontal"
269
+ labelPadding={16}
270
+ labelTextColor={labelTextColor}
271
+ enableLabels={enableLabels}
272
+ animate
273
+ {...({ onNodeMouseEnter: handleNodeHover, onNodeMouseLeave: handleNodeLeave } as Record<string, unknown>)}
274
+ />
275
+
276
+ {hoveredNode ? (
277
+ <HoverCard
278
+ data={hoveredNode.data}
279
+ position={{ x: hoveredNode.x, y: hoveredNode.y }}
280
+ onDropOffClick={onDropOffClick}
281
+ onViewDetails={
282
+ onViewDetails
283
+ ? () => onViewDetails(hoveredNode.id)
284
+ : undefined
285
+ }
286
+ />
287
+ ) : null}
288
+ </div>
289
+ )
290
+ }
@@ -0,0 +1,261 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ AreaChart,
6
+ Area,
7
+ Tooltip,
8
+ ResponsiveContainer,
9
+ } from "recharts"
10
+ import { ArrowUp, ArrowDown, MoreHorizontal } from "lucide-react"
11
+
12
+ import { cn } from "../lib/utils"
13
+ import { Button } from "../components/button"
14
+
15
+ export interface MetricCardData {
16
+ label: string
17
+ value: string | number
18
+ trend?: {
19
+ value: number
20
+ direction: "up" | "down" | "neutral"
21
+ }
22
+ alert?: boolean
23
+ }
24
+
25
+ export interface TopLineMetricsProps {
26
+ metrics: MetricCardData[]
27
+ chartData?: { date: string; value: number; secondary?: number }[]
28
+ filters?: string[]
29
+ activeFilter?: string
30
+ onFilterChange?: (filter: string) => void
31
+ className?: string
32
+ }
33
+
34
+ function generateDefaultData() {
35
+ const data = []
36
+ const base = 150
37
+ for (let i = 0; i < 7; i++) {
38
+ const date = new Date()
39
+ date.setDate(date.getDate() - (6 - i))
40
+ const dayFactor = Math.sin(i * 0.8) * 40
41
+ const value = Math.floor(base + dayFactor + Math.random() * 30)
42
+ const secondary = Math.floor(value * 0.4)
43
+ data.push({
44
+ date: date.toLocaleDateString("en-US", {
45
+ month: "2-digit",
46
+ day: "2-digit",
47
+ }),
48
+ value,
49
+ secondary,
50
+ })
51
+ }
52
+ return data
53
+ }
54
+
55
+ const DEFAULT_DATA = generateDefaultData()
56
+
57
+ export function TopLineMetrics({
58
+ metrics,
59
+ chartData,
60
+ filters,
61
+ activeFilter,
62
+ onFilterChange,
63
+ className,
64
+ }: TopLineMetricsProps) {
65
+ const [internalFilter, setInternalFilter] = React.useState(
66
+ activeFilter ?? filters?.[0] ?? "All",
67
+ )
68
+ const currentFilter = activeFilter ?? internalFilter
69
+ const data = chartData ?? DEFAULT_DATA
70
+ const primaryMetric = metrics[0]
71
+
72
+ const handleFilterClick = (f: string) => {
73
+ setInternalFilter(f)
74
+ onFilterChange?.(f)
75
+ }
76
+
77
+ return (
78
+ <div className={cn("w-full", className)}>
79
+ <div className="overflow-hidden rounded-xl border border-border bg-card shadow-sm">
80
+ <div className="flex flex-col gap-4 border-b border-border p-6">
81
+ <div className="flex items-start justify-between">
82
+ <div>
83
+ <div className="mb-4 flex items-center gap-4">
84
+ <h3 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
85
+ Work Queue Activity
86
+ </h3>
87
+ {filters && filters.length > 0 ? (
88
+ <>
89
+ <div className="h-4 w-px bg-muted" />
90
+ <div className="flex gap-1">
91
+ {filters.map((f) => (
92
+ <button
93
+ key={f}
94
+ onClick={() => handleFilterClick(f)}
95
+ className={cn(
96
+ "rounded-full px-2 py-0.5 text-xs font-medium transition-colors",
97
+ currentFilter === f
98
+ ? "bg-muted text-foreground"
99
+ : "text-muted-foreground hover:text-foreground",
100
+ )}
101
+ >
102
+ {f}
103
+ </button>
104
+ ))}
105
+ </div>
106
+ </>
107
+ ) : null}
108
+ </div>
109
+
110
+ <div className="flex items-baseline gap-3">
111
+ <span className="text-4xl font-bold tracking-tight text-foreground">
112
+ {primaryMetric.value}
113
+ </span>
114
+ <span className="text-lg font-medium text-muted-foreground">
115
+ {primaryMetric.label}
116
+ </span>
117
+ {primaryMetric.trend ? (
118
+ <span
119
+ className={cn(
120
+ "ml-2 flex items-center rounded-full px-2 py-0.5 text-sm font-bold",
121
+ primaryMetric.trend.direction === "up"
122
+ ? "bg-emerald-50 text-emerald-700"
123
+ : "bg-red-50 text-red-700",
124
+ )}
125
+ >
126
+ {primaryMetric.trend.direction === "up" ? (
127
+ <ArrowUp className="mr-1 h-3.5 w-3.5" />
128
+ ) : (
129
+ <ArrowDown className="mr-1 h-3.5 w-3.5" />
130
+ )}
131
+ {primaryMetric.trend.value}%
132
+ </span>
133
+ ) : null}
134
+ </div>
135
+ </div>
136
+
137
+ <Button
138
+ variant="ghost"
139
+ size="sm"
140
+ className="h-8 w-8 p-0 text-muted-foreground/70 hover:text-muted-foreground"
141
+ >
142
+ <MoreHorizontal className="h-5 w-5" />
143
+ </Button>
144
+ </div>
145
+ </div>
146
+
147
+ <div className="group relative h-[240px] w-full">
148
+ <ResponsiveContainer width="100%" height="100%">
149
+ <AreaChart
150
+ data={data}
151
+ margin={{ top: 0, right: 0, left: 0, bottom: 0 }}
152
+ >
153
+ <defs>
154
+ <linearGradient
155
+ id="tlm-primary"
156
+ x1="0"
157
+ y1="0"
158
+ x2="0"
159
+ y2="1"
160
+ >
161
+ <stop offset="5%" stopColor="#10b981" stopOpacity={0.3} />
162
+ <stop offset="95%" stopColor="#10b981" stopOpacity={0} />
163
+ </linearGradient>
164
+ <linearGradient
165
+ id="tlm-secondary"
166
+ x1="0"
167
+ y1="0"
168
+ x2="0"
169
+ y2="1"
170
+ >
171
+ <stop offset="5%" stopColor="#f97316" stopOpacity={0.3} />
172
+ <stop offset="95%" stopColor="#f97316" stopOpacity={0} />
173
+ </linearGradient>
174
+ </defs>
175
+ <Tooltip
176
+ content={({ active, payload }) => {
177
+ if (!active || !payload?.length) return null
178
+ return (
179
+ <div className="min-w-[200px] animate-in fade-in zoom-in-95 rounded-lg border border-border bg-card/95 p-4 shadow-lg backdrop-blur-sm duration-200">
180
+ <div className="mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground/70">
181
+ Metrics Snapshot
182
+ </div>
183
+ <div className="space-y-3">
184
+ {metrics.map((m, i) => (
185
+ <div
186
+ key={i}
187
+ className="flex items-center justify-between gap-4"
188
+ >
189
+ <span
190
+ className={cn(
191
+ "text-sm font-medium",
192
+ i === 0
193
+ ? "font-bold text-foreground"
194
+ : "text-muted-foreground",
195
+ )}
196
+ >
197
+ {m.label}
198
+ </span>
199
+ <div className="flex items-center gap-2">
200
+ <span
201
+ className={cn(
202
+ "font-mono text-sm",
203
+ m.alert
204
+ ? "font-bold text-red-600"
205
+ : "text-foreground",
206
+ )}
207
+ >
208
+ {m.value}
209
+ </span>
210
+ {m.alert ? (
211
+ <div className="h-1.5 w-1.5 animate-pulse rounded-full bg-red-500" />
212
+ ) : null}
213
+ </div>
214
+ </div>
215
+ ))}
216
+ </div>
217
+ </div>
218
+ )
219
+ }}
220
+ cursor={{
221
+ stroke: "#94a3b8",
222
+ strokeWidth: 1,
223
+ strokeDasharray: "4 4",
224
+ }}
225
+ />
226
+ <Area
227
+ type="monotone"
228
+ dataKey="value"
229
+ stroke="#10b981"
230
+ strokeWidth={3}
231
+ fillOpacity={1}
232
+ fill="url(#tlm-primary)"
233
+ />
234
+ {data[0]?.secondary != null ? (
235
+ <Area
236
+ type="monotone"
237
+ dataKey="secondary"
238
+ stroke="#f97316"
239
+ strokeWidth={3}
240
+ fillOpacity={1}
241
+ fill="url(#tlm-secondary)"
242
+ style={{ opacity: 0.6 }}
243
+ />
244
+ ) : null}
245
+ </AreaChart>
246
+ </ResponsiveContainer>
247
+
248
+ <div className="pointer-events-none absolute bottom-4 left-0 right-0 flex justify-between px-8 text-xs font-medium text-muted-foreground/70">
249
+ {data.map((d, i) => (
250
+ <span key={i}>{d.date}</span>
251
+ ))}
252
+ </div>
253
+
254
+ <div className="pointer-events-none absolute right-4 top-4 rounded bg-background/50 px-2 py-1 text-[10px] text-muted-foreground opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100">
255
+ Hover for details
256
+ </div>
257
+ </div>
258
+ </div>
259
+ </div>
260
+ )
261
+ }
@@ -0,0 +1,150 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ AreaChart,
6
+ Area,
7
+ XAxis,
8
+ YAxis,
9
+ CartesianGrid,
10
+ Tooltip,
11
+ ResponsiveContainer,
12
+ } from "recharts"
13
+
14
+ import { cn } from "../lib/utils"
15
+ import { CHART_TOOLTIP_STYLE, CHART_CURSOR_STYLE } from "./chart-tooltip"
16
+
17
+ export interface TrendSeries {
18
+ dataKey: string
19
+ color: string
20
+ label?: string
21
+ fillOpacity?: number
22
+ }
23
+
24
+ export interface TrendAreaChartProps {
25
+ data: Record<string, unknown>[]
26
+ series: TrendSeries[]
27
+ height?: number
28
+ xAxisKey?: string
29
+ showGrid?: boolean
30
+ yDomain?: [number, number]
31
+ tooltipFormatter?: (value: number | string, name: string) => [string, string]
32
+ className?: string
33
+ }
34
+
35
+ export function TrendAreaChart({
36
+ data,
37
+ series,
38
+ height = 250,
39
+ xAxisKey = "date",
40
+ showGrid = true,
41
+ yDomain,
42
+ tooltipFormatter,
43
+ className,
44
+ }: TrendAreaChartProps) {
45
+ const chartId = React.useId().replace(/:/g, "")
46
+
47
+ const CustomTooltip = React.useCallback(
48
+ ({ active, payload, label }: { active?: boolean; payload?: Array<{ value?: number | string; name?: string; dataKey?: string; color?: string }>; label?: string }) => {
49
+ if (!active || !payload || !payload.length) return null
50
+
51
+ return (
52
+ <div
53
+ className="rounded-lg border border-border bg-background px-3 py-2 shadow-md"
54
+ style={{ fontSize: "12px" }}
55
+ >
56
+ {label ? (
57
+ <p className="mb-1 text-xs font-medium text-muted-foreground">{label}</p>
58
+ ) : null}
59
+ {payload.map((entry: { value?: number | string; name?: string; dataKey?: string; color?: string; stroke?: string }, i: number) => {
60
+ const val = entry.value ?? ""
61
+ const name = entry.name || entry.dataKey || ""
62
+ const [displayValue, displayName] = tooltipFormatter
63
+ ? tooltipFormatter(val, name)
64
+ : [String(val), name]
65
+ return (
66
+ <div key={i} className="flex items-center gap-2">
67
+ <div
68
+ className="h-2 w-2 shrink-0 rounded-full"
69
+ style={{ backgroundColor: entry.color || entry.stroke }}
70
+ />
71
+ <span className="text-xs font-medium text-foreground">
72
+ {displayName}: {displayValue}
73
+ </span>
74
+ </div>
75
+ )
76
+ })}
77
+ </div>
78
+ )
79
+ },
80
+ [tooltipFormatter],
81
+ )
82
+
83
+ return (
84
+ <div className={cn("w-full", className)} style={{ height }}>
85
+ <ResponsiveContainer width="100%" height="100%">
86
+ <AreaChart
87
+ data={data}
88
+ margin={{ top: 10, right: 10, left: -20, bottom: 0 }}
89
+ >
90
+ <defs>
91
+ {series.map((s, i) => (
92
+ <linearGradient
93
+ key={s.dataKey}
94
+ id={`trend-${chartId}-${i}`}
95
+ x1="0"
96
+ y1="0"
97
+ x2="0"
98
+ y2="1"
99
+ >
100
+ <stop
101
+ offset="5%"
102
+ stopColor={s.color}
103
+ stopOpacity={s.fillOpacity ?? 0.15}
104
+ />
105
+ <stop offset="95%" stopColor={s.color} stopOpacity={0} />
106
+ </linearGradient>
107
+ ))}
108
+ </defs>
109
+ {showGrid ? (
110
+ <CartesianGrid
111
+ strokeDasharray="3 3"
112
+ vertical={false}
113
+ stroke="#e2e8f0"
114
+ />
115
+ ) : null}
116
+ <XAxis
117
+ dataKey={xAxisKey}
118
+ axisLine={false}
119
+ tickLine={false}
120
+ tick={{ fontSize: 10, fill: "#64748b" }}
121
+ dy={10}
122
+ />
123
+ <YAxis
124
+ axisLine={false}
125
+ tickLine={false}
126
+ tick={{ fontSize: 10, fill: "#64748b" }}
127
+ domain={yDomain}
128
+ />
129
+ <Tooltip
130
+ content={tooltipFormatter ? <CustomTooltip /> : undefined}
131
+ contentStyle={tooltipFormatter ? undefined : CHART_TOOLTIP_STYLE}
132
+ cursor={CHART_CURSOR_STYLE}
133
+ />
134
+ {series.map((s, i) => (
135
+ <Area
136
+ key={s.dataKey}
137
+ type="linear"
138
+ dataKey={s.dataKey}
139
+ stroke={s.color}
140
+ strokeWidth={2}
141
+ fillOpacity={1}
142
+ fill={`url(#trend-${chartId}-${i})`}
143
+ activeDot={{ r: 4, strokeWidth: 0 }}
144
+ />
145
+ ))}
146
+ </AreaChart>
147
+ </ResponsiveContainer>
148
+ </div>
149
+ )
150
+ }