@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,810 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ ArrowLeft,
6
+ ChevronDown,
7
+ ChevronRight,
8
+ Filter,
9
+ FileText,
10
+ Clock,
11
+ CheckSquare,
12
+ Eye,
13
+ Plus,
14
+
15
+ Building,
16
+ LayoutList,
17
+ Columns2,
18
+ Square,
19
+ Tag,
20
+ } from "lucide-react"
21
+
22
+ import { Button } from "../components/button"
23
+ import { Badge } from "../components/badge"
24
+ import { Input } from "../components/input"
25
+ import { ViewModeToggle } from "../components/view-mode-toggle"
26
+ import {
27
+ InboxToolbar,
28
+ type AssigneeFilter,
29
+ type InboxFilterCategory,
30
+ } from "../components/inbox-toolbar"
31
+ import { GroupedListView, type GroupedListGroup } from "../components/item-list"
32
+ import { SignalApproval } from "../components/signal-feedback-inline"
33
+ import { ScoreBreakdown } from "../components/score-breakdown"
34
+ import { Citation, type SourceDef } from "../components/detail-view"
35
+ import {
36
+ SuggestedActions,
37
+ type SuggestedAction,
38
+ type SuggestedContact,
39
+ } from "../components/suggested-actions"
40
+ import { TimelineActivity, type TimelineEvent } from "../components/timeline-activity"
41
+ import type {
42
+ QueueItem,
43
+ InboxViewConfig,
44
+ InboxDetailSections,
45
+ SignalScoreData,
46
+ } from "./prototype-config"
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Props
50
+ // ---------------------------------------------------------------------------
51
+
52
+ export interface PrototypeInboxViewProps extends InboxViewConfig {
53
+ /** Extra ReactNode rendered at the end of the header bar (e.g. exit button). */
54
+ headerActions?: React.ReactNode
55
+ onOpenEntityPanel?: () => void
56
+ onOpenRecentActivity?: () => void
57
+ onNavigateToInbox?: () => void
58
+ onItemSelect?: (item: QueueItem) => void
59
+ defaultViewMode?: "list" | "split"
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Default detail sections
64
+ // ---------------------------------------------------------------------------
65
+
66
+ const DEFAULT_DETAIL_SECTIONS: InboxDetailSections = {
67
+ signalBrief: true,
68
+ suggestedActions: true,
69
+ timeline: true,
70
+ }
71
+
72
+ const DEFAULT_SIGNAL_SCORE: SignalScoreData = {
73
+ score: 65,
74
+ factors: [
75
+ { key: "trigger", label: "Trigger strength", score: 70, why: "Moderate signal detected based on account activity" },
76
+ { key: "fit", label: "Company fit", score: 65, why: "Reasonable fit based on company profile" },
77
+ { key: "timing", label: "Timing", score: 58, why: "Within general evaluation window" },
78
+ ],
79
+ whyNow: "Moderate signals detected that warrant review and potential outreach.",
80
+ evidence: [
81
+ "Activity patterns suggest potential opportunity",
82
+ "Company profile aligns with target segment",
83
+ ],
84
+ confidence: 72,
85
+ }
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Detail View
89
+ // ---------------------------------------------------------------------------
90
+
91
+ export interface DetailViewProps {
92
+ item: QueueItem
93
+ sections: InboxDetailSections
94
+ getSignalScore: (company: string) => SignalScoreData
95
+ buildSuggestedActions: (item: QueueItem) => SuggestedAction[]
96
+ buildSourceItems: (item: QueueItem) => SourceDef[]
97
+ getTimelineEvents?: (item: QueueItem) => TimelineEvent[]
98
+ accountContacts: SuggestedContact[]
99
+ emailSignature: string | React.ReactNode
100
+ iconMap: Record<string, string>
101
+ onOpenEntityPanel?: () => void
102
+ onOpenRecentActivity?: () => void
103
+ onSuggestedActionFeedback?: (actionId: number | string, feedback: string, actionTitle?: string) => void
104
+ signalLabels?: { approveButton?: string; dismissButton?: string; approvedStatus?: string; dismissedStatus?: string }
105
+ }
106
+
107
+ export function DetailView({
108
+ item,
109
+ sections,
110
+ getSignalScore,
111
+ buildSuggestedActions,
112
+ buildSourceItems,
113
+ getTimelineEvents,
114
+ accountContacts,
115
+ emailSignature,
116
+ iconMap,
117
+ onOpenEntityPanel,
118
+ onOpenRecentActivity,
119
+ onSuggestedActionFeedback: _onSuggestedActionFeedback,
120
+ signalLabels,
121
+ }: DetailViewProps) {
122
+ const [evidenceExpanded, setEvidenceExpanded] = React.useState(false)
123
+ const [showTimeline, setShowTimeline] = React.useState(false)
124
+ const [extraActions, setExtraActions] = React.useState<SuggestedAction[]>([])
125
+
126
+ React.useEffect(() => {
127
+ setShowTimeline(false)
128
+ setEvidenceExpanded(false)
129
+ setExtraActions([])
130
+ }, [item.id])
131
+
132
+ const signalData = React.useMemo(
133
+ () => getSignalScore(item.company),
134
+ [getSignalScore, item.company],
135
+ )
136
+
137
+ const suggestedActions = React.useMemo(
138
+ () => [...buildSuggestedActions(item), ...extraActions],
139
+ [buildSuggestedActions, item, extraActions],
140
+ )
141
+ const sourceItems = React.useMemo(() => buildSourceItems(item), [buildSourceItems, item])
142
+ const timelineEvents = React.useMemo(
143
+ () => getTimelineEvents?.(item) ?? [],
144
+ [getTimelineEvents, item],
145
+ )
146
+
147
+ const handleDuplicate = React.useCallback(
148
+ (id: number | string) => {
149
+ const base = suggestedActions.find((a) => a.id === id)
150
+ if (!base || base.type !== "email") return
151
+ const clone: SuggestedAction = {
152
+ ...base,
153
+ id: `${base.id}-dup-${Date.now()}`,
154
+ emailMeta: base.emailMeta ? { ...base.emailMeta, to: undefined } : undefined,
155
+ }
156
+ setExtraActions((prev) => [...prev, clone])
157
+ },
158
+ [suggestedActions],
159
+ )
160
+
161
+ return (
162
+ <SignalApproval.Root
163
+ companyName={item.company}
164
+ opportunityUrl={`https://acme.lightning.force.com/lightning/r/Opportunity/006${item.id}/view`}
165
+ labels={signalLabels}
166
+ onApprove={() => {
167
+ console.log("Approved signal:", { taskId: item.id, company: item.company })
168
+ }}
169
+ onApproveFeedback={(reasons, detail) => {
170
+ signalData.onApproveFeedback?.(reasons, detail)
171
+ console.log("Approval feedback:", { taskId: item.id, company: item.company, reasons, detail })
172
+ }}
173
+ onDismiss={(reasons, detail) => {
174
+ signalData.onDismissFeedback?.(reasons, detail)
175
+ console.log("Dismissed signal:", { taskId: item.id, reasons, detail })
176
+ }}
177
+ >
178
+ <div className="mx-auto w-full max-w-3xl p-6 pb-12 md:p-8">
179
+ <div className="pb-8">
180
+ {/* Header */}
181
+ <div className="mb-4 flex items-center gap-2">
182
+ <button
183
+ type="button"
184
+ className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
185
+ >
186
+ <ArrowLeft className="h-3.5 w-3.5" />
187
+ Back
188
+ </button>
189
+ <span className="text-muted-foreground/40">&middot;</span>
190
+ <span className="text-xs text-muted-foreground">{item.company}</span>
191
+ </div>
192
+
193
+ <h1 className="mb-3 text-2xl font-bold tracking-tight text-foreground">{item.title}</h1>
194
+
195
+ <div className="mb-6 flex flex-wrap items-center gap-2">
196
+ {item.statusColor === "red" && (
197
+ <div className="inline-flex items-center gap-1 rounded-md bg-red-50 px-2.5 py-1 text-xs font-semibold text-red-700">
198
+ <span className="text-[10px] font-bold">!</span> Urgent
199
+ </div>
200
+ )}
201
+ <div className="inline-flex items-center gap-1 rounded-md bg-muted px-2.5 py-1 text-xs font-medium text-muted-foreground">
202
+ {item.tag1}
203
+ </div>
204
+ <button
205
+ type="button"
206
+ onClick={onOpenEntityPanel}
207
+ className="ml-1 inline-flex items-center gap-1.5 rounded-md border border-border/60 bg-muted/30 px-2 py-1 transition-colors hover:bg-muted/50"
208
+ >
209
+ <div className="flex h-4 w-4 items-center justify-center rounded bg-muted-foreground/10 text-[9px] font-semibold text-muted-foreground">
210
+ {item.company.substring(0, 1)}
211
+ </div>
212
+ <span className="text-xs font-medium text-foreground">{item.company}</span>
213
+ <ChevronRight className="h-3 w-3 text-muted-foreground/50" />
214
+ </button>
215
+ </div>
216
+
217
+ {/* Signal Brief */}
218
+ {sections.signalBrief && (() => {
219
+ const pct = signalData.score
220
+ const scoreColor = pct >= 70 ? "text-emerald-600" : pct >= 40 ? "text-amber-600" : "text-red-600"
221
+ const barColor = pct >= 70 ? "bg-emerald-500" : pct >= 40 ? "bg-amber-500" : "bg-red-500"
222
+ const scoreLabel = pct >= 70 ? "HIGH" : pct >= 40 ? "MEDIUM" : "LOW"
223
+
224
+ const evidenceWithCitations: React.ReactNode[] =
225
+ sourceItems.length >= 4
226
+ ? [
227
+ <>
228
+ There are <span className="font-medium text-foreground">3 unusual signals</span> including a large balance
229
+ outflow and reduced login frequency
230
+ <Citation number={1} source={sourceItems[0]} />
231
+ <Citation number={2} source={sourceItems[1]} />
232
+ <Citation number={3} source={sourceItems[2]} />
233
+ </>,
234
+ <>
235
+ Scott mentioned in <span className="font-medium text-foreground">#treasury-questions</span> that they are actively
236
+ looking for treasury management options
237
+ <Citation number={4} source={sourceItems[2]} />
238
+ </>,
239
+ <>
240
+ You have a recent email thread regarding optimization options that hasn&apos;t been replied to
241
+ <Citation number={5} source={sourceItems[3]} />
242
+ </>,
243
+ ]
244
+ : signalData.evidence.map((ev, i) => (
245
+ <span key={i}>{ev}</span>
246
+ ))
247
+
248
+ return (
249
+ <div className="mb-8">
250
+ <h3 className="text-xs font-bold text-muted-foreground uppercase tracking-wider mb-3">Signal brief</h3>
251
+ <p className="text-sm text-muted-foreground leading-relaxed mb-2">
252
+ We detected signals that suggest a potential opportunity with {item.company}.
253
+ </p>
254
+ <p className="text-sm text-foreground/90 leading-relaxed mb-4">
255
+ {signalData.whyNow}
256
+ </p>
257
+
258
+ <div className="mb-5 rounded-md border border-border bg-muted/20 p-3">
259
+ <div className="flex items-center justify-between mb-1.5">
260
+ <span className="text-[10px] font-bold text-muted-foreground uppercase tracking-wider">Signal Score</span>
261
+ <div className="flex items-center gap-2">
262
+ <span className="text-sm font-bold text-foreground">{signalData.score}/100</span>
263
+ <span className={`text-[10px] font-bold uppercase ${scoreColor}`}>{scoreLabel}</span>
264
+ </div>
265
+ </div>
266
+ <div className="h-1.5 bg-muted rounded-full overflow-hidden mb-2">
267
+ <div
268
+ className={`h-full rounded-full transition-all duration-500 ${barColor}`}
269
+ style={{ width: `${signalData.score}%` }}
270
+ />
271
+ </div>
272
+ <button
273
+ type="button"
274
+ onClick={() => setEvidenceExpanded((prev) => !prev)}
275
+ className="flex items-center gap-1 text-[11px] font-medium text-muted-foreground hover:text-foreground transition-colors"
276
+ >
277
+ <ChevronDown className={`h-3 w-3 transition-transform duration-200 ${evidenceExpanded ? "rotate-180" : ""}`} />
278
+ View more
279
+ </button>
280
+
281
+ {evidenceExpanded && (
282
+ <div className="mt-3 space-y-3">
283
+ <ul className="space-y-2">
284
+ {evidenceWithCitations.map((ev, index) => (
285
+ <li key={index} className="flex items-start gap-2 text-sm">
286
+ <div className="w-1.5 h-1.5 bg-primary rounded-full mt-2 flex-shrink-0" />
287
+ <span className="text-muted-foreground leading-relaxed">{ev}</span>
288
+ </li>
289
+ ))}
290
+ </ul>
291
+ <ScoreBreakdown
292
+ factors={signalData.factors}
293
+ onFactorFeedback={signalData.onFactorFeedback ?? ((key, type, detail) =>
294
+ console.log("Signal factor feedback:", { company: item.company, factor: key, type, detail })
295
+ )}
296
+ />
297
+ <SignalApproval.Actions />
298
+ </div>
299
+ )}
300
+ </div>
301
+
302
+ {!evidenceExpanded && <SignalApproval.Actions />}
303
+ </div>
304
+ )
305
+ })()}
306
+
307
+ {/* Activity Timeline */}
308
+ {sections.timeline && timelineEvents.length > 0 && (
309
+ <div className="mb-8">
310
+ <button
311
+ type="button"
312
+ onClick={() => setShowTimeline((prev) => !prev)}
313
+ className="group/timeline flex w-full items-center justify-between gap-2 py-2 rounded-md transition-colors hover:bg-muted/40 -mx-2 px-2 cursor-pointer"
314
+ >
315
+ <div className="flex items-center gap-2">
316
+ <h3 className="text-xs font-bold text-muted-foreground uppercase tracking-wider group-hover/timeline:text-foreground transition-colors">Activity timeline</h3>
317
+ {!showTimeline && (
318
+ <span className="text-[11px] text-muted-foreground/60">&middot; Last activity 1d ago</span>
319
+ )}
320
+ </div>
321
+ <div className="flex items-center gap-1.5">
322
+ <span className="text-[11px] font-medium text-muted-foreground">{timelineEvents.length} events</span>
323
+ <ChevronDown className={`h-3.5 w-3.5 text-muted-foreground transition-transform duration-200 ${showTimeline ? "rotate-180" : ""}`} />
324
+ </div>
325
+ </button>
326
+ {showTimeline && (
327
+ <div className="mt-3">
328
+ <TimelineActivity events={timelineEvents} />
329
+ </div>
330
+ )}
331
+ </div>
332
+ )}
333
+ </div>
334
+
335
+ {/* Suggested Actions */}
336
+ {sections.suggestedActions && (
337
+ <SignalApproval.Gate>
338
+ <SuggestedActions
339
+ actions={suggestedActions}
340
+ accountContacts={accountContacts}
341
+ signature={emailSignature}
342
+ iconMap={iconMap}
343
+ onDismiss={(id) => console.log("Dismiss action:", id)}
344
+ onSend={(id) => console.log("Send action:", id)}
345
+ onSaveDraft={(id) => console.log("Save draft:", id)}
346
+ onDuplicate={handleDuplicate}
347
+ onOpenAccountDetails={onOpenEntityPanel}
348
+ onOpenRecentActivity={onOpenRecentActivity}
349
+ onMarkComplete={(id) => console.log("Mark complete:", id)}
350
+ onDispatchAgent={(id) => console.log("Dispatch agent:", id)}
351
+ />
352
+ </SignalApproval.Gate>
353
+ )}
354
+ </div>
355
+ </SignalApproval.Root>
356
+ )
357
+ }
358
+
359
+ // ---------------------------------------------------------------------------
360
+ // Main Component
361
+ // ---------------------------------------------------------------------------
362
+
363
+ export function PrototypeInboxView({
364
+ items,
365
+ filterCategories,
366
+ detailSections,
367
+ accountContacts = [],
368
+ buildAccountContacts,
369
+ emailSignature = "",
370
+ buildSuggestedActions: buildSuggestedActionsProp,
371
+ buildSourceItems: buildSourceItemsProp,
372
+ getSignalScore: getSignalScoreProp,
373
+ getTimelineEvents,
374
+ iconMap = {},
375
+ hideToolbarActions,
376
+ hideHoverActions,
377
+ onSuggestedActionFeedback,
378
+ headerActions,
379
+ onOpenEntityPanel,
380
+ onOpenRecentActivity,
381
+ onItemSelect,
382
+ defaultViewMode,
383
+ buildEntityChips,
384
+ quickFilterTabs,
385
+ hideAccountsButton,
386
+ accountDetailsLabel,
387
+ signalLabels,
388
+ }: PrototypeInboxViewProps) {
389
+ const [inboxViewMode, setInboxViewMode] = React.useState<"inbox" | "list" | "detail">(
390
+ defaultViewMode === "list" ? "list" : defaultViewMode === "split" ? "inbox" : "inbox"
391
+ )
392
+ const [previousViewMode, setPreviousViewMode] = React.useState<"inbox" | "list">("inbox")
393
+ const [selectedTask, setSelectedTask] = React.useState(items[0])
394
+ const [inboxAssignee, setInboxAssignee] = React.useState<AssigneeFilter>("me")
395
+ const [inboxFilters, setInboxFilters] = React.useState<Record<string, string>>({})
396
+ const [activeQuickFilter, setActiveQuickFilter] = React.useState<string>("all")
397
+ const [splitViewSearch, setSplitViewSearch] = React.useState("")
398
+
399
+ const sections = React.useMemo(
400
+ () => ({ ...DEFAULT_DETAIL_SECTIONS, ...detailSections }),
401
+ [detailSections],
402
+ )
403
+
404
+ const resolvedFilterCategories: InboxFilterCategory[] = React.useMemo(
405
+ () =>
406
+ filterCategories ?? [
407
+ {
408
+ id: "category",
409
+ label: "Category",
410
+ icon: <Tag className="h-3.5 w-3.5 text-muted-foreground" />,
411
+ options: [...new Set(items.map((i) => i.tag1))],
412
+ },
413
+ {
414
+ id: "account",
415
+ label: "Account",
416
+ icon: <Building className="h-3.5 w-3.5 text-muted-foreground" />,
417
+ options: [...new Set(items.map((i) => i.company))],
418
+ },
419
+ ],
420
+ [filterCategories, items],
421
+ )
422
+
423
+ const buildSuggestedActions = React.useMemo(
424
+ () => buildSuggestedActionsProp ?? (() => []),
425
+ [buildSuggestedActionsProp],
426
+ )
427
+
428
+ const buildSourceItems = React.useMemo(
429
+ () => buildSourceItemsProp ?? (() => []),
430
+ [buildSourceItemsProp],
431
+ )
432
+
433
+ const getSignalScore = React.useMemo(
434
+ () => getSignalScoreProp ?? (() => DEFAULT_SIGNAL_SCORE),
435
+ [getSignalScoreProp],
436
+ )
437
+
438
+ // Build a map from filter category id → QueueItem field for targeted filtering.
439
+ // Known category ids are mapped explicitly; unknown categories fall back to a
440
+ // broad search across display fields so consumer-defined filters still work.
441
+ const filterFieldMap = React.useMemo<
442
+ Record<string, (item: QueueItem, value: string) => boolean>
443
+ >(() => {
444
+ const map: Record<string, (item: QueueItem, value: string) => boolean> = {}
445
+ for (const cat of resolvedFilterCategories) {
446
+ switch (cat.id) {
447
+ case "category":
448
+ case "signalType":
449
+ map[cat.id] = (item, v) => item.tag1.toLowerCase() === v.toLowerCase()
450
+ break
451
+ case "account":
452
+ map[cat.id] = (item, v) => item.company.toLowerCase() === v.toLowerCase()
453
+ break
454
+ default:
455
+ // Fallback: check all display fields
456
+ map[cat.id] = (item, v) => {
457
+ const lv = v.toLowerCase()
458
+ return (
459
+ item.tag1.toLowerCase() === lv ||
460
+ item.company.toLowerCase() === lv ||
461
+ item.title.toLowerCase().includes(lv) ||
462
+ item.details.toLowerCase().includes(lv)
463
+ )
464
+ }
465
+ }
466
+ }
467
+ return map
468
+ }, [resolvedFilterCategories])
469
+
470
+ // Filter items for list view based on toolbar filters
471
+ const filteredItems = React.useMemo(() => {
472
+ const activeFilters = Object.entries(inboxFilters).filter(
473
+ ([, value]) => value && value !== "all"
474
+ )
475
+ if (activeFilters.length === 0) return items
476
+ return items.filter((item) =>
477
+ activeFilters.every(([key, value]) => {
478
+ const matcher = filterFieldMap[key]
479
+ return matcher ? matcher(item, value) : true
480
+ })
481
+ )
482
+ }, [items, inboxFilters, filterFieldMap])
483
+
484
+ // Resolve quick filter tabs once — used by both the split view filter and
485
+ // the tab bar render. Each tab's `matchValue` (falling back to `label`) is
486
+ // compared against `item.tag1` so consumer labels can differ from data values.
487
+ type QuickFilterTab = { id: string; label: string; matchValue?: string; count?: number }
488
+ const resolvedQuickFilterTabs = React.useMemo<QuickFilterTab[]>(() => {
489
+ if (quickFilterTabs) return quickFilterTabs
490
+ // Derive default tabs from the actual item tag1 values
491
+ const uniqueTags = [...new Set(items.map((i) => i.tag1))]
492
+ return uniqueTags.map((tag) => ({
493
+ id: tag.toLowerCase().replace(/\s+/g, "-"),
494
+ label: tag,
495
+ }))
496
+ }, [quickFilterTabs, items])
497
+
498
+ // Compute per-tab counts once so they can be displayed in the tab bar
499
+ const quickFilterTabCounts = React.useMemo(() => {
500
+ const counts: Record<string, number> = {}
501
+ for (const tab of resolvedQuickFilterTabs) {
502
+ const match = (tab.matchValue ?? tab.label).toLowerCase()
503
+ counts[tab.id] = items.filter((i) => i.tag1.toLowerCase() === match).length
504
+ }
505
+ return counts
506
+ }, [resolvedQuickFilterTabs, items])
507
+
508
+ // Filter items for split view based on quick filter tabs and search
509
+ const splitViewItems = React.useMemo(() => {
510
+ let filtered = items
511
+ // Apply quick filter tab
512
+ if (activeQuickFilter !== "all") {
513
+ const activeTab = resolvedQuickFilterTabs.find((t) => t.id === activeQuickFilter)
514
+ if (activeTab) {
515
+ const match = (activeTab.matchValue ?? activeTab.label).toLowerCase()
516
+ filtered = filtered.filter(
517
+ (item) => item.tag1.toLowerCase() === match
518
+ )
519
+ }
520
+ }
521
+ // Apply search input
522
+ if (splitViewSearch.trim()) {
523
+ const q = splitViewSearch.trim().toLowerCase()
524
+ filtered = filtered.filter(
525
+ (item) =>
526
+ item.tag1.toLowerCase().includes(q) ||
527
+ item.company.toLowerCase().includes(q) ||
528
+ item.title.toLowerCase().includes(q)
529
+ )
530
+ }
531
+ return filtered
532
+ }, [items, activeQuickFilter, resolvedQuickFilterTabs, splitViewSearch])
533
+
534
+ // Grouped items for list view
535
+ const inboxGroups = React.useMemo<GroupedListGroup<QueueItem>[]>(() => {
536
+ const urgent = filteredItems.filter((i) => i.statusColor === "red")
537
+ const active = filteredItems.filter((i) => i.statusColor !== "red")
538
+ return [
539
+ { key: "urgent", label: "Urgent", items: urgent },
540
+ { key: "active", label: "Active", items: active },
541
+ ].filter((g) => g.items.length > 0)
542
+ }, [filteredItems])
543
+
544
+ const renderInboxRow = React.useCallback(
545
+ (item: QueueItem) => (
546
+ <>
547
+ <span className={`h-2 w-2 shrink-0 rounded-full ${item.statusColor === "red" ? "bg-[#f43f5e]" : "bg-[#3b82f6]"}`} />
548
+ <span className="w-[80px] shrink-0 font-mono text-xs text-muted-foreground/80">{item.id}</span>
549
+ <span className="shrink-0 rounded-md border border-border bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground whitespace-nowrap">{item.tag1}</span>
550
+ <span className="min-w-0 flex-1 truncate text-sm font-semibold text-foreground">{item.title}</span>
551
+ <span className="w-[120px] shrink-0 truncate text-xs font-medium text-foreground">{item.company}</span>
552
+ <span className="w-[80px] shrink-0 text-right text-xs text-muted-foreground">{item.time}</span>
553
+ </>
554
+ ),
555
+ [],
556
+ )
557
+
558
+ const handleInboxItemSelect = React.useCallback(
559
+ (item: QueueItem) => {
560
+ setSelectedTask(item)
561
+ if (onItemSelect) {
562
+ onItemSelect(item)
563
+ } else if (inboxViewMode === "list") {
564
+ setPreviousViewMode("list")
565
+ setInboxViewMode("detail")
566
+ }
567
+ },
568
+ [inboxViewMode, onItemSelect],
569
+ )
570
+
571
+ const handleBackFromDetail = React.useCallback(() => {
572
+ setInboxViewMode(previousViewMode)
573
+ }, [previousViewMode])
574
+
575
+ const handleViewModeChange = React.useCallback((id: string) => {
576
+ const mode = id as "inbox" | "list" | "detail"
577
+ if (mode !== "detail") {
578
+ setPreviousViewMode(mode)
579
+ }
580
+ setInboxViewMode(mode)
581
+ }, [])
582
+
583
+ React.useEffect(() => {
584
+ const mql = window.matchMedia("(max-width: 768px)")
585
+ function handleChange(e: MediaQueryListEvent | MediaQueryList) {
586
+ if (e.matches && inboxViewMode === "inbox") {
587
+ setPreviousViewMode("inbox")
588
+ setInboxViewMode("detail")
589
+ }
590
+ }
591
+ handleChange(mql)
592
+ mql.addEventListener("change", handleChange)
593
+ return () => mql.removeEventListener("change", handleChange)
594
+ }, [inboxViewMode])
595
+
596
+ const detailViewProps: DetailViewProps = {
597
+ item: selectedTask,
598
+ sections,
599
+ getSignalScore,
600
+ buildSuggestedActions,
601
+ buildSourceItems,
602
+ getTimelineEvents,
603
+ accountContacts: buildAccountContacts?.(selectedTask) ?? accountContacts,
604
+ emailSignature,
605
+ iconMap,
606
+ onOpenEntityPanel,
607
+ onOpenRecentActivity,
608
+ onSuggestedActionFeedback,
609
+ signalLabels,
610
+ }
611
+
612
+ return (
613
+ <div className="flex h-full w-full flex-col">
614
+ {/* Toolbar */}
615
+ <div className="flex items-center justify-between border-b border-border bg-background px-4 py-3 shrink-0">
616
+ <div className="flex items-center gap-3">
617
+ {inboxViewMode === "detail" ? (
618
+ <button
619
+ type="button"
620
+ onClick={handleBackFromDetail}
621
+ className="flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
622
+ >
623
+ <ArrowLeft className="h-4 w-4" />
624
+ Back
625
+ </button>
626
+ ) : null}
627
+ <h2 className="text-lg font-semibold text-foreground">Inbox</h2>
628
+ <Badge variant="secondary" className="bg-muted text-muted-foreground hover:bg-muted font-medium text-[11px] px-2 py-0.5 rounded-md">
629
+ {items.length}
630
+ </Badge>
631
+ </div>
632
+ <div className="flex items-center gap-3">
633
+ <ViewModeToggle
634
+ modes={[
635
+ { id: "inbox", icon: <Columns2 className="h-3.5 w-3.5" />, label: "Split View" },
636
+ { id: "list", icon: <LayoutList className="h-3.5 w-3.5" />, label: "List View" },
637
+ { id: "detail", icon: <Square className="h-3.5 w-3.5" />, label: "Detail View" },
638
+ ]}
639
+ activeMode={inboxViewMode}
640
+ onModeChange={handleViewModeChange}
641
+ />
642
+ {headerActions}
643
+ </div>
644
+ </div>
645
+
646
+ {/* View modes */}
647
+ {inboxViewMode === "detail" ? (
648
+ <div className="flex h-full flex-1 flex-col overflow-hidden bg-background">
649
+ <div className="flex-1 overflow-y-auto">
650
+ <DetailView {...detailViewProps} />
651
+ </div>
652
+ </div>
653
+ ) : inboxViewMode === "list" ? (
654
+ <div className="flex-1 overflow-y-auto bg-background">
655
+ <InboxToolbar
656
+ assignee={inboxAssignee}
657
+ onAssigneeChange={setInboxAssignee}
658
+ filterCategories={resolvedFilterCategories}
659
+ selectedFilters={inboxFilters}
660
+ onFilterChange={(catId, val) =>
661
+ setInboxFilters((prev) => ({ ...prev, [catId]: val }))
662
+ }
663
+ onClearFilters={() => setInboxFilters({})}
664
+ />
665
+ <GroupedListView<QueueItem>
666
+ groups={inboxGroups}
667
+ renderRow={renderInboxRow}
668
+ getItemKey={(item) => item.id}
669
+ selectedKey={selectedTask.id}
670
+ onItemClick={handleInboxItemSelect}
671
+ emptyMessage="No inbox items"
672
+ />
673
+ </div>
674
+ ) : (
675
+ /* Split view */
676
+ <div className="flex h-full min-h-0 w-full flex-1">
677
+ <div className="flex h-full min-w-[380px] w-[380px] flex-col border-r border-border bg-background shadow-sm z-10">
678
+ <div className="flex flex-col gap-4 border-b border-border p-4 shrink-0">
679
+ {!hideToolbarActions && (
680
+ <div className="flex items-center justify-between">
681
+ <div className="flex items-center gap-1">
682
+ <Button variant="outline" size="icon" className="h-8 w-8 text-muted-foreground"><Eye className="w-4 h-4" /></Button>
683
+ <Button variant="outline" size="icon" className="h-8 w-8 text-muted-foreground"><FileText className="w-4 h-4" /></Button>
684
+ <Button variant="outline" size="icon" className="h-8 w-8 text-muted-foreground"><Clock className="w-4 h-4" /></Button>
685
+ <Button variant="outline" size="icon" className="h-8 w-8 text-muted-foreground"><CheckSquare className="w-4 h-4" /></Button>
686
+ </div>
687
+ <Button size="sm" className="h-8 px-4 bg-foreground text-background hover:bg-foreground/90 text-xs font-semibold gap-1.5 rounded-md">
688
+ <Plus className="w-4 h-4" /> Add Task
689
+ </Button>
690
+ </div>
691
+ )}
692
+ <div className="flex items-center gap-2">
693
+ <div className="relative flex-1">
694
+ <Filter className="absolute left-2.5 top-1.5 w-4 h-4 text-muted-foreground" />
695
+ <Input
696
+ className="h-8 pl-8 text-xs bg-background border-border rounded-md shadow-none"
697
+ placeholder="Filter by category..."
698
+ value={splitViewSearch}
699
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSplitViewSearch(e.target.value)}
700
+ />
701
+ </div>
702
+ {!hideAccountsButton && (
703
+ <Button variant="outline" size="sm" className="h-8 text-xs font-medium rounded-md shadow-none">
704
+ <Building className="w-3.5 h-3.5 mr-1.5" /> {accountDetailsLabel ?? "Accounts"}
705
+ </Button>
706
+ )}
707
+ </div>
708
+ <div className="flex items-center gap-1.5 overflow-x-auto pb-1 mt-1 scrollbar-hide">
709
+ <Button
710
+ size="sm"
711
+ variant={activeQuickFilter === "all" ? "default" : "outline"}
712
+ className={`h-7 rounded-full px-3.5 text-[11px] font-semibold shadow-none ${
713
+ activeQuickFilter === "all"
714
+ ? "bg-foreground text-background hover:bg-foreground/90"
715
+ : "bg-transparent border-border text-muted-foreground hover:text-foreground"
716
+ }`}
717
+ onClick={() => setActiveQuickFilter("all")}
718
+ >
719
+ All
720
+ </Button>
721
+ {resolvedQuickFilterTabs.map((tab) => {
722
+ const count = tab.count ?? quickFilterTabCounts[tab.id]
723
+ return (
724
+ <Button
725
+ key={tab.id}
726
+ size="sm"
727
+ variant={activeQuickFilter === tab.id ? "default" : "outline"}
728
+ className={`h-7 rounded-full px-3.5 text-[11px] font-medium shadow-none ${
729
+ activeQuickFilter === tab.id
730
+ ? "bg-foreground text-background hover:bg-foreground/90"
731
+ : "bg-transparent border-border text-muted-foreground hover:text-foreground"
732
+ }`}
733
+ onClick={() => setActiveQuickFilter(tab.id)}
734
+ >
735
+ {tab.label}{count != null && count > 0 ? ` (${count})` : ""}
736
+ </Button>
737
+ )
738
+ })}
739
+ </div>
740
+ </div>
741
+
742
+ <div className="flex-1 overflow-y-auto">
743
+ {splitViewItems.map((item) => (
744
+ <div
745
+ key={item.id}
746
+ onClick={() => { setSelectedTask(item); onItemSelect?.(item) }}
747
+ className={`cursor-pointer border-b border-border p-4 transition-colors group relative border-l-2 ${
748
+ selectedTask.id === item.id
749
+ ? "bg-muted/30 border-l-brand-purple"
750
+ : "bg-transparent border-l-transparent hover:bg-muted/10"
751
+ }`}
752
+ >
753
+ <div className="mb-1.5 flex items-center gap-2">
754
+ <span className="min-w-0 truncate text-[13px] font-semibold text-foreground leading-tight">{item.title}</span>
755
+ {selectedTask.id !== item.id && item.tag1 && (
756
+ <span className="shrink-0 rounded-md border border-border bg-muted/60 px-2 py-0.5 text-[10px] font-medium text-muted-foreground">
757
+ {item.tag1}
758
+ </span>
759
+ )}
760
+ <span className="ml-auto shrink-0 text-[10px] font-medium text-muted-foreground/80">{item.time}</span>
761
+ </div>
762
+ <div className="flex items-start gap-2 mt-2">
763
+ <span className={`w-1.5 h-1.5 rounded-full shrink-0 mt-1.5 ${item.statusColor === "red" ? "bg-[#f43f5e]" : "bg-[#3b82f6]"}`} />
764
+ <span className="text-xs text-muted-foreground leading-tight">{item.details}</span>
765
+ </div>
766
+ {buildEntityChips && (() => {
767
+ const chips = buildEntityChips(item)
768
+ if (!chips.length) return null
769
+ return (
770
+ <div className="flex items-center gap-1.5 mt-2 flex-wrap">
771
+ {chips.map((chip) => (
772
+ <button
773
+ key={chip.id}
774
+ type="button"
775
+ onClick={(e) => { e.stopPropagation(); chip.onClick?.() }}
776
+ className="inline-flex items-center gap-1 rounded-md border border-border/60 bg-muted/30 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground"
777
+ >
778
+ <span className="flex h-3.5 w-3.5 items-center justify-center rounded bg-muted-foreground/10 text-[8px] font-semibold">{chip.avatarLetter}</span>
779
+ {chip.label}
780
+ </button>
781
+ ))}
782
+ </div>
783
+ )
784
+ })()}
785
+ {!hideHoverActions && (
786
+ <div className={`absolute right-4 bottom-4 flex items-center gap-1.5 bg-background shadow-sm rounded-md px-1 py-0.5 border border-border ${
787
+ selectedTask.id === item.id ? "opacity-100" : "opacity-0 group-hover:opacity-100 transition-opacity"
788
+ }`}>
789
+ <Button variant="ghost" size="icon" className="h-6 w-6 rounded text-muted-foreground hover:text-foreground"><CheckSquare className="w-3.5 h-3.5" /></Button>
790
+ <Button variant="ghost" size="icon" className="h-6 w-6 rounded text-muted-foreground hover:text-foreground"><Clock className="w-3.5 h-3.5" /></Button>
791
+ </div>
792
+ )}
793
+ </div>
794
+ ))}
795
+ <div className="p-4">
796
+ <Button variant="outline" size="sm" className="h-8 text-xs font-semibold rounded-md shadow-none">See more</Button>
797
+ </div>
798
+ </div>
799
+ </div>
800
+
801
+ <div className="flex h-full flex-1 flex-col overflow-hidden bg-background">
802
+ <div className="flex-1 overflow-y-auto">
803
+ <DetailView {...detailViewProps} />
804
+ </div>
805
+ </div>
806
+ </div>
807
+ )}
808
+ </div>
809
+ )
810
+ }