@handled-ai/design-system 0.17.1 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/charts/empty-chart-state.d.ts +11 -0
- package/dist/charts/empty-chart-state.js +70 -0
- package/dist/charts/empty-chart-state.js.map +1 -0
- package/dist/charts/index.d.ts +1 -0
- package/dist/charts/index.js +1 -0
- package/dist/charts/index.js.map +1 -1
- package/dist/charts/pipeline-overview.d.ts +2 -1
- package/dist/charts/pipeline-overview.js +29 -1
- package/dist/charts/pipeline-overview.js.map +1 -1
- package/dist/components/actor-byline.d.ts +3 -0
- package/dist/components/actor-byline.js +5 -0
- package/dist/components/actor-byline.js.map +1 -0
- package/dist/components/days-open-cell.d.ts +16 -0
- package/dist/components/days-open-cell.js +73 -0
- package/dist/components/days-open-cell.js.map +1 -0
- package/dist/components/detail-drawer.d.ts +16 -0
- package/dist/components/detail-drawer.js +45 -0
- package/dist/components/detail-drawer.js.map +1 -0
- package/dist/components/feedback-primitives.d.ts +66 -0
- package/dist/components/feedback-primitives.js +295 -0
- package/dist/components/feedback-primitives.js.map +1 -0
- package/dist/components/insights-filter-bar.d.ts +2 -1
- package/dist/components/insights-filter-bar.js +13 -5
- package/dist/components/insights-filter-bar.js.map +1 -1
- package/dist/components/linked-entity-cell.d.ts +14 -0
- package/dist/components/linked-entity-cell.js +96 -0
- package/dist/components/linked-entity-cell.js.map +1 -0
- package/dist/components/metric-card.d.ts +14 -1
- package/dist/components/metric-card.js +86 -0
- package/dist/components/metric-card.js.map +1 -1
- package/dist/components/performance-metrics-table.d.ts +2 -1
- package/dist/components/performance-metrics-table.js +78 -46
- package/dist/components/performance-metrics-table.js.map +1 -1
- package/dist/components/pill.d.ts +26 -0
- package/dist/components/pill.js +77 -0
- package/dist/components/pill.js.map +1 -0
- package/dist/components/quick-segment.d.ts +13 -0
- package/dist/components/quick-segment.js +96 -0
- package/dist/components/quick-segment.js.map +1 -0
- package/dist/components/score-why-chips.d.ts +8 -17
- package/dist/components/score-why-chips.js +266 -180
- package/dist/components/score-why-chips.js.map +1 -1
- package/dist/components/signal-priority-popover.d.ts +17 -0
- package/dist/components/signal-priority-popover.js +247 -0
- package/dist/components/signal-priority-popover.js.map +1 -0
- package/dist/components/user-display.d.ts +22 -0
- package/dist/components/user-display.js +138 -0
- package/dist/components/user-display.js.map +1 -0
- package/dist/components/user-pill.d.ts +3 -0
- package/dist/components/user-pill.js +5 -0
- package/dist/components/user-pill.js.map +1 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/user-display.d.ts +31 -0
- package/dist/lib/user-display.js +57 -0
- package/dist/lib/user-display.js.map +1 -0
- package/dist/prototype/index.d.ts +2 -1
- package/dist/prototype/prototype-accounts-view.d.ts +2 -1
- package/dist/prototype/prototype-admin-view.d.ts +2 -1
- package/dist/prototype/prototype-config.d.ts +15 -332
- package/dist/prototype/prototype-inbox-view.d.ts +2 -1
- package/dist/prototype/prototype-inbox-view.js +11 -12
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/dist/prototype/prototype-insights-view.d.ts +2 -1
- package/dist/prototype/prototype-shell.d.ts +2 -1
- package/dist/signal-priority-popover-DQ_VuHac.d.ts +390 -0
- package/package.json +1 -1
- package/src/charts/__tests__/insights-charts.test.tsx +62 -0
- package/src/charts/empty-chart-state.tsx +44 -0
- package/src/charts/index.ts +1 -0
- package/src/charts/pipeline-overview.tsx +38 -1
- package/src/components/__tests__/contextual-quick-action-launcher.test.tsx +99 -188
- package/src/components/__tests__/feedback-primitives.test.tsx +509 -0
- package/src/components/__tests__/insights-primitives.test.tsx +117 -0
- package/src/components/__tests__/performance-metrics-table.test.tsx +54 -0
- package/src/components/__tests__/score-why-chips.test.tsx +540 -0
- package/src/components/__tests__/signal-priority-popover.test.tsx +312 -0
- package/src/components/__tests__/user-display.test.tsx +75 -0
- package/src/components/actor-byline.tsx +1 -0
- package/src/components/days-open-cell.tsx +50 -0
- package/src/components/detail-drawer.tsx +60 -0
- package/src/components/feedback-primitives.tsx +424 -0
- package/src/components/insights-filter-bar.tsx +13 -4
- package/src/components/linked-entity-cell.tsx +74 -0
- package/src/components/metric-card.tsx +82 -0
- package/src/components/performance-metrics-table.tsx +99 -63
- package/src/components/pill.tsx +67 -0
- package/src/components/quick-segment.tsx +68 -0
- package/src/components/score-why-chips.tsx +413 -203
- package/src/components/signal-priority-popover.tsx +359 -0
- package/src/components/user-display.tsx +96 -0
- package/src/components/user-pill.tsx +1 -0
- package/src/index.ts +11 -0
- package/src/lib/__tests__/user-display.test.ts +85 -0
- package/src/lib/user-display.ts +88 -0
- package/src/prototype/__tests__/detail-view-score-why.test.tsx +33 -29
- package/src/prototype/__tests__/detail-view-title-slots.test.tsx +65 -0
- package/src/prototype/prototype-config.ts +28 -4
- package/src/prototype/prototype-inbox-view.tsx +8 -10
- package/src/prototype/__tests__/detail-view-title-subtext.test.tsx +0 -72
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { FeedbackChipTree, FeedbackSubmitData } from './components/feedback-primitives.js';
|
|
3
|
+
import { SidebarNavSection } from './components/quick-action-sidebar-nav.js';
|
|
4
|
+
import { ScoreFactor } from './components/score-breakdown.js';
|
|
5
|
+
import { SuggestedContact, SuggestedAction } from './components/suggested-actions.js';
|
|
6
|
+
import { SourceDef } from './components/detail-view.js';
|
|
7
|
+
import { InboxFilterCategory } from './components/inbox-toolbar.js';
|
|
8
|
+
import { DataTableFilterCategory } from './components/data-table-filter.js';
|
|
9
|
+
import { DataRow } from './components/data-table.js';
|
|
10
|
+
import { MetricCardProps } from './components/metric-card.js';
|
|
11
|
+
import { PipelineStage, PipelineStageMetrics, PipelineStageTiming } from './charts/pipeline-overview.js';
|
|
12
|
+
import { TimelineEvent } from './components/timeline-activity.js';
|
|
13
|
+
import { ApprovalState } from './components/signal-feedback-inline.js';
|
|
14
|
+
import { LucideIcon } from 'lucide-react';
|
|
15
|
+
|
|
16
|
+
interface PrototypeBrandConfig {
|
|
17
|
+
name: string;
|
|
18
|
+
logo?: string;
|
|
19
|
+
assistantName?: string;
|
|
20
|
+
}
|
|
21
|
+
interface QueueItem {
|
|
22
|
+
id: string;
|
|
23
|
+
title: string;
|
|
24
|
+
details: string;
|
|
25
|
+
statusColor: string;
|
|
26
|
+
time: string;
|
|
27
|
+
company: string;
|
|
28
|
+
tag1: string;
|
|
29
|
+
}
|
|
30
|
+
type SignalScoreUrgencyLabel = "Low" | "Medium" | "High" | "Urgent";
|
|
31
|
+
interface SignalScoreExplanationSignal {
|
|
32
|
+
id?: string;
|
|
33
|
+
label: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
source?: string;
|
|
36
|
+
time?: string;
|
|
37
|
+
metric?: string;
|
|
38
|
+
/** Signal type name (e.g., "treasury_liquidation"). Used for combined signal component identification. */
|
|
39
|
+
signalTypeName?: string;
|
|
40
|
+
/** Primary display value (e.g., "-$1,724,310.11"). */
|
|
41
|
+
primaryValue?: string;
|
|
42
|
+
/** Qualifier text (e.g., "100% of balance"). */
|
|
43
|
+
qualifier?: string;
|
|
44
|
+
/** Counterparty / destination (e.g., "-> JPMorgan Chase --6042"). */
|
|
45
|
+
counterparty?: string;
|
|
46
|
+
/** Component breakdown for combined signals. */
|
|
47
|
+
components?: Array<{
|
|
48
|
+
type: string;
|
|
49
|
+
count: number;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
52
|
+
interface SignalScoreExplanationBucket {
|
|
53
|
+
key: string;
|
|
54
|
+
label: string;
|
|
55
|
+
kind: "signal" | "factor" | "merged";
|
|
56
|
+
score?: number;
|
|
57
|
+
classification?: string;
|
|
58
|
+
rationale?: string;
|
|
59
|
+
evidence?: string[];
|
|
60
|
+
signals?: SignalScoreExplanationSignal[];
|
|
61
|
+
primaryMetricLabel?: string;
|
|
62
|
+
primaryMetricValue?: string;
|
|
63
|
+
signalCount?: number;
|
|
64
|
+
signalIds?: string[];
|
|
65
|
+
primarySignalId?: string;
|
|
66
|
+
factorKeys?: string[];
|
|
67
|
+
/** Lucide icon name for the bucket type (e.g., "trending-down"). */
|
|
68
|
+
icon?: string;
|
|
69
|
+
/** Tonal styling hint for the bucket. */
|
|
70
|
+
tone?: "alert" | "warn" | "info";
|
|
71
|
+
}
|
|
72
|
+
interface SignalScoreData {
|
|
73
|
+
score: number;
|
|
74
|
+
factors: ScoreFactor[];
|
|
75
|
+
whyNow: string;
|
|
76
|
+
evidence: string[];
|
|
77
|
+
confidence: number;
|
|
78
|
+
urgencyLabel?: SignalScoreUrgencyLabel;
|
|
79
|
+
urgencyExplanation?: string;
|
|
80
|
+
explanationBuckets?: SignalScoreExplanationBucket[];
|
|
81
|
+
onFactorFeedback?: (factorKey: string, type: "up" | "down" | null, detail?: string) => void;
|
|
82
|
+
/** @deprecated The compact score UX no longer renders score-level thumbs by default. */
|
|
83
|
+
onScoreFeedback?: (type: "up" | "down", pills: string[], detail: string) => void;
|
|
84
|
+
onApproveFeedback?: (reasons: string[], detail: string) => void;
|
|
85
|
+
onDismissFeedback?: (reasons: string[], detail: string, subReason?: string) => void;
|
|
86
|
+
/** @deprecated The compact score UX no longer renders score-level thumbs by default. */
|
|
87
|
+
initialScoreFeedback?: {
|
|
88
|
+
type: "up" | "down";
|
|
89
|
+
pills: string[];
|
|
90
|
+
detail: string;
|
|
91
|
+
} | null;
|
|
92
|
+
initialFactorFeedback?: Record<string, {
|
|
93
|
+
type: "up" | "down";
|
|
94
|
+
detail: string;
|
|
95
|
+
}>;
|
|
96
|
+
/** Priority factors for the popover breakdown. */
|
|
97
|
+
priorityFactors?: PriorityFactor[];
|
|
98
|
+
/** Negative feedback chip tree for the priority popover. */
|
|
99
|
+
priorityFeedbackChips?: FeedbackChipTree[];
|
|
100
|
+
/** Callback when user submits priority-level feedback. */
|
|
101
|
+
onPriorityFeedback?: (data: FeedbackSubmitData) => void;
|
|
102
|
+
/** Callback when user submits bucket-level feedback. */
|
|
103
|
+
onBucketFeedback?: (bucketKey: string, data: FeedbackSubmitData) => void;
|
|
104
|
+
/** AI-generated signal brief text. When present, rendered in a dedicated section. */
|
|
105
|
+
signalBrief?: string;
|
|
106
|
+
/** Compact label for time-remaining chip (e.g., "13 days left"). */
|
|
107
|
+
timeChipLabel?: string;
|
|
108
|
+
/** Expanded detail for time chip popover (e.g., "Day 2 of 14 · Event detected on May 1"). */
|
|
109
|
+
timeChipDetail?: string;
|
|
110
|
+
}
|
|
111
|
+
interface InboxSortOption {
|
|
112
|
+
id: string;
|
|
113
|
+
label: string;
|
|
114
|
+
}
|
|
115
|
+
/** Controls the visual prominence of the signal brief section. */
|
|
116
|
+
type BriefStyleVariant = "default" | "prominent";
|
|
117
|
+
interface InboxDetailSections {
|
|
118
|
+
signalBrief?: boolean;
|
|
119
|
+
suggestedActions?: boolean;
|
|
120
|
+
timeline?: boolean;
|
|
121
|
+
}
|
|
122
|
+
interface InboxViewConfig {
|
|
123
|
+
items: QueueItem[];
|
|
124
|
+
filterCategories?: InboxFilterCategory[];
|
|
125
|
+
detailSections?: InboxDetailSections;
|
|
126
|
+
accountContacts?: SuggestedContact[];
|
|
127
|
+
buildAccountContacts?: (item: QueueItem) => SuggestedContact[];
|
|
128
|
+
emailSignature?: string | React.ReactNode;
|
|
129
|
+
buildSuggestedActions?: (item: QueueItem) => SuggestedAction[];
|
|
130
|
+
buildSourceItems?: (item: QueueItem) => SourceDef[];
|
|
131
|
+
getSignalScore?: (company: string, item?: QueueItem) => SignalScoreData;
|
|
132
|
+
getTimelineEvents?: (item: QueueItem) => TimelineEvent[];
|
|
133
|
+
iconMap?: Record<string, string>;
|
|
134
|
+
hideToolbarActions?: boolean;
|
|
135
|
+
hideHoverActions?: boolean;
|
|
136
|
+
onSuggestedActionFeedback?: (actionId: number | string, feedback: string, actionTitle?: string) => void;
|
|
137
|
+
/** @deprecated The compact score UX no longer renders score-level thumbs by default. */
|
|
138
|
+
onScoreFeedback?: (type: "up" | "down", pills: string[], detail: string) => void;
|
|
139
|
+
onOpenSignalBucket?: (args: {
|
|
140
|
+
item: QueueItem;
|
|
141
|
+
bucketKey: string;
|
|
142
|
+
signalId: string;
|
|
143
|
+
}) => void;
|
|
144
|
+
buildEntityChips?: (item: QueueItem) => Array<{
|
|
145
|
+
id: string;
|
|
146
|
+
label: string;
|
|
147
|
+
avatarLetter: string;
|
|
148
|
+
onClick?: () => void;
|
|
149
|
+
}>;
|
|
150
|
+
quickFilterTabs?: Array<{
|
|
151
|
+
id: string;
|
|
152
|
+
label: string;
|
|
153
|
+
matchValue?: string;
|
|
154
|
+
count?: number;
|
|
155
|
+
}>;
|
|
156
|
+
hideAccountsButton?: boolean;
|
|
157
|
+
accountDetailsLabel?: string;
|
|
158
|
+
onSignalApprove?: (item: QueueItem) => void | Promise<boolean>;
|
|
159
|
+
getSignalApprovalState?: (item: QueueItem) => ApprovalState | undefined;
|
|
160
|
+
signalLabels?: {
|
|
161
|
+
approveButton?: string;
|
|
162
|
+
dismissButton?: string;
|
|
163
|
+
approvedStatus?: string;
|
|
164
|
+
dismissedStatus?: string;
|
|
165
|
+
opportunityCreated?: string;
|
|
166
|
+
confirmPrompt?: string;
|
|
167
|
+
creatingStatus?: string;
|
|
168
|
+
};
|
|
169
|
+
/** When true, the approve/create-opportunity button is hidden but the dismiss button remains. */
|
|
170
|
+
hideApproveButton?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Override the copy used in the detail panel "Signal brief" section.
|
|
173
|
+
* Useful when the prototype represents an internal ops inbox rather than
|
|
174
|
+
* an outbound sales signal — the default heading and intro line are
|
|
175
|
+
* written for the latter.
|
|
176
|
+
*/
|
|
177
|
+
signalBriefCopy?: {
|
|
178
|
+
/** Section heading (default: "Signal brief"). Pass an empty string to hide. */
|
|
179
|
+
heading?: string;
|
|
180
|
+
/**
|
|
181
|
+
* Introductory line rendered above the per-item `whyNow` text.
|
|
182
|
+
* Receives the `QueueItem` so the caller can interpolate the company
|
|
183
|
+
* name or other fields. Pass `null` (or a function returning `null`)
|
|
184
|
+
* to suppress the intro line entirely.
|
|
185
|
+
*/
|
|
186
|
+
intro?: string | ((item: QueueItem) => string | null) | null;
|
|
187
|
+
};
|
|
188
|
+
/** Controls the visual prominence of the signal brief section.
|
|
189
|
+
* - "default": current styling (muted intro, text-foreground/90 brief)
|
|
190
|
+
* - "prominent": standard foreground color, text-base size for the brief
|
|
191
|
+
*/
|
|
192
|
+
briefStyleVariant?: BriefStyleVariant;
|
|
193
|
+
/** Render extra content at the end of the detail view, below the suggested actions section. */
|
|
194
|
+
renderDetailExtra?: (item: QueueItem) => React.ReactNode;
|
|
195
|
+
/** Render content between the signal brief text and the signal score bar (e.g. "Signals on Case" chips). */
|
|
196
|
+
renderBeforeScore?: (item: QueueItem) => React.ReactNode;
|
|
197
|
+
/** Render content between the signal score section and the activity timeline (e.g. OpportunityPanel). */
|
|
198
|
+
renderAfterScore?: (item: QueueItem) => React.ReactNode;
|
|
199
|
+
/** Formatted string for "Last activity X ago" in the collapsed timeline header. If omitted, falls back to the first event's time. */
|
|
200
|
+
lastActivityTime?: string;
|
|
201
|
+
/** Render extra content inline with the detail title. */
|
|
202
|
+
renderTitleExtra?: (item: QueueItem) => React.ReactNode;
|
|
203
|
+
/** Render supporting content below the detail title. */
|
|
204
|
+
renderTitleSubtext?: (item: QueueItem) => React.ReactNode;
|
|
205
|
+
/** Sort options for the inbox. When provided, a sort dropdown is rendered in the split view toolbar. */
|
|
206
|
+
sortOptions?: InboxSortOption[];
|
|
207
|
+
/** Currently active sort option id. */
|
|
208
|
+
activeSortId?: string;
|
|
209
|
+
/** Callback when the user changes the sort option. */
|
|
210
|
+
onSortChange?: (sortId: string) => void;
|
|
211
|
+
}
|
|
212
|
+
interface InsightsCustomTab {
|
|
213
|
+
id: string;
|
|
214
|
+
label: string;
|
|
215
|
+
icon?: React.ComponentType<{
|
|
216
|
+
className?: string;
|
|
217
|
+
}>;
|
|
218
|
+
content: React.ReactNode;
|
|
219
|
+
}
|
|
220
|
+
interface InsightsViewConfig {
|
|
221
|
+
customTabs?: InsightsCustomTab[];
|
|
222
|
+
tabs?: {
|
|
223
|
+
overview?: boolean;
|
|
224
|
+
analytics?: boolean;
|
|
225
|
+
};
|
|
226
|
+
coaching?: {
|
|
227
|
+
enabled?: boolean;
|
|
228
|
+
message?: string;
|
|
229
|
+
};
|
|
230
|
+
metrics?: MetricCardProps[];
|
|
231
|
+
expandedMetrics?: MetricCardProps[];
|
|
232
|
+
dashboardCards?: {
|
|
233
|
+
topTasks?: boolean;
|
|
234
|
+
upcomingMeetings?: boolean;
|
|
235
|
+
recentlyCompleted?: boolean;
|
|
236
|
+
checkIns?: boolean;
|
|
237
|
+
};
|
|
238
|
+
analytics?: {
|
|
239
|
+
pipeline?: {
|
|
240
|
+
stages: PipelineStage[];
|
|
241
|
+
stageMetrics: Record<string, PipelineStageMetrics>;
|
|
242
|
+
stageTimings: (PipelineStageTiming | null)[];
|
|
243
|
+
filterBreakdowns?: Record<string, Record<string, Record<string, number>>>;
|
|
244
|
+
};
|
|
245
|
+
volumeChart?: {
|
|
246
|
+
data: Record<string, unknown>[];
|
|
247
|
+
dataKeys: Array<{
|
|
248
|
+
key: string;
|
|
249
|
+
color: string;
|
|
250
|
+
}>;
|
|
251
|
+
filterOptions?: Array<{
|
|
252
|
+
label: string;
|
|
253
|
+
value: string;
|
|
254
|
+
}>;
|
|
255
|
+
};
|
|
256
|
+
donutChart?: {
|
|
257
|
+
data: Array<{
|
|
258
|
+
name: string;
|
|
259
|
+
value: number;
|
|
260
|
+
color: string;
|
|
261
|
+
}>;
|
|
262
|
+
centerLabel?: number;
|
|
263
|
+
};
|
|
264
|
+
trendChart?: {
|
|
265
|
+
data: Record<string, unknown>[];
|
|
266
|
+
series: Array<{
|
|
267
|
+
dataKey: string;
|
|
268
|
+
color: string;
|
|
269
|
+
}>;
|
|
270
|
+
xAxisKey?: string;
|
|
271
|
+
height?: number;
|
|
272
|
+
toggleOptions?: string[];
|
|
273
|
+
};
|
|
274
|
+
barChart?: {
|
|
275
|
+
data: Record<string, unknown>[];
|
|
276
|
+
bars: Array<{
|
|
277
|
+
dataKey: string;
|
|
278
|
+
color: string;
|
|
279
|
+
name: string;
|
|
280
|
+
icon?: LucideIcon;
|
|
281
|
+
}>;
|
|
282
|
+
};
|
|
283
|
+
barList?: {
|
|
284
|
+
data: Array<{
|
|
285
|
+
name: string;
|
|
286
|
+
value: number;
|
|
287
|
+
}>;
|
|
288
|
+
valueFormatter?: (v: number) => string;
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
interface AccountFilterTab {
|
|
293
|
+
label: string;
|
|
294
|
+
count?: number;
|
|
295
|
+
variant?: "default" | "attention" | "ghost";
|
|
296
|
+
}
|
|
297
|
+
interface AccountsViewConfig {
|
|
298
|
+
filterTabs?: AccountFilterTab[];
|
|
299
|
+
rows?: DataRow[];
|
|
300
|
+
filterCategories?: DataTableFilterCategory[];
|
|
301
|
+
quickViews?: string[];
|
|
302
|
+
moreQuickViews?: string[];
|
|
303
|
+
quickViewFilters?: Record<string, (row: DataRow) => boolean>;
|
|
304
|
+
iconMap?: {
|
|
305
|
+
salesforce?: string;
|
|
306
|
+
};
|
|
307
|
+
entityUrlBuilder?: (row: DataRow) => string;
|
|
308
|
+
onScoreFactorFeedback?: (account: string, scoreType: string, factorKey: string, type: "up" | "down" | null, detail?: string) => void;
|
|
309
|
+
onScoreApproveFeedback?: (account: string, scoreType: string, reasons: string[], detail: string) => void;
|
|
310
|
+
onScoreDismissFeedback?: (account: string, scoreType: string, reasons: string[], detail: string) => void;
|
|
311
|
+
}
|
|
312
|
+
interface WorkQueueViewConfig {
|
|
313
|
+
[key: string]: unknown;
|
|
314
|
+
}
|
|
315
|
+
interface AdminTab {
|
|
316
|
+
id: string;
|
|
317
|
+
label: string;
|
|
318
|
+
icon?: React.ComponentType<{
|
|
319
|
+
className?: string;
|
|
320
|
+
}>;
|
|
321
|
+
content: React.ReactNode;
|
|
322
|
+
}
|
|
323
|
+
interface AdminViewConfig {
|
|
324
|
+
/** View title displayed in the header. Defaults to "Admin". */
|
|
325
|
+
title?: string;
|
|
326
|
+
/** Icon displayed next to the title. */
|
|
327
|
+
icon?: React.ComponentType<{
|
|
328
|
+
className?: string;
|
|
329
|
+
}>;
|
|
330
|
+
tabs: AdminTab[];
|
|
331
|
+
/** Which tab is active initially. Defaults to first tab's id. */
|
|
332
|
+
defaultTab?: string;
|
|
333
|
+
}
|
|
334
|
+
interface EntityPanelSection {
|
|
335
|
+
type: "details" | "contacts" | "recentActivity" | "connectedApps" | "systemActivity";
|
|
336
|
+
props?: Record<string, unknown>;
|
|
337
|
+
}
|
|
338
|
+
interface EntityPanelConfig {
|
|
339
|
+
sections?: EntityPanelSection[];
|
|
340
|
+
icons?: Record<string, string>;
|
|
341
|
+
}
|
|
342
|
+
interface PrototypeConfig {
|
|
343
|
+
brand?: PrototypeBrandConfig;
|
|
344
|
+
sidebar: SidebarNavSection[];
|
|
345
|
+
views: {
|
|
346
|
+
inbox?: InboxViewConfig;
|
|
347
|
+
insights?: InsightsViewConfig;
|
|
348
|
+
accounts?: AccountsViewConfig;
|
|
349
|
+
workQueue?: WorkQueueViewConfig;
|
|
350
|
+
admin?: AdminViewConfig;
|
|
351
|
+
};
|
|
352
|
+
defaultView: string;
|
|
353
|
+
entityPanel?: EntityPanelConfig;
|
|
354
|
+
/** Sidebar item IDs that trigger view navigation. Defaults to keys of `views`. */
|
|
355
|
+
navigableViews?: string[];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* A single contributing factor in the priority popover.
|
|
360
|
+
*/
|
|
361
|
+
interface PriorityFactor {
|
|
362
|
+
key: string;
|
|
363
|
+
label: string;
|
|
364
|
+
/** Lucide icon name (e.g. "radar", "wallet", "link-2", "message-square"). */
|
|
365
|
+
icon: string;
|
|
366
|
+
/** Drives icon background tint. */
|
|
367
|
+
tone: "alert" | "warn" | "info";
|
|
368
|
+
/** Explicit semantic label - NOT inferred from score+weight. */
|
|
369
|
+
direction: "raises" | "lowers" | "neutral";
|
|
370
|
+
/** 0-100 */
|
|
371
|
+
score: number;
|
|
372
|
+
/** Evidence text (e.g. "$3.4M moved in 8h - current treasury balance $0.00"). */
|
|
373
|
+
rationale: string;
|
|
374
|
+
}
|
|
375
|
+
interface SignalPriorityPopoverProps {
|
|
376
|
+
score: number;
|
|
377
|
+
urgencyLabel?: SignalScoreUrgencyLabel;
|
|
378
|
+
/** Synthesis sentence displayed in the popover head. */
|
|
379
|
+
urgencyExplanation?: string;
|
|
380
|
+
factors: PriorityFactor[];
|
|
381
|
+
/** e.g. "Updated 4m ago - model v3.2" */
|
|
382
|
+
metaText?: string;
|
|
383
|
+
/** Negative feedback issue tree. */
|
|
384
|
+
feedbackChips?: FeedbackChipTree[];
|
|
385
|
+
onFeedbackSubmit?: (data: FeedbackSubmitData) => void;
|
|
386
|
+
className?: string;
|
|
387
|
+
}
|
|
388
|
+
declare function SignalPriorityPopover({ score, urgencyLabel: providedLabel, urgencyExplanation, factors, metaText, feedbackChips, onFeedbackSubmit, className, }: SignalPriorityPopoverProps): React.JSX.Element;
|
|
389
|
+
|
|
390
|
+
export { type AccountFilterTab as A, type BriefStyleVariant as B, type EntityPanelConfig as E, type InboxDetailSections as I, type PriorityFactor as P, type QueueItem as Q, SignalPriorityPopover as S, type WorkQueueViewConfig as W, type AccountsViewConfig as a, type AdminTab as b, type AdminViewConfig as c, type EntityPanelSection as d, type InboxSortOption as e, type InboxViewConfig as f, type InsightsCustomTab as g, type InsightsViewConfig as h, type PrototypeBrandConfig as i, type PrototypeConfig as j, type SignalPriorityPopoverProps as k, type SignalScoreData as l, type SignalScoreExplanationBucket as m, type SignalScoreExplanationSignal as n, type SignalScoreUrgencyLabel as o };
|
package/package.json
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { describe, expect, it, vi } from "vitest"
|
|
3
|
+
import { render, screen, fireEvent } from "@testing-library/react"
|
|
4
|
+
|
|
5
|
+
import { EmptyChartState } from "../empty-chart-state"
|
|
6
|
+
import { PipelineOverview, type PipelineStage, type PipelineStageMetrics } from "../pipeline-overview"
|
|
7
|
+
|
|
8
|
+
vi.mock("@nivo/sankey", () => ({
|
|
9
|
+
ResponsiveSankey: ({ data }: { data: { nodes: unknown[]; links: unknown[] } }) => (
|
|
10
|
+
<div data-testid="mock-sankey">{data.nodes.length}:{data.links.length}</div>
|
|
11
|
+
),
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
const stages: PipelineStage[] = [
|
|
15
|
+
{ id: "received", label: "Received", count: 100, trend: "+5%", nextConversion: "80%" },
|
|
16
|
+
{ id: "contacted", label: "Contacted", count: 80, trend: "+3%", nextConversion: null },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
const metrics: Record<string, PipelineStageMetrics> = {
|
|
20
|
+
received: { medianTime: "1d", avgTime: "2d", dropOffs: [] },
|
|
21
|
+
contacted: { medianTime: "2d", avgTime: "3d", dropOffs: [] },
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function renderPipeline(variant?: "sankey" | "compact", onFilterChange = vi.fn()) {
|
|
25
|
+
return render(
|
|
26
|
+
<PipelineOverview
|
|
27
|
+
variant={variant}
|
|
28
|
+
stages={stages}
|
|
29
|
+
stageMetrics={metrics}
|
|
30
|
+
stageTimings={[{ median: "1d", avg: "2d" }, null]}
|
|
31
|
+
filterOptions={["Facility", "Source"]}
|
|
32
|
+
onFilterChange={onFilterChange}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe("Insights chart primitives", () => {
|
|
38
|
+
it("EmptyChartState renders defaults and action", () => {
|
|
39
|
+
const { container } = render(<EmptyChartState action={<button type="button">Reset</button>} />)
|
|
40
|
+
|
|
41
|
+
expect(container.querySelector('[data-slot="empty-chart-state"]')).not.toBeNull()
|
|
42
|
+
expect(screen.getByText("No chart data")).not.toBeNull()
|
|
43
|
+
expect(screen.getByRole("button", { name: "Reset" })).not.toBeNull()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it("PipelineOverview default variant preserves Sankey rendering and callbacks", () => {
|
|
47
|
+
const onFilterChange = vi.fn()
|
|
48
|
+
renderPipeline(undefined, onFilterChange)
|
|
49
|
+
|
|
50
|
+
expect(screen.getByTestId("mock-sankey")).not.toBeNull()
|
|
51
|
+
fireEvent.click(screen.getByRole("button", { name: "Source" }))
|
|
52
|
+
expect(onFilterChange).toHaveBeenCalledWith("Source")
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it("PipelineOverview compact variant renders compact bars instead of Sankey", () => {
|
|
56
|
+
const { container } = renderPipeline("compact")
|
|
57
|
+
|
|
58
|
+
expect(screen.queryByTestId("mock-sankey")).toBeNull()
|
|
59
|
+
expect(container.querySelector('[data-slot="pipeline-overview-compact"]')).not.toBeNull()
|
|
60
|
+
expect(container.querySelectorAll('[data-slot="pipeline-overview-compact-bar"]')).toHaveLength(2)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { BarChart3 } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
import { cn } from "../lib/utils"
|
|
5
|
+
|
|
6
|
+
export interface EmptyChartStateProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
|
|
7
|
+
title?: React.ReactNode
|
|
8
|
+
description?: React.ReactNode
|
|
9
|
+
icon?: React.ReactNode
|
|
10
|
+
action?: React.ReactNode
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function EmptyChartState({
|
|
14
|
+
title = "No chart data",
|
|
15
|
+
description = "Try adjusting filters or selecting a different time range.",
|
|
16
|
+
icon,
|
|
17
|
+
action,
|
|
18
|
+
className,
|
|
19
|
+
...props
|
|
20
|
+
}: EmptyChartStateProps) {
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
data-slot="empty-chart-state"
|
|
24
|
+
className={cn(
|
|
25
|
+
"flex min-h-[240px] flex-col items-center justify-center rounded-xl border border-dashed border-border bg-muted/30 p-8 text-center",
|
|
26
|
+
className
|
|
27
|
+
)}
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
<div data-slot="empty-chart-state-icon" className="mb-3 text-muted-foreground [&>svg]:h-10 [&>svg]:w-10">
|
|
31
|
+
{icon ?? <BarChart3 aria-hidden="true" />}
|
|
32
|
+
</div>
|
|
33
|
+
<div data-slot="empty-chart-state-title" className="text-sm font-semibold text-foreground">
|
|
34
|
+
{title}
|
|
35
|
+
</div>
|
|
36
|
+
{description ? (
|
|
37
|
+
<div data-slot="empty-chart-state-description" className="mt-1 max-w-sm text-sm text-muted-foreground">
|
|
38
|
+
{description}
|
|
39
|
+
</div>
|
|
40
|
+
) : null}
|
|
41
|
+
{action ? <div data-slot="empty-chart-state-action" className="mt-4">{action}</div> : null}
|
|
42
|
+
</div>
|
|
43
|
+
)
|
|
44
|
+
}
|
package/src/charts/index.ts
CHANGED
|
@@ -125,6 +125,7 @@ function StageHoverCard({
|
|
|
125
125
|
|
|
126
126
|
export interface PipelineOverviewProps {
|
|
127
127
|
title?: string
|
|
128
|
+
variant?: "sankey" | "compact"
|
|
128
129
|
stages: PipelineStage[]
|
|
129
130
|
stageMetrics: Record<string, PipelineStageMetrics>
|
|
130
131
|
stageTimings: (PipelineStageTiming | null)[]
|
|
@@ -162,6 +163,7 @@ export interface PipelineOverviewProps {
|
|
|
162
163
|
|
|
163
164
|
export function PipelineOverview({
|
|
164
165
|
title = "Pipeline Overview",
|
|
166
|
+
variant = "sankey",
|
|
165
167
|
stages,
|
|
166
168
|
stageMetrics,
|
|
167
169
|
stageTimings,
|
|
@@ -424,8 +426,42 @@ export function PipelineOverview({
|
|
|
424
426
|
})}
|
|
425
427
|
</div>
|
|
426
428
|
|
|
427
|
-
{
|
|
429
|
+
{variant === "compact" ? (
|
|
430
|
+
<div data-slot="pipeline-overview-compact" className="mt-4 space-y-2">
|
|
431
|
+
{stages.map((stage, index) => {
|
|
432
|
+
const maxCount = Math.max(...stages.map((item) => item.count), 1)
|
|
433
|
+
const width = `${Math.max((stage.count / maxCount) * 100, 4)}%`
|
|
434
|
+
|
|
435
|
+
return (
|
|
436
|
+
<div key={stage.id} className="rounded-lg border border-border bg-background p-3">
|
|
437
|
+
<div className="mb-2 flex items-center justify-between gap-3">
|
|
438
|
+
<div className="min-w-0">
|
|
439
|
+
<div className="truncate text-sm font-medium text-foreground">{stage.label}</div>
|
|
440
|
+
<div className="text-xs text-muted-foreground">{stage.trend}</div>
|
|
441
|
+
</div>
|
|
442
|
+
<div className="text-right text-lg font-bold text-foreground">
|
|
443
|
+
{stage.count.toLocaleString()}
|
|
444
|
+
</div>
|
|
445
|
+
</div>
|
|
446
|
+
<div className="h-2 overflow-hidden rounded-full bg-muted">
|
|
447
|
+
<div
|
|
448
|
+
data-slot="pipeline-overview-compact-bar"
|
|
449
|
+
className="h-full rounded-full bg-emerald-600"
|
|
450
|
+
style={{ width }}
|
|
451
|
+
/>
|
|
452
|
+
</div>
|
|
453
|
+
{index < stages.length - 1 && stage.nextConversion ? (
|
|
454
|
+
<div className="mt-2 text-xs text-muted-foreground">
|
|
455
|
+
{stage.nextConversion} to {stages[index + 1]?.label}
|
|
456
|
+
</div>
|
|
457
|
+
) : null}
|
|
458
|
+
</div>
|
|
459
|
+
)
|
|
460
|
+
})}
|
|
461
|
+
</div>
|
|
462
|
+
) : (
|
|
428
463
|
<div
|
|
464
|
+
data-slot="pipeline-overview-sankey"
|
|
429
465
|
className="relative mt-4 w-full"
|
|
430
466
|
style={{ height: 400, minWidth: 0 }}
|
|
431
467
|
>
|
|
@@ -471,6 +507,7 @@ export function PipelineOverview({
|
|
|
471
507
|
}}
|
|
472
508
|
/>
|
|
473
509
|
</div>
|
|
510
|
+
)}
|
|
474
511
|
</div>
|
|
475
512
|
)
|
|
476
513
|
}
|