@handled-ai/design-system 0.19.2 → 0.20.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/components/score-why-chips.d.ts +1 -1
- package/dist/components/signal-priority-popover.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- 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 +16 -3
- package/dist/prototype/prototype-inbox-view.js +143 -113
- 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-CZitE9xq.d.ts → signal-priority-popover-BJHd07dU.d.ts} +11 -0
- package/package.json +1 -1
- package/src/components/__tests__/conversation-panel.test.tsx +1 -1
- package/src/prototype/__tests__/detail-view-case-panel-v2.test.tsx +181 -0
- package/src/prototype/__tests__/detail-view-metadata-layout.test.tsx +97 -0
- package/src/prototype/__tests__/detail-view-title-slots.test.tsx +30 -2
- package/src/prototype/prototype-config.ts +11 -0
- package/src/prototype/prototype-inbox-view.tsx +205 -118
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { cleanup, render, screen } from "@testing-library/react"
|
|
2
|
+
import { afterEach, describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import { DetailView } from "../prototype-inbox-view"
|
|
5
|
+
import type { DetailViewProps } from "../prototype-inbox-view"
|
|
6
|
+
import type { SignalScoreData } from "../prototype-config"
|
|
7
|
+
|
|
8
|
+
const baseItem = {
|
|
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
|
+
afterEach(() => {
|
|
19
|
+
cleanup()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
function renderDetailView(
|
|
23
|
+
overrides: Partial<DetailViewProps> = {},
|
|
24
|
+
scoreOverrides: Partial<SignalScoreData> = {},
|
|
25
|
+
) {
|
|
26
|
+
const props: DetailViewProps = {
|
|
27
|
+
item: baseItem,
|
|
28
|
+
sections: { signalBrief: true, suggestedActions: false, timeline: false },
|
|
29
|
+
getSignalScore: () => ({
|
|
30
|
+
score: 72,
|
|
31
|
+
factors: [],
|
|
32
|
+
whyNow: "Why now",
|
|
33
|
+
evidence: [],
|
|
34
|
+
confidence: 80,
|
|
35
|
+
urgencyLabel: "High",
|
|
36
|
+
urgencyExplanation: "High priority",
|
|
37
|
+
signalBrief: "BRIEF BODY TEXT",
|
|
38
|
+
timeChipLabel: "3 days left",
|
|
39
|
+
...scoreOverrides,
|
|
40
|
+
}),
|
|
41
|
+
buildSuggestedActions: () => [],
|
|
42
|
+
buildSourceItems: () => [],
|
|
43
|
+
accountContacts: [],
|
|
44
|
+
emailSignature: "",
|
|
45
|
+
iconMap: {},
|
|
46
|
+
renderMetadataExtra: () => <span data-testid="metadata-extra">Owner: Lee</span>,
|
|
47
|
+
...overrides,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return render(<DetailView {...props} />)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const PRECEDES = Node.DOCUMENT_POSITION_PRECEDING
|
|
54
|
+
const FOLLOWS = Node.DOCUMENT_POSITION_FOLLOWING
|
|
55
|
+
|
|
56
|
+
describe("DetailView metadataLayout", () => {
|
|
57
|
+
it("renders the metadata chips above the brief by default", () => {
|
|
58
|
+
renderDetailView()
|
|
59
|
+
|
|
60
|
+
const brief = screen.getByText("BRIEF BODY TEXT")
|
|
61
|
+
const meta = screen.getByTestId("metadata-extra")
|
|
62
|
+
// metadata-extra comes before the brief body in document order
|
|
63
|
+
expect(brief.compareDocumentPosition(meta) & PRECEDES).toBeTruthy()
|
|
64
|
+
expect(screen.getByTestId("priority-popover-trigger")).toBeTruthy()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it("relocates the metadata chips below the brief when metadataLayout='below-brief'", () => {
|
|
68
|
+
renderDetailView({ metadataLayout: "below-brief" })
|
|
69
|
+
|
|
70
|
+
const brief = screen.getByText("BRIEF BODY TEXT")
|
|
71
|
+
const meta = screen.getByTestId("metadata-extra")
|
|
72
|
+
// metadata-extra now follows the brief body
|
|
73
|
+
expect(brief.compareDocumentPosition(meta) & FOLLOWS).toBeTruthy()
|
|
74
|
+
// chips themselves (priority + account + deadline) render exactly once
|
|
75
|
+
expect(screen.getAllByTestId("priority-popover-trigger")).toHaveLength(1)
|
|
76
|
+
expect(screen.getByText("3 days left")).toBeTruthy()
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe("DetailView deadline chip tone", () => {
|
|
81
|
+
it("applies the red tone when timeChipTone='red'", () => {
|
|
82
|
+
renderDetailView({}, { timeChipTone: "red" })
|
|
83
|
+
expect(screen.getByText("3 days left").className).toContain("text-red-700")
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it("applies the amber tone when timeChipTone='amber'", () => {
|
|
87
|
+
renderDetailView({}, { timeChipTone: "amber" })
|
|
88
|
+
expect(screen.getByText("3 days left").className).toContain("text-amber-700")
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("uses a neutral tone when timeChipTone is omitted", () => {
|
|
92
|
+
renderDetailView()
|
|
93
|
+
const chip = screen.getByText("3 days left")
|
|
94
|
+
expect(chip.className).not.toContain("text-red-700")
|
|
95
|
+
expect(chip.className).not.toContain("text-amber-700")
|
|
96
|
+
})
|
|
97
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { render, screen } from "@testing-library/react"
|
|
2
|
-
import { describe, expect, it, vi } from "vitest"
|
|
1
|
+
import { cleanup, render, screen } from "@testing-library/react"
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest"
|
|
3
3
|
|
|
4
4
|
import { DetailView } from "../prototype-inbox-view"
|
|
5
5
|
import type { DetailViewProps } from "../prototype-inbox-view"
|
|
@@ -14,6 +14,10 @@ const baseItem = {
|
|
|
14
14
|
tag1: "Signal",
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
cleanup()
|
|
19
|
+
})
|
|
20
|
+
|
|
17
21
|
function renderDetailView(overrides: Partial<DetailViewProps> = {}) {
|
|
18
22
|
const props: DetailViewProps = {
|
|
19
23
|
item: baseItem,
|
|
@@ -39,6 +43,30 @@ function renderDetailView(overrides: Partial<DetailViewProps> = {}) {
|
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
describe("DetailView title slots", () => {
|
|
46
|
+
it("does not render the inert detail Back button while preserving title and metadata slots", () => {
|
|
47
|
+
renderDetailView({
|
|
48
|
+
renderTitleActionRow: () => (
|
|
49
|
+
<button type="button">Full-width quick action</button>
|
|
50
|
+
),
|
|
51
|
+
renderMetadataExtra: () => <span data-testid="metadata-extra">Owner: Lee</span>,
|
|
52
|
+
accountDetailsButtonLabel: (item) => (
|
|
53
|
+
<>
|
|
54
|
+
<span>View account details</span>
|
|
55
|
+
<span>{item.company}</span>
|
|
56
|
+
</>
|
|
57
|
+
),
|
|
58
|
+
getAccountDetailsButtonAriaLabel: (item) => `View account details for ${item.company}`,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
expect(screen.queryByRole("button", { name: "Back" })).toBeNull()
|
|
62
|
+
expect(screen.getByRole("heading", { name: "Churn risk case title" })).toBeTruthy()
|
|
63
|
+
expect(screen.getAllByText("Acme Corp").length).toBeGreaterThan(0)
|
|
64
|
+
expect(screen.getByRole("button", { name: "Full-width quick action" })).toBeTruthy()
|
|
65
|
+
expect(screen.getByTestId("metadata-extra").textContent).toBe("Owner: Lee")
|
|
66
|
+
expect(screen.getByTestId("priority-popover-trigger")).toBeTruthy()
|
|
67
|
+
expect(screen.getByRole("button", { name: "View account details for Acme Corp" })).toBeTruthy()
|
|
68
|
+
})
|
|
69
|
+
|
|
42
70
|
it("renders supporting title text below the main title", () => {
|
|
43
71
|
renderDetailView({
|
|
44
72
|
renderTitleSubtext: (item) => <p data-testid="title-subtext">Full title: {item.title}</p>,
|
|
@@ -142,6 +142,11 @@ export interface SignalScoreData {
|
|
|
142
142
|
timeChipLabel?: string
|
|
143
143
|
/** Expanded detail for time chip popover (e.g., "Day 2 of 14 · Event detected on May 1"). */
|
|
144
144
|
timeChipDetail?: string
|
|
145
|
+
/**
|
|
146
|
+
* Visual urgency tone for the deadline chip. The consumer computes this from the live deadline
|
|
147
|
+
* (e.g. amber when a few days remain, red when due/overdue). Defaults to neutral when omitted.
|
|
148
|
+
*/
|
|
149
|
+
timeChipTone?: "neutral" | "amber" | "red"
|
|
145
150
|
}
|
|
146
151
|
|
|
147
152
|
// ---------------------------------------------------------------------------
|
|
@@ -219,12 +224,18 @@ export interface InboxViewConfig {
|
|
|
219
224
|
* - "prominent": standard foreground color, text-base size for the brief
|
|
220
225
|
*/
|
|
221
226
|
briefStyleVariant?: BriefStyleVariant
|
|
227
|
+
/** Opt-in detail panel section ordering. Defaults to the existing layout. */
|
|
228
|
+
sectionLayout?: "default" | "case-panel-v2"
|
|
222
229
|
/** Render extra content at the end of the detail view, below the suggested actions section. */
|
|
223
230
|
renderDetailExtra?: (item: QueueItem) => React.ReactNode
|
|
224
231
|
/** Render content between the signal brief text and the signal score bar (e.g. "Signals on Case" chips). */
|
|
225
232
|
renderBeforeScore?: (item: QueueItem) => React.ReactNode
|
|
226
233
|
/** Render content between the signal score section and the activity timeline (e.g. OpportunityPanel). */
|
|
227
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
|
|
228
239
|
/** Formatted string for "Last activity X ago" in the collapsed timeline header. If omitted, falls back to the first event's time. */
|
|
229
240
|
lastActivityTime?: string
|
|
230
241
|
/** Configuration for the system-noise events toggle (score changes, etc.). */
|
|
@@ -155,11 +155,17 @@ export interface DetailViewProps {
|
|
|
155
155
|
hideApproveButton?: boolean
|
|
156
156
|
signalBriefCopy?: InboxViewConfig["signalBriefCopy"]
|
|
157
157
|
briefStyleVariant?: BriefStyleVariant
|
|
158
|
+
/** Opt-in detail panel section ordering. Defaults to the existing layout. */
|
|
159
|
+
sectionLayout?: InboxViewConfig["sectionLayout"]
|
|
158
160
|
renderDetailExtra?: (item: QueueItem) => React.ReactNode
|
|
159
161
|
/** Render content between the signal brief text and the signal score bar (e.g. "Signals on Case" chips). */
|
|
160
162
|
renderBeforeScore?: (item: QueueItem) => React.ReactNode
|
|
161
163
|
/** Render content between the signal score section and the activity timeline. */
|
|
162
164
|
renderAfterScore?: (item: QueueItem) => React.ReactNode
|
|
165
|
+
/** Render primary case-panel content between the opportunity section and comment area. */
|
|
166
|
+
renderPrimaryAction?: (item: QueueItem) => React.ReactNode
|
|
167
|
+
/** Render case-panel comment content before the activity timeline. */
|
|
168
|
+
renderCommentArea?: (item: QueueItem) => React.ReactNode
|
|
163
169
|
lastActivityTime?: string
|
|
164
170
|
/** Render extra content inline with the detail title. */
|
|
165
171
|
renderTitleExtra?: (item: QueueItem) => React.ReactNode
|
|
@@ -169,6 +175,13 @@ export interface DetailViewProps {
|
|
|
169
175
|
renderTitleSubtext?: (item: QueueItem) => React.ReactNode
|
|
170
176
|
/** Render extra metadata chips (e.g. assignee) inside the chips row below the title. */
|
|
171
177
|
renderMetadataExtra?: (item: QueueItem) => React.ReactNode
|
|
178
|
+
/**
|
|
179
|
+
* Where the metadata chips row (priority · deadline · account · renderMetadataExtra) sits.
|
|
180
|
+
* - "default": above the signal brief (legacy layout, unchanged for existing consumers).
|
|
181
|
+
* - "below-brief": moved beneath the brief text, just before `renderBeforeScore`
|
|
182
|
+
* (case-panel redesign — chips + owners read as one block under the brief).
|
|
183
|
+
*/
|
|
184
|
+
metadataLayout?: "default" | "below-brief"
|
|
172
185
|
/** Override the built-in account details metadata button label. */
|
|
173
186
|
accountDetailsButtonLabel?: (item: QueueItem) => React.ReactNode
|
|
174
187
|
/** Accessible label for the built-in account details metadata button. */
|
|
@@ -344,14 +357,18 @@ export function DetailView({
|
|
|
344
357
|
hideApproveButton,
|
|
345
358
|
signalBriefCopy,
|
|
346
359
|
briefStyleVariant = "default",
|
|
360
|
+
sectionLayout = "default",
|
|
347
361
|
renderDetailExtra,
|
|
348
362
|
renderBeforeScore,
|
|
349
363
|
renderAfterScore,
|
|
364
|
+
renderPrimaryAction,
|
|
365
|
+
renderCommentArea,
|
|
350
366
|
lastActivityTime,
|
|
351
367
|
renderTitleExtra,
|
|
352
368
|
renderTitleActionRow,
|
|
353
369
|
renderTitleSubtext,
|
|
354
370
|
renderMetadataExtra,
|
|
371
|
+
metadataLayout = "default",
|
|
355
372
|
accountDetailsButtonLabel,
|
|
356
373
|
getAccountDetailsButtonAriaLabel,
|
|
357
374
|
onOpenSignalBucket,
|
|
@@ -465,6 +482,121 @@ export function DetailView({
|
|
|
465
482
|
[suggestedActions],
|
|
466
483
|
)
|
|
467
484
|
|
|
485
|
+
// Deadline chip tone (case-panel redesign): amber as the deadline nears, red when due/overdue.
|
|
486
|
+
const timeChipToneClass =
|
|
487
|
+
signalData.timeChipTone === "red"
|
|
488
|
+
? "border-red-300 bg-red-50 text-red-700 hover:bg-red-50"
|
|
489
|
+
: signalData.timeChipTone === "amber"
|
|
490
|
+
? "border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-50"
|
|
491
|
+
: "hover:bg-muted/50"
|
|
492
|
+
|
|
493
|
+
const isCasePanelV2 = sectionLayout === "case-panel-v2"
|
|
494
|
+
const v2WhyText = signalData.signalBrief
|
|
495
|
+
? signalData.whyNow || signalData.urgencyExplanation
|
|
496
|
+
: undefined
|
|
497
|
+
|
|
498
|
+
// The metadata chips row (priority · deadline · account · renderMetadataExtra). Rendered above
|
|
499
|
+
// the brief by default, or beneath it when `metadataLayout === "below-brief"` (case-panel redesign).
|
|
500
|
+
const metadataChips = (
|
|
501
|
+
<>
|
|
502
|
+
<SignalPriorityPopover
|
|
503
|
+
score={signalData.score}
|
|
504
|
+
urgencyLabel={signalData.urgencyLabel}
|
|
505
|
+
urgencyExplanation={signalData.urgencyExplanation ?? signalData.signalBrief}
|
|
506
|
+
scoreDisplay={signalData.priorityScoreDisplay}
|
|
507
|
+
formulaLabel={signalData.priorityFormulaLabel}
|
|
508
|
+
factors={signalData.priorityFactors ?? []}
|
|
509
|
+
metaText={undefined}
|
|
510
|
+
feedbackChips={signalData.priorityFeedbackChips}
|
|
511
|
+
onFeedbackSubmit={signalData.onPriorityFeedback}
|
|
512
|
+
initialFactorFeedback={signalData.initialFactorPopoverFeedback}
|
|
513
|
+
onFactorFeedback={signalData.onFactorFeedback}
|
|
514
|
+
initialPriorityFeedback={signalData.initialPriorityFeedback}
|
|
515
|
+
/>
|
|
516
|
+
{signalData.timeChipLabel && (
|
|
517
|
+
<Badge
|
|
518
|
+
variant="outline"
|
|
519
|
+
title={signalData.timeChipDetail ?? undefined}
|
|
520
|
+
className={`transition-colors ${timeChipToneClass}`}
|
|
521
|
+
>
|
|
522
|
+
{signalData.timeChipLabel}
|
|
523
|
+
</Badge>
|
|
524
|
+
)}
|
|
525
|
+
<TooltipProvider delayDuration={300}>
|
|
526
|
+
<Tooltip>
|
|
527
|
+
<TooltipTrigger asChild>
|
|
528
|
+
<button
|
|
529
|
+
type="button"
|
|
530
|
+
onClick={onOpenEntityPanel}
|
|
531
|
+
className="group/account ml-1 inline-flex max-w-full items-center gap-2 rounded-md border border-border/60 bg-background px-2 py-1 transition-colors hover:border-border hover:bg-muted/50"
|
|
532
|
+
aria-label={getAccountDetailsButtonAriaLabel?.(item) ?? `View account details for ${item.company}`}
|
|
533
|
+
>
|
|
534
|
+
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded bg-foreground text-[9px] font-semibold uppercase leading-none text-background">
|
|
535
|
+
{getCompanyInitials(item.company)}
|
|
536
|
+
</span>
|
|
537
|
+
{accountDetailsButtonLabel ? (
|
|
538
|
+
<span className="inline-flex min-w-0 items-center gap-1.5 text-xs font-medium text-foreground">
|
|
539
|
+
{accountDetailsButtonLabel(item)}
|
|
540
|
+
</span>
|
|
541
|
+
) : (
|
|
542
|
+
<span className="truncate text-xs font-medium text-foreground">{item.company}</span>
|
|
543
|
+
)}
|
|
544
|
+
<span className="flex shrink-0 items-center gap-1.5">
|
|
545
|
+
<span
|
|
546
|
+
aria-hidden
|
|
547
|
+
className={`h-1.5 w-1.5 shrink-0 rounded-full ${STATUS_DOT_CLASS[item.statusColor] ?? "bg-muted-foreground/40"}`}
|
|
548
|
+
/>
|
|
549
|
+
{/* "View account" expands in on hover via an animated grid column */}
|
|
550
|
+
<span className="-ml-1.5 grid grid-cols-[0fr] transition-all duration-200 ease-out group-hover/account:ml-0 group-hover/account:grid-cols-[1fr]">
|
|
551
|
+
<span className="overflow-hidden">
|
|
552
|
+
<span className="block whitespace-nowrap text-xs font-medium text-muted-foreground/80">
|
|
553
|
+
View account
|
|
554
|
+
</span>
|
|
555
|
+
</span>
|
|
556
|
+
</span>
|
|
557
|
+
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/50 transition-transform duration-150 group-hover/account:translate-x-0.5 group-hover/account:text-muted-foreground" />
|
|
558
|
+
</span>
|
|
559
|
+
</button>
|
|
560
|
+
</TooltipTrigger>
|
|
561
|
+
<TooltipContent>Open account panel</TooltipContent>
|
|
562
|
+
</Tooltip>
|
|
563
|
+
</TooltipProvider>
|
|
564
|
+
{renderMetadataExtra?.(item)}
|
|
565
|
+
</>
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
const timelineSection = sections.timeline && timelineEvents.length > 0 ? (
|
|
569
|
+
<TimelineSection
|
|
570
|
+
timelineEvents={timelineEvents}
|
|
571
|
+
showTimeline={showTimeline}
|
|
572
|
+
setShowTimeline={setShowTimeline}
|
|
573
|
+
showSystemEvents={showSystemEvents}
|
|
574
|
+
setShowSystemEvents={setShowSystemEvents}
|
|
575
|
+
attentionCount={attentionCount}
|
|
576
|
+
sysEvtConfig={sysEvtConfig}
|
|
577
|
+
lastActivityTime={lastActivityTime}
|
|
578
|
+
/>
|
|
579
|
+
) : null
|
|
580
|
+
|
|
581
|
+
const suggestedActionsSection = sections.suggestedActions ? (
|
|
582
|
+
<SignalApproval.Gate>
|
|
583
|
+
<SuggestedActions
|
|
584
|
+
actions={suggestedActions}
|
|
585
|
+
accountContacts={accountContacts}
|
|
586
|
+
signature={emailSignature}
|
|
587
|
+
iconMap={iconMap}
|
|
588
|
+
onDismiss={(id) => console.log("Dismiss action:", id)}
|
|
589
|
+
onSend={(id) => console.log("Send action:", id)}
|
|
590
|
+
onSaveDraft={(id) => console.log("Save draft:", id)}
|
|
591
|
+
onDuplicate={handleDuplicate}
|
|
592
|
+
onOpenAccountDetails={onOpenEntityPanel}
|
|
593
|
+
onOpenRecentActivity={onOpenRecentActivity}
|
|
594
|
+
onMarkComplete={(id) => console.log("Mark complete:", id)}
|
|
595
|
+
onDispatchAgent={(id) => console.log("Dispatch agent:", id)}
|
|
596
|
+
/>
|
|
597
|
+
</SignalApproval.Gate>
|
|
598
|
+
) : null
|
|
599
|
+
|
|
468
600
|
return (
|
|
469
601
|
<SignalApproval.Root
|
|
470
602
|
key={item.id}
|
|
@@ -488,14 +620,6 @@ export function DetailView({
|
|
|
488
620
|
<div className="pb-8">
|
|
489
621
|
{/* Header */}
|
|
490
622
|
<div className="mb-4 flex items-center gap-2">
|
|
491
|
-
<button
|
|
492
|
-
type="button"
|
|
493
|
-
className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
|
|
494
|
-
>
|
|
495
|
-
<ArrowLeft className="h-3.5 w-3.5" />
|
|
496
|
-
Back
|
|
497
|
-
</button>
|
|
498
|
-
<span className="text-muted-foreground/40">·</span>
|
|
499
623
|
<span className="text-xs text-muted-foreground">{item.company}</span>
|
|
500
624
|
</div>
|
|
501
625
|
|
|
@@ -514,71 +638,9 @@ export function DetailView({
|
|
|
514
638
|
) : null}
|
|
515
639
|
</div>
|
|
516
640
|
|
|
517
|
-
|
|
518
|
-
<
|
|
519
|
-
|
|
520
|
-
urgencyLabel={signalData.urgencyLabel}
|
|
521
|
-
urgencyExplanation={signalData.urgencyExplanation ?? signalData.signalBrief}
|
|
522
|
-
scoreDisplay={signalData.priorityScoreDisplay}
|
|
523
|
-
formulaLabel={signalData.priorityFormulaLabel}
|
|
524
|
-
factors={signalData.priorityFactors ?? []}
|
|
525
|
-
metaText={undefined}
|
|
526
|
-
feedbackChips={signalData.priorityFeedbackChips}
|
|
527
|
-
onFeedbackSubmit={signalData.onPriorityFeedback}
|
|
528
|
-
initialFactorFeedback={signalData.initialFactorPopoverFeedback}
|
|
529
|
-
onFactorFeedback={signalData.onFactorFeedback}
|
|
530
|
-
initialPriorityFeedback={signalData.initialPriorityFeedback}
|
|
531
|
-
/>
|
|
532
|
-
{signalData.timeChipLabel && (
|
|
533
|
-
<Badge
|
|
534
|
-
variant="outline"
|
|
535
|
-
title={signalData.timeChipDetail ?? undefined}
|
|
536
|
-
className="transition-colors hover:bg-muted/50"
|
|
537
|
-
>
|
|
538
|
-
{signalData.timeChipLabel}
|
|
539
|
-
</Badge>
|
|
540
|
-
)}
|
|
541
|
-
<TooltipProvider delayDuration={300}>
|
|
542
|
-
<Tooltip>
|
|
543
|
-
<TooltipTrigger asChild>
|
|
544
|
-
<button
|
|
545
|
-
type="button"
|
|
546
|
-
onClick={onOpenEntityPanel}
|
|
547
|
-
className="group/account ml-1 inline-flex max-w-full items-center gap-2 rounded-md border border-border/60 bg-background px-2 py-1 transition-colors hover:border-border hover:bg-muted/50"
|
|
548
|
-
aria-label={getAccountDetailsButtonAriaLabel?.(item) ?? `View account details for ${item.company}`}
|
|
549
|
-
>
|
|
550
|
-
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded bg-foreground text-[9px] font-semibold uppercase leading-none text-background">
|
|
551
|
-
{getCompanyInitials(item.company)}
|
|
552
|
-
</span>
|
|
553
|
-
{accountDetailsButtonLabel ? (
|
|
554
|
-
<span className="inline-flex min-w-0 items-center gap-1.5 text-xs font-medium text-foreground">
|
|
555
|
-
{accountDetailsButtonLabel(item)}
|
|
556
|
-
</span>
|
|
557
|
-
) : (
|
|
558
|
-
<span className="truncate text-xs font-medium text-foreground">{item.company}</span>
|
|
559
|
-
)}
|
|
560
|
-
<span className="flex shrink-0 items-center gap-1.5">
|
|
561
|
-
<span
|
|
562
|
-
aria-hidden
|
|
563
|
-
className={`h-1.5 w-1.5 shrink-0 rounded-full ${STATUS_DOT_CLASS[item.statusColor] ?? "bg-muted-foreground/40"}`}
|
|
564
|
-
/>
|
|
565
|
-
{/* "View account" expands in on hover via an animated grid column */}
|
|
566
|
-
<span className="-ml-1.5 grid grid-cols-[0fr] transition-all duration-200 ease-out group-hover/account:ml-0 group-hover/account:grid-cols-[1fr]">
|
|
567
|
-
<span className="overflow-hidden">
|
|
568
|
-
<span className="block whitespace-nowrap text-xs font-medium text-muted-foreground/80">
|
|
569
|
-
View account
|
|
570
|
-
</span>
|
|
571
|
-
</span>
|
|
572
|
-
</span>
|
|
573
|
-
<ChevronRight className="h-3 w-3 shrink-0 text-muted-foreground/50 transition-transform duration-150 group-hover/account:translate-x-0.5 group-hover/account:text-muted-foreground" />
|
|
574
|
-
</span>
|
|
575
|
-
</button>
|
|
576
|
-
</TooltipTrigger>
|
|
577
|
-
<TooltipContent>Open account panel</TooltipContent>
|
|
578
|
-
</Tooltip>
|
|
579
|
-
</TooltipProvider>
|
|
580
|
-
{renderMetadataExtra?.(item)}
|
|
581
|
-
</div>
|
|
641
|
+
{!isCasePanelV2 && metadataLayout === "default" ? (
|
|
642
|
+
<div className="mb-6 flex flex-wrap items-center gap-2">{metadataChips}</div>
|
|
643
|
+
) : null}
|
|
582
644
|
|
|
583
645
|
{/* Signal Brief */}
|
|
584
646
|
{sections.signalBrief && (() => {
|
|
@@ -620,59 +682,78 @@ export function DetailView({
|
|
|
620
682
|
</p>
|
|
621
683
|
)}
|
|
622
684
|
|
|
623
|
-
{/*
|
|
624
|
-
{
|
|
685
|
+
{/* Metadata chips relocated beneath the brief (case-panel redesign). */}
|
|
686
|
+
{isCasePanelV2 || metadataLayout === "below-brief" ? (
|
|
687
|
+
<div className="mb-4 flex flex-wrap items-center gap-2">{metadataChips}</div>
|
|
688
|
+
) : null}
|
|
625
689
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
690
|
+
{!isCasePanelV2 ? (
|
|
691
|
+
<>
|
|
692
|
+
{/* Before-score content slot (e.g. "Signals on Case" chips) */}
|
|
693
|
+
{renderBeforeScore?.(item)}
|
|
694
|
+
|
|
695
|
+
<ScoreWhyChips
|
|
696
|
+
item={item}
|
|
697
|
+
signalData={signalData}
|
|
698
|
+
onOpenSignalBucket={onOpenSignalBucket}
|
|
699
|
+
/>
|
|
700
|
+
<div className="mt-4">
|
|
701
|
+
<SignalApproval.Actions />
|
|
702
|
+
</div>
|
|
703
|
+
</>
|
|
704
|
+
) : null}
|
|
634
705
|
</div>
|
|
635
706
|
)
|
|
636
707
|
})()}
|
|
637
708
|
|
|
638
|
-
{
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
709
|
+
{isCasePanelV2 ? (
|
|
710
|
+
<>
|
|
711
|
+
{sections.signalBrief ? (
|
|
712
|
+
<>
|
|
713
|
+
{/* Before-score content slot (e.g. status/attention pills) */}
|
|
714
|
+
{renderBeforeScore?.(item)}
|
|
715
|
+
|
|
716
|
+
{v2WhyText ? (
|
|
717
|
+
<div className="mb-8">
|
|
718
|
+
<h3 className="text-xs font-bold text-muted-foreground uppercase tracking-wider mb-3">The why</h3>
|
|
719
|
+
<p className="text-sm text-foreground/90 leading-relaxed mb-4">
|
|
720
|
+
{v2WhyText}
|
|
721
|
+
</p>
|
|
722
|
+
</div>
|
|
723
|
+
) : null}
|
|
724
|
+
|
|
725
|
+
<div className="mb-8">
|
|
726
|
+
<ScoreWhyChips
|
|
727
|
+
item={item}
|
|
728
|
+
signalData={signalData}
|
|
729
|
+
onOpenSignalBucket={onOpenSignalBucket}
|
|
730
|
+
/>
|
|
731
|
+
<div className="mt-4">
|
|
732
|
+
<SignalApproval.Actions />
|
|
733
|
+
</div>
|
|
734
|
+
</div>
|
|
735
|
+
</>
|
|
736
|
+
) : null}
|
|
737
|
+
|
|
738
|
+
{/* After-score content slot (e.g. OpportunityPanel) */}
|
|
739
|
+
{renderAfterScore?.(item)}
|
|
740
|
+
{renderPrimaryAction?.(item)}
|
|
741
|
+
{renderCommentArea?.(item)}
|
|
742
|
+
{timelineSection}
|
|
743
|
+
</>
|
|
744
|
+
) : (
|
|
745
|
+
<>
|
|
746
|
+
{/* After-score content slot (e.g. OpportunityPanel) */}
|
|
747
|
+
{renderAfterScore?.(item)}
|
|
748
|
+
|
|
749
|
+
{/* Activity Timeline */}
|
|
750
|
+
{timelineSection}
|
|
751
|
+
</>
|
|
653
752
|
)}
|
|
654
753
|
</div>
|
|
655
754
|
|
|
656
|
-
{
|
|
657
|
-
{
|
|
658
|
-
<SignalApproval.Gate>
|
|
659
|
-
<SuggestedActions
|
|
660
|
-
actions={suggestedActions}
|
|
661
|
-
accountContacts={accountContacts}
|
|
662
|
-
signature={emailSignature}
|
|
663
|
-
iconMap={iconMap}
|
|
664
|
-
onDismiss={(id) => console.log("Dismiss action:", id)}
|
|
665
|
-
onSend={(id) => console.log("Send action:", id)}
|
|
666
|
-
onSaveDraft={(id) => console.log("Save draft:", id)}
|
|
667
|
-
onDuplicate={handleDuplicate}
|
|
668
|
-
onOpenAccountDetails={onOpenEntityPanel}
|
|
669
|
-
onOpenRecentActivity={onOpenRecentActivity}
|
|
670
|
-
onMarkComplete={(id) => console.log("Mark complete:", id)}
|
|
671
|
-
onDispatchAgent={(id) => console.log("Dispatch agent:", id)}
|
|
672
|
-
/>
|
|
673
|
-
</SignalApproval.Gate>
|
|
674
|
-
)}
|
|
675
|
-
{renderDetailExtra?.(item)}
|
|
755
|
+
{!isCasePanelV2 ? suggestedActionsSection : null}
|
|
756
|
+
{!isCasePanelV2 ? renderDetailExtra?.(item) : null}
|
|
676
757
|
</div>
|
|
677
758
|
</SignalApproval.Root>
|
|
678
759
|
)
|
|
@@ -714,9 +795,12 @@ export function PrototypeInboxView({
|
|
|
714
795
|
hideApproveButton,
|
|
715
796
|
signalBriefCopy,
|
|
716
797
|
briefStyleVariant,
|
|
798
|
+
sectionLayout,
|
|
717
799
|
renderDetailExtra,
|
|
718
800
|
renderBeforeScore,
|
|
719
801
|
renderAfterScore,
|
|
802
|
+
renderPrimaryAction,
|
|
803
|
+
renderCommentArea,
|
|
720
804
|
lastActivityTime,
|
|
721
805
|
timelineSystemEventsConfig,
|
|
722
806
|
attentionCount,
|
|
@@ -959,9 +1043,12 @@ export function PrototypeInboxView({
|
|
|
959
1043
|
hideApproveButton,
|
|
960
1044
|
signalBriefCopy,
|
|
961
1045
|
briefStyleVariant,
|
|
1046
|
+
sectionLayout,
|
|
962
1047
|
renderDetailExtra,
|
|
963
1048
|
renderBeforeScore,
|
|
964
1049
|
renderAfterScore,
|
|
1050
|
+
renderPrimaryAction,
|
|
1051
|
+
renderCommentArea,
|
|
965
1052
|
lastActivityTime,
|
|
966
1053
|
timelineSystemEventsConfig,
|
|
967
1054
|
attentionCount,
|