@handled-ai/design-system 0.20.0 → 0.20.2
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/components/conversation-panel.d.ts +1 -1
- package/dist/components/conversation-panel.js +282 -15
- package/dist/components/conversation-panel.js.map +1 -1
- package/dist/components/owner-chips.d.ts +3 -4
- package/dist/components/owner-chips.js +77 -41
- package/dist/components/owner-chips.js.map +1 -1
- package/dist/components/score-why-chips.d.ts +1 -1
- package/dist/components/signal-priority-popover.d.ts +1 -1
- package/dist/components/timeline-activity.d.ts +4 -2
- package/dist/components/timeline-activity.js +366 -154
- package/dist/components/timeline-activity.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/prototype/index.d.ts +1 -1
- package/dist/prototype/prototype-accounts-view.d.ts +1 -1
- package/dist/prototype/prototype-admin-view.d.ts +1 -1
- package/dist/prototype/prototype-config.d.ts +1 -1
- package/dist/prototype/prototype-inbox-view.d.ts +9 -3
- package/dist/prototype/prototype-inbox-view.js +94 -47
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/dist/prototype/prototype-insights-view.d.ts +1 -1
- package/dist/prototype/prototype-shell.d.ts +1 -1
- package/dist/{signal-priority-popover-Cg9XPJsp.d.ts → signal-priority-popover-BJHd07dU.d.ts} +6 -0
- package/package.json +1 -1
- package/src/components/__tests__/conversation-panel.test.tsx +276 -0
- package/src/components/__tests__/owner-chips.test.tsx +137 -17
- package/src/components/__tests__/timeline-activity.test.tsx +92 -1
- package/src/components/conversation-panel.tsx +358 -21
- package/src/components/owner-chips.tsx +98 -63
- package/src/components/timeline-activity.tsx +452 -160
- package/src/prototype/__tests__/detail-view-case-panel-v2.test.tsx +181 -0
- package/src/prototype/__tests__/detail-view-timeline-system-events.test.tsx +16 -2
- package/src/prototype/prototype-config.ts +6 -0
- package/src/prototype/prototype-inbox-view.tsx +128 -51
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { cleanup, render, screen } from "@testing-library/react"
|
|
2
|
+
import { afterEach, describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import { DetailView, type DetailViewProps } from "../prototype-inbox-view"
|
|
5
|
+
import type { QueueItem, SignalScoreData } from "../prototype-config"
|
|
6
|
+
import type { TimelineEvent } from "../../components/timeline-activity"
|
|
7
|
+
|
|
8
|
+
const baseItem: QueueItem = {
|
|
9
|
+
id: "case-1",
|
|
10
|
+
title: "Churn risk case title",
|
|
11
|
+
details: "Case details",
|
|
12
|
+
statusColor: "red",
|
|
13
|
+
time: "1h ago",
|
|
14
|
+
company: "Acme Corp",
|
|
15
|
+
tag1: "Signal",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const timelineEvents: TimelineEvent[] = [
|
|
19
|
+
{
|
|
20
|
+
id: "timeline-1",
|
|
21
|
+
icon: <span aria-hidden="true">•</span>,
|
|
22
|
+
title: "Timeline marker event",
|
|
23
|
+
time: "10m ago",
|
|
24
|
+
},
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
const FOLLOWS = Node.DOCUMENT_POSITION_FOLLOWING
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
cleanup()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
function makeSignalScore(overrides: Partial<SignalScoreData> = {}): SignalScoreData {
|
|
34
|
+
return {
|
|
35
|
+
score: 84,
|
|
36
|
+
factors: [],
|
|
37
|
+
whyNow: "The why copy: customer replied after the escalation window opened.",
|
|
38
|
+
evidence: ["Evidence line"],
|
|
39
|
+
confidence: 91,
|
|
40
|
+
urgencyLabel: "High",
|
|
41
|
+
urgencyExplanation: "Urgency explanation copy.",
|
|
42
|
+
signalBrief: "Signal brief copy: account activity needs review.",
|
|
43
|
+
timeChipLabel: "3 days left",
|
|
44
|
+
explanationBuckets: [
|
|
45
|
+
{
|
|
46
|
+
key: "cash-movement",
|
|
47
|
+
label: "Cash movement",
|
|
48
|
+
kind: "signal",
|
|
49
|
+
signals: [{ id: "sig-1", label: "Large outgoing transfer" }],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
...overrides,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function renderDetailView(
|
|
57
|
+
overrides: Partial<DetailViewProps> = {},
|
|
58
|
+
scoreOverrides: Partial<SignalScoreData> = {},
|
|
59
|
+
) {
|
|
60
|
+
const props: DetailViewProps = {
|
|
61
|
+
item: baseItem,
|
|
62
|
+
sections: { signalBrief: true, suggestedActions: false, timeline: true },
|
|
63
|
+
getSignalScore: () => makeSignalScore(scoreOverrides),
|
|
64
|
+
buildSuggestedActions: () => [],
|
|
65
|
+
buildSourceItems: () => [],
|
|
66
|
+
getTimelineEvents: () => timelineEvents,
|
|
67
|
+
accountContacts: [],
|
|
68
|
+
emailSignature: "",
|
|
69
|
+
iconMap: {},
|
|
70
|
+
...overrides,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return render(<DetailView {...props} />)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function expectInDocumentOrder(...nodes: HTMLElement[]) {
|
|
77
|
+
nodes.forEach((node, index) => {
|
|
78
|
+
const next = nodes[index + 1]
|
|
79
|
+
if (!next) return
|
|
80
|
+
expect(node.compareDocumentPosition(next) & FOLLOWS).toBeTruthy()
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe("DetailView case-panel-v2 section layout", () => {
|
|
85
|
+
it("keeps the default layout unchanged and ignores v2-only slots", () => {
|
|
86
|
+
renderDetailView({
|
|
87
|
+
renderMetadataExtra: () => <span>Metadata marker</span>,
|
|
88
|
+
renderAfterScore: () => <section>After-score marker</section>,
|
|
89
|
+
renderPrimaryAction: () => <section>Primary action marker</section>,
|
|
90
|
+
renderCommentArea: () => <section>Comment area marker</section>,
|
|
91
|
+
renderDetailExtra: () => <section>Legacy detail extra marker</section>,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
expect(screen.queryByText("Primary action marker")).toBeNull()
|
|
95
|
+
expect(screen.queryByText("Comment area marker")).toBeNull()
|
|
96
|
+
expect(screen.getByText("Legacy detail extra marker")).toBeTruthy()
|
|
97
|
+
|
|
98
|
+
expectInDocumentOrder(
|
|
99
|
+
screen.getByText("Metadata marker"),
|
|
100
|
+
screen.getByText("Signal brief copy: account activity needs review."),
|
|
101
|
+
screen.getByText("Cash movement"),
|
|
102
|
+
screen.getByText("Approve action"),
|
|
103
|
+
screen.getByText("After-score marker"),
|
|
104
|
+
screen.getByText("Activity timeline"),
|
|
105
|
+
screen.getByText("Legacy detail extra marker"),
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it("renders v2 sections in the approved order", () => {
|
|
110
|
+
renderDetailView({
|
|
111
|
+
sectionLayout: "case-panel-v2",
|
|
112
|
+
renderMetadataExtra: () => <span>Metadata marker</span>,
|
|
113
|
+
renderBeforeScore: () => <section>Before-score status marker</section>,
|
|
114
|
+
renderAfterScore: () => <section>Opportunity marker</section>,
|
|
115
|
+
renderPrimaryAction: () => <section>Primary action marker</section>,
|
|
116
|
+
renderCommentArea: () => <section>Comment area marker</section>,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
expectInDocumentOrder(
|
|
120
|
+
screen.getByText("Signal brief copy: account activity needs review."),
|
|
121
|
+
screen.getByText("Metadata marker"),
|
|
122
|
+
screen.getByText("Before-score status marker"),
|
|
123
|
+
screen.getByText("The why"),
|
|
124
|
+
screen.getByText("The why copy: customer replied after the escalation window opened."),
|
|
125
|
+
screen.getByText("Cash movement"),
|
|
126
|
+
screen.getByText("Approve action"),
|
|
127
|
+
screen.getByText("Opportunity marker"),
|
|
128
|
+
screen.getByText("Primary action marker"),
|
|
129
|
+
screen.getByText("Comment area marker"),
|
|
130
|
+
screen.getByText("Activity timeline"),
|
|
131
|
+
)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it("renders the primary slot in the v2 layout", () => {
|
|
135
|
+
renderDetailView({
|
|
136
|
+
sectionLayout: "case-panel-v2",
|
|
137
|
+
renderPrimaryAction: (item) => (
|
|
138
|
+
<section aria-label="primary slot">Primary action for {item.id}</section>
|
|
139
|
+
),
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
expect(screen.getByText("Primary action for case-1")).toBeTruthy()
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it("places the comment slot before the timeline in the v2 layout", () => {
|
|
146
|
+
renderDetailView({
|
|
147
|
+
sectionLayout: "case-panel-v2",
|
|
148
|
+
renderCommentArea: () => <section>Comment composer marker</section>,
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
expectInDocumentOrder(
|
|
152
|
+
screen.getByText("Comment composer marker"),
|
|
153
|
+
screen.getByText("Activity timeline"),
|
|
154
|
+
)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it("renders signal brief and The why separately when both copies exist", () => {
|
|
158
|
+
renderDetailView({ sectionLayout: "case-panel-v2" })
|
|
159
|
+
|
|
160
|
+
expectInDocumentOrder(
|
|
161
|
+
screen.getByText("Signal brief copy: account activity needs review."),
|
|
162
|
+
screen.getByText("The why"),
|
|
163
|
+
screen.getByText("The why copy: customer replied after the escalation window opened."),
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it("falls back to whyNow in the brief and suppresses the separate The why block when signalBrief is missing", () => {
|
|
168
|
+
renderDetailView(
|
|
169
|
+
{ sectionLayout: "case-panel-v2" },
|
|
170
|
+
{
|
|
171
|
+
signalBrief: undefined,
|
|
172
|
+
whyNow: "Fallback why copy shown in the brief.",
|
|
173
|
+
urgencyExplanation: "Urgency copy should not render as a separate why block.",
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
expect(screen.getAllByText("Fallback why copy shown in the brief.")).toHaveLength(1)
|
|
178
|
+
expect(screen.queryByText("The why")).toBeNull()
|
|
179
|
+
expect(screen.queryByText("Urgency copy should not render as a separate why block.")).toBeNull()
|
|
180
|
+
})
|
|
181
|
+
})
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "@testing-library/jest-dom/vitest"
|
|
1
2
|
import { describe, it, expect, vi, beforeEach } from "vitest"
|
|
2
3
|
import React from "react"
|
|
3
4
|
import { render, fireEvent } from "@testing-library/react"
|
|
@@ -181,14 +182,19 @@ describe("DetailView timeline system-events toggle", () => {
|
|
|
181
182
|
'[data-testid="system-events-toggle"]',
|
|
182
183
|
) as HTMLElement
|
|
183
184
|
fireEvent.click(toggle)
|
|
185
|
+
expect(toggle).toHaveAttribute("aria-pressed", "true")
|
|
184
186
|
expect(container.textContent).toContain("Score updated +3")
|
|
185
187
|
|
|
186
188
|
// Collapse the timeline
|
|
187
189
|
expandTimeline(container)
|
|
190
|
+
expect(toggle).toHaveAttribute("aria-pressed", "true")
|
|
191
|
+
expect(container.textContent).not.toContain("Score updated +3")
|
|
192
|
+
|
|
188
193
|
// Re-expand
|
|
189
194
|
expandTimeline(container)
|
|
190
195
|
|
|
191
196
|
// System events should still be visible (toggle didn't change)
|
|
197
|
+
expect(toggle).toHaveAttribute("aria-pressed", "true")
|
|
192
198
|
expect(container.textContent).toContain("Score updated +3")
|
|
193
199
|
})
|
|
194
200
|
|
|
@@ -203,15 +209,20 @@ describe("DetailView timeline system-events toggle", () => {
|
|
|
203
209
|
const badge = container.querySelector('[data-testid="hidden-count-badge"]')
|
|
204
210
|
expect(badge).not.toBeNull()
|
|
205
211
|
expect(badge?.textContent).toBe("2")
|
|
212
|
+
expect(badge).toHaveClass("min-w-[18px]")
|
|
206
213
|
})
|
|
207
214
|
|
|
208
|
-
it("calls localStorage.setItem when toggle changes", () => {
|
|
215
|
+
it("calls localStorage.setItem when toggle changes and shows a stronger pressed style", () => {
|
|
209
216
|
const { container } = render(<DetailView {...baseProps()} />)
|
|
210
217
|
expandTimeline(container)
|
|
211
218
|
const toggle = container.querySelector(
|
|
212
219
|
'[data-testid="system-events-toggle"]',
|
|
213
220
|
) as HTMLElement
|
|
221
|
+
expect(toggle).toHaveAttribute("aria-pressed", "false")
|
|
214
222
|
fireEvent.click(toggle)
|
|
223
|
+
expect(toggle).toHaveAttribute("aria-pressed", "true")
|
|
224
|
+
expect(toggle.className).toContain("border-primary/40")
|
|
225
|
+
expect(toggle.className).toContain("bg-primary/10")
|
|
215
226
|
expect(localStorageMock.setItem).toHaveBeenCalledWith(
|
|
216
227
|
"test-show-score-changes",
|
|
217
228
|
"true",
|
|
@@ -301,12 +312,15 @@ describe("DetailView timeline system-events toggle", () => {
|
|
|
301
312
|
expect(toggle).toBeNull()
|
|
302
313
|
})
|
|
303
314
|
|
|
304
|
-
it("shows footer hint when timeline is expanded and system events are hidden", () => {
|
|
315
|
+
it("shows footer hint below the case-panel timeline when timeline is expanded and system events are hidden", () => {
|
|
305
316
|
const { container } = render(<DetailView {...baseProps()} />)
|
|
306
317
|
expandTimeline(container)
|
|
318
|
+
const timeline = container.querySelector('[data-variant="case-panel"]')
|
|
307
319
|
const hint = container.querySelector('[data-testid="timeline-footer-hint"]')
|
|
320
|
+
expect(timeline).not.toBeNull()
|
|
308
321
|
expect(hint).not.toBeNull()
|
|
309
322
|
expect(hint?.textContent).toBe("Score changes are hidden.")
|
|
323
|
+
expect(timeline?.compareDocumentPosition(hint as Node)).toBe(Node.DOCUMENT_POSITION_FOLLOWING)
|
|
310
324
|
})
|
|
311
325
|
|
|
312
326
|
it("shows visible footer hint with count when system events are shown", () => {
|
|
@@ -224,12 +224,18 @@ export interface InboxViewConfig {
|
|
|
224
224
|
* - "prominent": standard foreground color, text-base size for the brief
|
|
225
225
|
*/
|
|
226
226
|
briefStyleVariant?: BriefStyleVariant
|
|
227
|
+
/** Opt-in detail panel section ordering. Defaults to the existing layout. */
|
|
228
|
+
sectionLayout?: "default" | "case-panel-v2"
|
|
227
229
|
/** Render extra content at the end of the detail view, below the suggested actions section. */
|
|
228
230
|
renderDetailExtra?: (item: QueueItem) => React.ReactNode
|
|
229
231
|
/** Render content between the signal brief text and the signal score bar (e.g. "Signals on Case" chips). */
|
|
230
232
|
renderBeforeScore?: (item: QueueItem) => React.ReactNode
|
|
231
233
|
/** Render content between the signal score section and the activity timeline (e.g. OpportunityPanel). */
|
|
232
234
|
renderAfterScore?: (item: QueueItem) => React.ReactNode
|
|
235
|
+
/** Render primary case-panel content between the opportunity section and comment area. */
|
|
236
|
+
renderPrimaryAction?: (item: QueueItem) => React.ReactNode
|
|
237
|
+
/** Render case-panel comment content before the activity timeline. */
|
|
238
|
+
renderCommentArea?: (item: QueueItem) => React.ReactNode
|
|
233
239
|
/** Formatted string for "Last activity X ago" in the collapsed timeline header. If omitted, falls back to the first event's time. */
|
|
234
240
|
lastActivityTime?: string
|
|
235
241
|
/** Configuration for the system-noise events toggle (score changes, etc.). */
|
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
type SuggestedContact,
|
|
49
49
|
} from "../components/suggested-actions"
|
|
50
50
|
import { TimelineActivity, type TimelineEvent } from "../components/timeline-activity"
|
|
51
|
+
import { cn } from "../lib/utils"
|
|
51
52
|
import type {
|
|
52
53
|
QueueItem,
|
|
53
54
|
InboxViewConfig,
|
|
@@ -155,11 +156,17 @@ export interface DetailViewProps {
|
|
|
155
156
|
hideApproveButton?: boolean
|
|
156
157
|
signalBriefCopy?: InboxViewConfig["signalBriefCopy"]
|
|
157
158
|
briefStyleVariant?: BriefStyleVariant
|
|
159
|
+
/** Opt-in detail panel section ordering. Defaults to the existing layout. */
|
|
160
|
+
sectionLayout?: InboxViewConfig["sectionLayout"]
|
|
158
161
|
renderDetailExtra?: (item: QueueItem) => React.ReactNode
|
|
159
162
|
/** Render content between the signal brief text and the signal score bar (e.g. "Signals on Case" chips). */
|
|
160
163
|
renderBeforeScore?: (item: QueueItem) => React.ReactNode
|
|
161
164
|
/** Render content between the signal score section and the activity timeline. */
|
|
162
165
|
renderAfterScore?: (item: QueueItem) => React.ReactNode
|
|
166
|
+
/** Render primary case-panel content between the opportunity section and comment area. */
|
|
167
|
+
renderPrimaryAction?: (item: QueueItem) => React.ReactNode
|
|
168
|
+
/** Render case-panel comment content before the activity timeline. */
|
|
169
|
+
renderCommentArea?: (item: QueueItem) => React.ReactNode
|
|
163
170
|
lastActivityTime?: string
|
|
164
171
|
/** Render extra content inline with the detail title. */
|
|
165
172
|
renderTitleExtra?: (item: QueueItem) => React.ReactNode
|
|
@@ -290,13 +297,23 @@ function TimelineSection({
|
|
|
290
297
|
<button
|
|
291
298
|
type="button"
|
|
292
299
|
onClick={() => setShowSystemEvents((prev) => !prev)}
|
|
293
|
-
className=
|
|
300
|
+
className={cn(
|
|
301
|
+
"flex shrink-0 cursor-pointer items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-medium transition-colors hover:text-foreground",
|
|
302
|
+
showSystemEvents
|
|
303
|
+
? "border-primary/40 bg-primary/10 text-primary shadow-sm hover:bg-primary/15"
|
|
304
|
+
: "border-border bg-background text-muted-foreground hover:bg-muted/40"
|
|
305
|
+
)}
|
|
294
306
|
aria-pressed={showSystemEvents}
|
|
295
307
|
data-testid="system-events-toggle"
|
|
296
308
|
>
|
|
297
309
|
{toggleLabel}
|
|
298
310
|
<span
|
|
299
|
-
className=
|
|
311
|
+
className={cn(
|
|
312
|
+
"inline-flex min-w-[18px] items-center justify-center rounded-full px-1.5 text-[10px] font-semibold tabular-nums",
|
|
313
|
+
showSystemEvents
|
|
314
|
+
? "bg-primary/15 text-primary ring-1 ring-primary/30"
|
|
315
|
+
: "bg-muted text-muted-foreground ring-1 ring-border/70"
|
|
316
|
+
)}
|
|
300
317
|
data-testid="hidden-count-badge"
|
|
301
318
|
>
|
|
302
319
|
{hiddenCount}
|
|
@@ -308,7 +325,7 @@ function TimelineSection({
|
|
|
308
325
|
{/* Timeline body */}
|
|
309
326
|
{showTimeline && visibleEvents.length > 0 && (
|
|
310
327
|
<div className="mt-3">
|
|
311
|
-
<TimelineActivity events={visibleEvents} />
|
|
328
|
+
<TimelineActivity events={visibleEvents} variant="case-panel" />
|
|
312
329
|
</div>
|
|
313
330
|
)}
|
|
314
331
|
|
|
@@ -351,9 +368,12 @@ export function DetailView({
|
|
|
351
368
|
hideApproveButton,
|
|
352
369
|
signalBriefCopy,
|
|
353
370
|
briefStyleVariant = "default",
|
|
371
|
+
sectionLayout = "default",
|
|
354
372
|
renderDetailExtra,
|
|
355
373
|
renderBeforeScore,
|
|
356
374
|
renderAfterScore,
|
|
375
|
+
renderPrimaryAction,
|
|
376
|
+
renderCommentArea,
|
|
357
377
|
lastActivityTime,
|
|
358
378
|
renderTitleExtra,
|
|
359
379
|
renderTitleActionRow,
|
|
@@ -481,6 +501,11 @@ export function DetailView({
|
|
|
481
501
|
? "border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-50"
|
|
482
502
|
: "hover:bg-muted/50"
|
|
483
503
|
|
|
504
|
+
const isCasePanelV2 = sectionLayout === "case-panel-v2"
|
|
505
|
+
const v2WhyText = signalData.signalBrief
|
|
506
|
+
? signalData.whyNow || signalData.urgencyExplanation
|
|
507
|
+
: undefined
|
|
508
|
+
|
|
484
509
|
// The metadata chips row (priority · deadline · account · renderMetadataExtra). Rendered above
|
|
485
510
|
// the brief by default, or beneath it when `metadataLayout === "below-brief"` (case-panel redesign).
|
|
486
511
|
const metadataChips = (
|
|
@@ -551,6 +576,38 @@ export function DetailView({
|
|
|
551
576
|
</>
|
|
552
577
|
)
|
|
553
578
|
|
|
579
|
+
const timelineSection = sections.timeline && timelineEvents.length > 0 ? (
|
|
580
|
+
<TimelineSection
|
|
581
|
+
timelineEvents={timelineEvents}
|
|
582
|
+
showTimeline={showTimeline}
|
|
583
|
+
setShowTimeline={setShowTimeline}
|
|
584
|
+
showSystemEvents={showSystemEvents}
|
|
585
|
+
setShowSystemEvents={setShowSystemEvents}
|
|
586
|
+
attentionCount={attentionCount}
|
|
587
|
+
sysEvtConfig={sysEvtConfig}
|
|
588
|
+
lastActivityTime={lastActivityTime}
|
|
589
|
+
/>
|
|
590
|
+
) : null
|
|
591
|
+
|
|
592
|
+
const suggestedActionsSection = sections.suggestedActions ? (
|
|
593
|
+
<SignalApproval.Gate>
|
|
594
|
+
<SuggestedActions
|
|
595
|
+
actions={suggestedActions}
|
|
596
|
+
accountContacts={accountContacts}
|
|
597
|
+
signature={emailSignature}
|
|
598
|
+
iconMap={iconMap}
|
|
599
|
+
onDismiss={(id) => console.log("Dismiss action:", id)}
|
|
600
|
+
onSend={(id) => console.log("Send action:", id)}
|
|
601
|
+
onSaveDraft={(id) => console.log("Save draft:", id)}
|
|
602
|
+
onDuplicate={handleDuplicate}
|
|
603
|
+
onOpenAccountDetails={onOpenEntityPanel}
|
|
604
|
+
onOpenRecentActivity={onOpenRecentActivity}
|
|
605
|
+
onMarkComplete={(id) => console.log("Mark complete:", id)}
|
|
606
|
+
onDispatchAgent={(id) => console.log("Dispatch agent:", id)}
|
|
607
|
+
/>
|
|
608
|
+
</SignalApproval.Gate>
|
|
609
|
+
) : null
|
|
610
|
+
|
|
554
611
|
return (
|
|
555
612
|
<SignalApproval.Root
|
|
556
613
|
key={item.id}
|
|
@@ -592,7 +649,7 @@ export function DetailView({
|
|
|
592
649
|
) : null}
|
|
593
650
|
</div>
|
|
594
651
|
|
|
595
|
-
{metadataLayout === "default" ? (
|
|
652
|
+
{!isCasePanelV2 && metadataLayout === "default" ? (
|
|
596
653
|
<div className="mb-6 flex flex-wrap items-center gap-2">{metadataChips}</div>
|
|
597
654
|
) : null}
|
|
598
655
|
|
|
@@ -637,63 +694,77 @@ export function DetailView({
|
|
|
637
694
|
)}
|
|
638
695
|
|
|
639
696
|
{/* Metadata chips relocated beneath the brief (case-panel redesign). */}
|
|
640
|
-
{metadataLayout === "below-brief" ? (
|
|
697
|
+
{isCasePanelV2 || metadataLayout === "below-brief" ? (
|
|
641
698
|
<div className="mb-4 flex flex-wrap items-center gap-2">{metadataChips}</div>
|
|
642
699
|
) : null}
|
|
643
700
|
|
|
644
|
-
{
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
701
|
+
{!isCasePanelV2 ? (
|
|
702
|
+
<>
|
|
703
|
+
{/* Before-score content slot (e.g. "Signals on Case" chips) */}
|
|
704
|
+
{renderBeforeScore?.(item)}
|
|
705
|
+
|
|
706
|
+
<ScoreWhyChips
|
|
707
|
+
item={item}
|
|
708
|
+
signalData={signalData}
|
|
709
|
+
onOpenSignalBucket={onOpenSignalBucket}
|
|
710
|
+
/>
|
|
711
|
+
<div className="mt-4">
|
|
712
|
+
<SignalApproval.Actions />
|
|
713
|
+
</div>
|
|
714
|
+
</>
|
|
715
|
+
) : null}
|
|
655
716
|
</div>
|
|
656
717
|
)
|
|
657
718
|
})()}
|
|
658
719
|
|
|
659
|
-
{
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
720
|
+
{isCasePanelV2 ? (
|
|
721
|
+
<>
|
|
722
|
+
{sections.signalBrief ? (
|
|
723
|
+
<>
|
|
724
|
+
{/* Before-score content slot (e.g. status/attention pills) */}
|
|
725
|
+
{renderBeforeScore?.(item)}
|
|
726
|
+
|
|
727
|
+
{v2WhyText ? (
|
|
728
|
+
<div className="mb-8">
|
|
729
|
+
<h3 className="text-xs font-bold text-muted-foreground uppercase tracking-wider mb-3">The why</h3>
|
|
730
|
+
<p className="text-sm text-foreground/90 leading-relaxed mb-4">
|
|
731
|
+
{v2WhyText}
|
|
732
|
+
</p>
|
|
733
|
+
</div>
|
|
734
|
+
) : null}
|
|
735
|
+
|
|
736
|
+
<div className="mb-8">
|
|
737
|
+
<ScoreWhyChips
|
|
738
|
+
item={item}
|
|
739
|
+
signalData={signalData}
|
|
740
|
+
onOpenSignalBucket={onOpenSignalBucket}
|
|
741
|
+
/>
|
|
742
|
+
<div className="mt-4">
|
|
743
|
+
<SignalApproval.Actions />
|
|
744
|
+
</div>
|
|
745
|
+
</div>
|
|
746
|
+
</>
|
|
747
|
+
) : null}
|
|
748
|
+
|
|
749
|
+
{/* After-score content slot (e.g. OpportunityPanel) */}
|
|
750
|
+
{renderAfterScore?.(item)}
|
|
751
|
+
{renderPrimaryAction?.(item)}
|
|
752
|
+
{renderCommentArea?.(item)}
|
|
753
|
+
{timelineSection}
|
|
754
|
+
</>
|
|
755
|
+
) : (
|
|
756
|
+
<>
|
|
757
|
+
{/* After-score content slot (e.g. OpportunityPanel) */}
|
|
758
|
+
{renderAfterScore?.(item)}
|
|
759
|
+
|
|
760
|
+
{/* Activity Timeline */}
|
|
761
|
+
{timelineSection}
|
|
762
|
+
</>
|
|
674
763
|
)}
|
|
675
764
|
</div>
|
|
676
765
|
|
|
677
|
-
{
|
|
678
|
-
{
|
|
679
|
-
<SignalApproval.Gate>
|
|
680
|
-
<SuggestedActions
|
|
681
|
-
actions={suggestedActions}
|
|
682
|
-
accountContacts={accountContacts}
|
|
683
|
-
signature={emailSignature}
|
|
684
|
-
iconMap={iconMap}
|
|
685
|
-
onDismiss={(id) => console.log("Dismiss action:", id)}
|
|
686
|
-
onSend={(id) => console.log("Send action:", id)}
|
|
687
|
-
onSaveDraft={(id) => console.log("Save draft:", id)}
|
|
688
|
-
onDuplicate={handleDuplicate}
|
|
689
|
-
onOpenAccountDetails={onOpenEntityPanel}
|
|
690
|
-
onOpenRecentActivity={onOpenRecentActivity}
|
|
691
|
-
onMarkComplete={(id) => console.log("Mark complete:", id)}
|
|
692
|
-
onDispatchAgent={(id) => console.log("Dispatch agent:", id)}
|
|
693
|
-
/>
|
|
694
|
-
</SignalApproval.Gate>
|
|
695
|
-
)}
|
|
696
|
-
{renderDetailExtra?.(item)}
|
|
766
|
+
{!isCasePanelV2 ? suggestedActionsSection : null}
|
|
767
|
+
{!isCasePanelV2 ? renderDetailExtra?.(item) : null}
|
|
697
768
|
</div>
|
|
698
769
|
</SignalApproval.Root>
|
|
699
770
|
)
|
|
@@ -735,9 +806,12 @@ export function PrototypeInboxView({
|
|
|
735
806
|
hideApproveButton,
|
|
736
807
|
signalBriefCopy,
|
|
737
808
|
briefStyleVariant,
|
|
809
|
+
sectionLayout,
|
|
738
810
|
renderDetailExtra,
|
|
739
811
|
renderBeforeScore,
|
|
740
812
|
renderAfterScore,
|
|
813
|
+
renderPrimaryAction,
|
|
814
|
+
renderCommentArea,
|
|
741
815
|
lastActivityTime,
|
|
742
816
|
timelineSystemEventsConfig,
|
|
743
817
|
attentionCount,
|
|
@@ -980,9 +1054,12 @@ export function PrototypeInboxView({
|
|
|
980
1054
|
hideApproveButton,
|
|
981
1055
|
signalBriefCopy,
|
|
982
1056
|
briefStyleVariant,
|
|
1057
|
+
sectionLayout,
|
|
983
1058
|
renderDetailExtra,
|
|
984
1059
|
renderBeforeScore,
|
|
985
1060
|
renderAfterScore,
|
|
1061
|
+
renderPrimaryAction,
|
|
1062
|
+
renderCommentArea,
|
|
986
1063
|
lastActivityTime,
|
|
987
1064
|
timelineSystemEventsConfig,
|
|
988
1065
|
attentionCount,
|