@handled-ai/design-system 0.18.3 → 0.18.4
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/chart.d.ts +1 -1
- package/dist/components/feedback-primitives.d.ts +41 -2
- package/dist/components/feedback-primitives.js +241 -6
- package/dist/components/feedback-primitives.js.map +1 -1
- package/dist/components/score-why-chips.d.ts +1 -1
- package/dist/components/score-why-chips.js +26 -5
- package/dist/components/score-why-chips.js.map +1 -1
- package/dist/components/signal-priority-popover.d.ts +1 -1
- package/dist/components/signal-priority-popover.js +32 -6
- package/dist/components/signal-priority-popover.js.map +1 -1
- package/dist/components/timeline-activity.d.ts +1 -16
- package/dist/components/timeline-activity.js +1 -69
- package/dist/components/timeline-activity.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/index.js.map +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 +2 -12
- package/dist/prototype/prototype-inbox-view.js +37 -102
- 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-DQ_VuHac.d.ts → signal-priority-popover-DWaAMhPI.d.ts} +26 -2
- package/package.json +3 -1
- package/src/components/__tests__/wit-636-feedback-states.test.tsx +546 -0
- package/src/components/feedback-primitives.tsx +333 -26
- package/src/components/score-why-chips.tsx +28 -2
- package/src/components/signal-priority-popover.tsx +44 -4
- package/src/components/timeline-activity.tsx +1 -112
- package/src/index.ts +2 -2
- package/src/prototype/__tests__/detail-view-attention.test.tsx +2 -2
- package/src/prototype/prototype-config.ts +11 -1
- package/src/prototype/prototype-inbox-view.tsx +33 -131
- package/src/components/__tests__/timeline-activity.test.tsx +0 -137
- package/src/prototype/__tests__/detail-view-timeline-system-events.test.tsx +0 -322
package/dist/charts/chart.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ declare function ChartTooltipContent({ active, payload, className, indicator, hi
|
|
|
48
48
|
labelClassName?: string;
|
|
49
49
|
color?: string;
|
|
50
50
|
}): React.JSX.Element | null;
|
|
51
|
-
declare const ChartLegend:
|
|
51
|
+
declare const ChartLegend: React.MemoExoticComponent<(outsideProps: RechartsPrimitive.LegendProps) => React.ReactPortal | null>;
|
|
52
52
|
type LegendPayloadItem = {
|
|
53
53
|
value?: string;
|
|
54
54
|
dataKey?: string;
|
|
@@ -16,6 +16,18 @@ interface FeedbackSubmitData {
|
|
|
16
16
|
pills: string[];
|
|
17
17
|
detail: string;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Persisted feedback data from a previous submission, used to hydrate the
|
|
21
|
+
* footer into its "already submitted" visual state.
|
|
22
|
+
*/
|
|
23
|
+
interface PersistedFeedbackData {
|
|
24
|
+
sentiment: "positive" | "negative";
|
|
25
|
+
reasonTop?: string;
|
|
26
|
+
reasonSub?: string;
|
|
27
|
+
pills?: string[];
|
|
28
|
+
detail?: string;
|
|
29
|
+
ownershipLabel: "Your feedback" | "Team feedback";
|
|
30
|
+
}
|
|
19
31
|
/**
|
|
20
32
|
* Defines a tier-1 chip that may have tier-2 sub-chips.
|
|
21
33
|
*/
|
|
@@ -60,7 +72,34 @@ interface FeedbackFooterProps {
|
|
|
60
72
|
negativeChips?: FeedbackChipTree[];
|
|
61
73
|
positiveChips?: string[];
|
|
62
74
|
className?: string;
|
|
75
|
+
/** Pre-existing feedback to hydrate from (e.g. after page reload). */
|
|
76
|
+
initialFeedback?: PersistedFeedbackData | null;
|
|
77
|
+
/** Label shown in the transient confirmation pill after submit. */
|
|
78
|
+
submittedLabel?: string;
|
|
79
|
+
/** Stable key for syncing initialFeedback into local state. When this
|
|
80
|
+
* changes, the component resets to the new initialFeedback value. */
|
|
81
|
+
feedbackKey?: string;
|
|
82
|
+
}
|
|
83
|
+
declare function FeedbackFooter({ feedback, onFeedbackChange, onSubmit, metaText, positivePrompt, negativePrompt, negativeChips, positiveChips, className, initialFeedback, submittedLabel, feedbackKey, }: FeedbackFooterProps): React.JSX.Element;
|
|
84
|
+
interface InlineFeedbackControlProps {
|
|
85
|
+
/** Unique key identifying the feedback target (e.g. factor key). */
|
|
86
|
+
feedbackKey: string;
|
|
87
|
+
/** Persisted/initial feedback to hydrate from. */
|
|
88
|
+
initialFeedback?: {
|
|
89
|
+
type: "up" | "down";
|
|
90
|
+
detail: string;
|
|
91
|
+
ownershipLabel?: string;
|
|
92
|
+
};
|
|
93
|
+
/** Called when user submits or clears feedback. */
|
|
94
|
+
onFeedback?: (key: string, type: "up" | "down" | null, detail?: string) => void;
|
|
95
|
+
/** Test ID prefix for all sub-elements. */
|
|
96
|
+
testIdPrefix?: string;
|
|
63
97
|
}
|
|
64
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Compact inline thumb-up/thumb-down feedback with optional detail text.
|
|
100
|
+
* Used by PriorityFactorRow and any other component that needs
|
|
101
|
+
* a lightweight feedback control.
|
|
102
|
+
*/
|
|
103
|
+
declare function InlineFeedbackControl({ feedbackKey, initialFeedback, onFeedback, testIdPrefix, }: InlineFeedbackControlProps): React.JSX.Element;
|
|
65
104
|
|
|
66
|
-
export { FeedbackActions, type FeedbackActionsProps, FeedbackChipGroup, type FeedbackChipGroupProps, type FeedbackChipTree, FeedbackFooter, type FeedbackFooterProps, FeedbackInput, type FeedbackInputProps, type FeedbackSubmitData };
|
|
105
|
+
export { FeedbackActions, type FeedbackActionsProps, FeedbackChipGroup, type FeedbackChipGroupProps, type FeedbackChipTree, FeedbackFooter, type FeedbackFooterProps, FeedbackInput, type FeedbackInputProps, type FeedbackSubmitData, InlineFeedbackControl, type InlineFeedbackControlProps, type PersistedFeedbackData };
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"use client";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import * as React from "react";
|
|
6
|
-
import { ThumbsUp, ThumbsDown } from "lucide-react";
|
|
6
|
+
import { ThumbsUp, ThumbsDown, Check, Pencil } from "lucide-react";
|
|
7
7
|
import { cn } from "../lib/utils.js";
|
|
8
8
|
const CHIP_SELECTED_CLASSES = {
|
|
9
9
|
negative: "bg-red-50 text-red-700 border-red-200",
|
|
@@ -107,7 +107,10 @@ function FeedbackFooter({
|
|
|
107
107
|
negativePrompt = "What's the issue?",
|
|
108
108
|
negativeChips = [],
|
|
109
109
|
positiveChips = [],
|
|
110
|
-
className
|
|
110
|
+
className,
|
|
111
|
+
initialFeedback,
|
|
112
|
+
submittedLabel = "Saved",
|
|
113
|
+
feedbackKey
|
|
111
114
|
}) {
|
|
112
115
|
const [expanded, setExpanded] = React.useState(false);
|
|
113
116
|
const [selectedTier1, setSelectedTier1] = React.useState(null);
|
|
@@ -117,6 +120,35 @@ function FeedbackFooter({
|
|
|
117
120
|
const [activeTreeIndex, setActiveTreeIndex] = React.useState(
|
|
118
121
|
null
|
|
119
122
|
);
|
|
123
|
+
const [submitted, setSubmitted] = React.useState(false);
|
|
124
|
+
const [persisted, setPersisted] = React.useState(
|
|
125
|
+
initialFeedback != null ? initialFeedback : null
|
|
126
|
+
);
|
|
127
|
+
const isEditingRef = React.useRef(false);
|
|
128
|
+
const lastKeyRef = React.useRef(feedbackKey);
|
|
129
|
+
const setIsEditing = React.useCallback((value) => {
|
|
130
|
+
isEditingRef.current = value;
|
|
131
|
+
}, []);
|
|
132
|
+
React.useEffect(() => {
|
|
133
|
+
const keyChanged = feedbackKey !== lastKeyRef.current;
|
|
134
|
+
lastKeyRef.current = feedbackKey;
|
|
135
|
+
if (keyChanged) {
|
|
136
|
+
setPersisted(initialFeedback != null ? initialFeedback : null);
|
|
137
|
+
setSubmitted(false);
|
|
138
|
+
setExpanded(false);
|
|
139
|
+
isEditingRef.current = false;
|
|
140
|
+
if (initialFeedback) {
|
|
141
|
+
onFeedbackChange(initialFeedback.sentiment);
|
|
142
|
+
} else {
|
|
143
|
+
onFeedbackChange(null);
|
|
144
|
+
}
|
|
145
|
+
} else if (!isEditingRef.current) {
|
|
146
|
+
setPersisted(initialFeedback != null ? initialFeedback : null);
|
|
147
|
+
if (initialFeedback) {
|
|
148
|
+
onFeedbackChange(initialFeedback.sentiment);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}, [initialFeedback, feedbackKey, onFeedbackChange]);
|
|
120
152
|
const resetState = React.useCallback(() => {
|
|
121
153
|
setExpanded(false);
|
|
122
154
|
setSelectedTier1(null);
|
|
@@ -124,15 +156,31 @@ function FeedbackFooter({
|
|
|
124
156
|
setAdditionalPills([]);
|
|
125
157
|
setDetailText("");
|
|
126
158
|
setActiveTreeIndex(null);
|
|
127
|
-
|
|
159
|
+
setIsEditing(false);
|
|
160
|
+
}, [setIsEditing]);
|
|
128
161
|
const handleSentimentClick = React.useCallback(
|
|
129
162
|
(sentiment) => {
|
|
130
163
|
onFeedbackChange(sentiment);
|
|
131
164
|
resetState();
|
|
132
165
|
setExpanded(true);
|
|
166
|
+
setSubmitted(false);
|
|
167
|
+
setPersisted(null);
|
|
168
|
+
setIsEditing(true);
|
|
133
169
|
},
|
|
134
|
-
[onFeedbackChange, resetState]
|
|
170
|
+
[onFeedbackChange, resetState, setIsEditing]
|
|
135
171
|
);
|
|
172
|
+
const handlePersistedClick = React.useCallback(() => {
|
|
173
|
+
var _a, _b, _c, _d;
|
|
174
|
+
if (!persisted) return;
|
|
175
|
+
onFeedbackChange(persisted.sentiment);
|
|
176
|
+
setSelectedTier1((_a = persisted.reasonTop) != null ? _a : null);
|
|
177
|
+
setSelectedTier2((_b = persisted.reasonSub) != null ? _b : null);
|
|
178
|
+
setAdditionalPills((_c = persisted.pills) != null ? _c : []);
|
|
179
|
+
setDetailText((_d = persisted.detail) != null ? _d : "");
|
|
180
|
+
setExpanded(true);
|
|
181
|
+
setSubmitted(false);
|
|
182
|
+
setIsEditing(true);
|
|
183
|
+
}, [persisted, onFeedbackChange, setIsEditing]);
|
|
136
184
|
const handleTier1Toggle = React.useCallback(
|
|
137
185
|
(chipLabel) => {
|
|
138
186
|
if (selectedTier1 === chipLabel) {
|
|
@@ -182,6 +230,7 @@ function FeedbackFooter({
|
|
|
182
230
|
pills: additionalPills,
|
|
183
231
|
detail: detailText
|
|
184
232
|
});
|
|
233
|
+
setSubmitted(true);
|
|
185
234
|
resetState();
|
|
186
235
|
}, [
|
|
187
236
|
feedback,
|
|
@@ -203,9 +252,30 @@ function FeedbackFooter({
|
|
|
203
252
|
return result;
|
|
204
253
|
}, [selectedTier1, additionalPills]);
|
|
205
254
|
const activeTree = activeTreeIndex !== null ? negativeChips[activeTreeIndex] : null;
|
|
255
|
+
const showPersistedIndicator = persisted && !expanded && !submitted;
|
|
206
256
|
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
|
|
207
257
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
208
|
-
|
|
258
|
+
showPersistedIndicator ? (
|
|
259
|
+
/* Persisted feedback indicator — clickable to reopen editor */
|
|
260
|
+
/* @__PURE__ */ jsxs(
|
|
261
|
+
"button",
|
|
262
|
+
{
|
|
263
|
+
type: "button",
|
|
264
|
+
onClick: handlePersistedClick,
|
|
265
|
+
className: "group flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors",
|
|
266
|
+
"data-testid": "persisted-feedback-indicator",
|
|
267
|
+
children: [
|
|
268
|
+
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
269
|
+
persisted.ownershipLabel,
|
|
270
|
+
":"
|
|
271
|
+
] }),
|
|
272
|
+
persisted.sentiment === "positive" ? /* @__PURE__ */ jsx(ThumbsUp, { className: "h-[11px] w-[11px]" }) : /* @__PURE__ */ jsx(ThumbsDown, { className: "h-[11px] w-[11px]" }),
|
|
273
|
+
persisted.detail && /* @__PURE__ */ jsx("span", { className: "max-w-[200px] truncate text-muted-foreground/70", children: persisted.detail }),
|
|
274
|
+
/* @__PURE__ */ jsx(Pencil, { className: "h-[9px] w-[9px] opacity-0 group-hover:opacity-100 transition-opacity" })
|
|
275
|
+
]
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
209
279
|
/* @__PURE__ */ jsxs(
|
|
210
280
|
"button",
|
|
211
281
|
{
|
|
@@ -235,6 +305,18 @@ function FeedbackFooter({
|
|
|
235
305
|
"Not helpful"
|
|
236
306
|
]
|
|
237
307
|
}
|
|
308
|
+
),
|
|
309
|
+
submitted && feedback && /* @__PURE__ */ jsxs(
|
|
310
|
+
"span",
|
|
311
|
+
{
|
|
312
|
+
className: "inline-flex items-center gap-1 text-[11px] font-medium text-emerald-600",
|
|
313
|
+
role: "status",
|
|
314
|
+
"data-testid": "feedback-submitted-pill",
|
|
315
|
+
children: [
|
|
316
|
+
/* @__PURE__ */ jsx(Check, { className: "h-[11px] w-[11px]" }),
|
|
317
|
+
submittedLabel
|
|
318
|
+
]
|
|
319
|
+
}
|
|
238
320
|
)
|
|
239
321
|
] }),
|
|
240
322
|
metaText && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: metaText })
|
|
@@ -286,10 +368,163 @@ function FeedbackFooter({
|
|
|
286
368
|
] })
|
|
287
369
|
] });
|
|
288
370
|
}
|
|
371
|
+
function InlineFeedbackControl({
|
|
372
|
+
feedbackKey,
|
|
373
|
+
initialFeedback,
|
|
374
|
+
onFeedback,
|
|
375
|
+
testIdPrefix = "inline-feedback"
|
|
376
|
+
}) {
|
|
377
|
+
var _a, _b, _c, _d;
|
|
378
|
+
const [thumbState, setThumbState] = React.useState(
|
|
379
|
+
(_a = initialFeedback == null ? void 0 : initialFeedback.type) != null ? _a : null
|
|
380
|
+
);
|
|
381
|
+
const [showInput, setShowInput] = React.useState(false);
|
|
382
|
+
const [detailText, setDetailText] = React.useState((_b = initialFeedback == null ? void 0 : initialFeedback.detail) != null ? _b : "");
|
|
383
|
+
const [saved, setSaved] = React.useState(!!initialFeedback);
|
|
384
|
+
const [savedDetail, setSavedDetail] = React.useState((_c = initialFeedback == null ? void 0 : initialFeedback.detail) != null ? _c : "");
|
|
385
|
+
const ownershipLabel = (_d = initialFeedback == null ? void 0 : initialFeedback.ownershipLabel) != null ? _d : "Your feedback";
|
|
386
|
+
React.useEffect(() => {
|
|
387
|
+
if (initialFeedback) {
|
|
388
|
+
setThumbState(initialFeedback.type);
|
|
389
|
+
setSaved(true);
|
|
390
|
+
setSavedDetail(initialFeedback.detail);
|
|
391
|
+
}
|
|
392
|
+
}, [initialFeedback]);
|
|
393
|
+
const handleThumbClick = React.useCallback(
|
|
394
|
+
(type) => {
|
|
395
|
+
if (thumbState === type) {
|
|
396
|
+
setThumbState(null);
|
|
397
|
+
setShowInput(false);
|
|
398
|
+
setSaved(false);
|
|
399
|
+
onFeedback == null ? void 0 : onFeedback(feedbackKey, null);
|
|
400
|
+
} else {
|
|
401
|
+
setThumbState(type);
|
|
402
|
+
setShowInput(true);
|
|
403
|
+
setSaved(false);
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
[thumbState, feedbackKey, onFeedback]
|
|
407
|
+
);
|
|
408
|
+
const handleSubmitDetail = React.useCallback(() => {
|
|
409
|
+
if (!thumbState) return;
|
|
410
|
+
const text = detailText.trim();
|
|
411
|
+
onFeedback == null ? void 0 : onFeedback(feedbackKey, thumbState, text);
|
|
412
|
+
setSaved(true);
|
|
413
|
+
setSavedDetail(text);
|
|
414
|
+
setShowInput(false);
|
|
415
|
+
}, [thumbState, detailText, feedbackKey, onFeedback]);
|
|
416
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
417
|
+
saved && !showInput ? (
|
|
418
|
+
/* Persisted / saved indicator */
|
|
419
|
+
/* @__PURE__ */ jsxs(
|
|
420
|
+
"button",
|
|
421
|
+
{
|
|
422
|
+
type: "button",
|
|
423
|
+
onClick: () => {
|
|
424
|
+
setDetailText(savedDetail);
|
|
425
|
+
setShowInput(true);
|
|
426
|
+
setSaved(false);
|
|
427
|
+
},
|
|
428
|
+
className: "group flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors",
|
|
429
|
+
"data-testid": `${testIdPrefix}-feedback-persisted-${feedbackKey}`,
|
|
430
|
+
children: [
|
|
431
|
+
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
432
|
+
ownershipLabel,
|
|
433
|
+
":"
|
|
434
|
+
] }),
|
|
435
|
+
thumbState === "up" ? /* @__PURE__ */ jsx(ThumbsUp, { className: "h-[10px] w-[10px]" }) : /* @__PURE__ */ jsx(ThumbsDown, { className: "h-[10px] w-[10px]" }),
|
|
436
|
+
savedDetail && /* @__PURE__ */ jsx("span", { className: "max-w-[180px] truncate text-muted-foreground/70", children: savedDetail }),
|
|
437
|
+
/* @__PURE__ */ jsx(Pencil, { className: "h-[9px] w-[9px] opacity-0 group-hover:opacity-100 transition-opacity" })
|
|
438
|
+
]
|
|
439
|
+
}
|
|
440
|
+
)
|
|
441
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
442
|
+
/* @__PURE__ */ jsx(
|
|
443
|
+
"button",
|
|
444
|
+
{
|
|
445
|
+
type: "button",
|
|
446
|
+
onClick: () => handleThumbClick("up"),
|
|
447
|
+
className: cn(
|
|
448
|
+
"p-1 rounded transition-colors",
|
|
449
|
+
thumbState === "up" ? "text-foreground bg-muted" : "text-muted-foreground/40 hover:text-foreground hover:bg-muted/50"
|
|
450
|
+
),
|
|
451
|
+
title: "This is accurate",
|
|
452
|
+
"data-testid": `${testIdPrefix}-thumb-up-${feedbackKey}`,
|
|
453
|
+
children: /* @__PURE__ */ jsx(ThumbsUp, { className: "h-[10px] w-[10px]" })
|
|
454
|
+
}
|
|
455
|
+
),
|
|
456
|
+
/* @__PURE__ */ jsx(
|
|
457
|
+
"button",
|
|
458
|
+
{
|
|
459
|
+
type: "button",
|
|
460
|
+
onClick: () => handleThumbClick("down"),
|
|
461
|
+
className: cn(
|
|
462
|
+
"p-1 rounded transition-colors",
|
|
463
|
+
thumbState === "down" ? "text-red-600 bg-red-50" : "text-muted-foreground/40 hover:text-red-600 hover:bg-red-50/50"
|
|
464
|
+
),
|
|
465
|
+
title: "Report issue",
|
|
466
|
+
"data-testid": `${testIdPrefix}-thumb-down-${feedbackKey}`,
|
|
467
|
+
children: /* @__PURE__ */ jsx(ThumbsDown, { className: "h-[10px] w-[10px]" })
|
|
468
|
+
}
|
|
469
|
+
),
|
|
470
|
+
saved && /* @__PURE__ */ jsxs(
|
|
471
|
+
"span",
|
|
472
|
+
{
|
|
473
|
+
className: "inline-flex items-center gap-1 text-[11px] font-medium text-emerald-600",
|
|
474
|
+
role: "status",
|
|
475
|
+
"data-testid": `${testIdPrefix}-saved-${feedbackKey}`,
|
|
476
|
+
children: [
|
|
477
|
+
/* @__PURE__ */ jsx(Check, { className: "h-[10px] w-[10px]" }),
|
|
478
|
+
"Saved"
|
|
479
|
+
]
|
|
480
|
+
}
|
|
481
|
+
)
|
|
482
|
+
] }),
|
|
483
|
+
showInput && thumbState && /* @__PURE__ */ jsxs("div", { className: "mt-1.5", children: [
|
|
484
|
+
/* @__PURE__ */ jsx(
|
|
485
|
+
"input",
|
|
486
|
+
{
|
|
487
|
+
type: "text",
|
|
488
|
+
value: detailText,
|
|
489
|
+
onChange: (e) => setDetailText(e.target.value),
|
|
490
|
+
onKeyDown: (e) => {
|
|
491
|
+
if (e.key === "Enter") handleSubmitDetail();
|
|
492
|
+
if (e.key === "Escape") setShowInput(false);
|
|
493
|
+
},
|
|
494
|
+
placeholder: thumbState === "up" ? "What\u2019s accurate? (optional)" : "What\u2019s wrong? (optional)",
|
|
495
|
+
className: "w-full h-6 rounded border border-border bg-background px-2 text-[11px] text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-1 focus:ring-ring",
|
|
496
|
+
"data-testid": `${testIdPrefix}-detail-input-${feedbackKey}`
|
|
497
|
+
}
|
|
498
|
+
),
|
|
499
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-1.5", children: [
|
|
500
|
+
/* @__PURE__ */ jsx(
|
|
501
|
+
"button",
|
|
502
|
+
{
|
|
503
|
+
type: "button",
|
|
504
|
+
onClick: handleSubmitDetail,
|
|
505
|
+
className: "bg-foreground text-background rounded px-2 py-0.5 text-[10px] font-semibold",
|
|
506
|
+
"data-testid": `${testIdPrefix}-submit-${feedbackKey}`,
|
|
507
|
+
children: "Submit"
|
|
508
|
+
}
|
|
509
|
+
),
|
|
510
|
+
/* @__PURE__ */ jsx(
|
|
511
|
+
"button",
|
|
512
|
+
{
|
|
513
|
+
type: "button",
|
|
514
|
+
onClick: () => setShowInput(false),
|
|
515
|
+
className: "border border-border rounded px-2 py-0.5 text-[10px] font-medium",
|
|
516
|
+
children: "Cancel"
|
|
517
|
+
}
|
|
518
|
+
)
|
|
519
|
+
] })
|
|
520
|
+
] })
|
|
521
|
+
] });
|
|
522
|
+
}
|
|
289
523
|
export {
|
|
290
524
|
FeedbackActions,
|
|
291
525
|
FeedbackChipGroup,
|
|
292
526
|
FeedbackFooter,
|
|
293
|
-
FeedbackInput
|
|
527
|
+
FeedbackInput,
|
|
528
|
+
InlineFeedbackControl
|
|
294
529
|
};
|
|
295
530
|
//# sourceMappingURL=feedback-primitives.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/feedback-primitives.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ThumbsUp, ThumbsDown } from \"lucide-react\"\nimport { cn } from \"../lib/utils\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Structured feedback data shape.\n *\n * Preserves the tree structure for DB persistence:\n * reasonTop -> tier-1 chip label (maps to case_feedback.reason_top)\n * reasonSub -> tier-2 sub-chip label (maps to case_feedback.reason_sub)\n * pills -> any additional selected chips (maps to case_feedback.pills)\n * detail -> free-text input (maps to case_feedback.free_text)\n */\nexport interface FeedbackSubmitData {\n sentiment: \"positive\" | \"negative\"\n reasonTop?: string\n reasonSub?: string\n pills: string[]\n detail: string\n}\n\n/**\n * Defines a tier-1 chip that may have tier-2 sub-chips.\n */\nexport interface FeedbackChipTree {\n label: string\n subPrompt?: string\n subChips?: string[]\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackChipGroup\n// ---------------------------------------------------------------------------\n\nexport interface FeedbackChipGroupProps {\n chips: string[]\n selected: string[]\n onToggle: (chip: string) => void\n flavor: \"positive\" | \"negative\"\n className?: string\n}\n\nconst CHIP_SELECTED_CLASSES: Record<\"positive\" | \"negative\", string> = {\n negative: \"bg-red-50 text-red-700 border-red-200\",\n positive: \"bg-muted text-foreground border-border\",\n}\n\nconst CHIP_IDLE_CLASS =\n \"bg-background text-muted-foreground border-border hover:bg-muted/50\"\n\nexport function FeedbackChipGroup({\n chips,\n selected,\n onToggle,\n flavor,\n className,\n}: FeedbackChipGroupProps) {\n return (\n <div className={cn(\"flex flex-wrap gap-1.5\", className)}>\n {chips.map((chip) => {\n const isSelected = selected.includes(chip)\n return (\n <button\n key={chip}\n type=\"button\"\n onClick={() => onToggle(chip)}\n className={cn(\n \"rounded-md px-2.5 py-1 text-[11px] font-medium border transition-colors\",\n isSelected ? CHIP_SELECTED_CLASSES[flavor] : CHIP_IDLE_CLASS,\n )}\n >\n {chip}\n </button>\n )\n })}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackInput\n// ---------------------------------------------------------------------------\n\nexport interface FeedbackInputProps {\n placeholder?: string\n value: string\n onChange: (value: string) => void\n onSubmit?: () => void\n className?: string\n}\n\nexport function FeedbackInput({\n placeholder,\n value,\n onChange,\n onSubmit,\n className,\n}: FeedbackInputProps) {\n return (\n <input\n type=\"text\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && onSubmit) {\n e.preventDefault()\n onSubmit()\n }\n }}\n placeholder={placeholder}\n className={cn(\n \"w-full text-xs bg-background border border-border rounded-md px-2.5 py-2 text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-1 focus:ring-ring\",\n className,\n )}\n />\n )\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackActions\n// ---------------------------------------------------------------------------\n\nexport interface FeedbackActionsProps {\n onSubmit: () => void\n onCancel: () => void\n submitDisabled?: boolean\n submitLabel?: string\n cancelLabel?: string\n hint?: string\n className?: string\n}\n\nexport function FeedbackActions({\n onSubmit,\n onCancel,\n submitDisabled = false,\n submitLabel = \"Submit\",\n cancelLabel = \"Cancel\",\n hint,\n className,\n}: FeedbackActionsProps) {\n return (\n <div className={cn(\"flex items-center gap-2\", className)}>\n <button\n type=\"button\"\n onClick={onSubmit}\n disabled={submitDisabled}\n className=\"bg-foreground text-background rounded-md px-3 py-1.5 text-xs font-semibold disabled:opacity-50\"\n >\n {submitLabel}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"border border-border rounded-md px-3 py-1.5 text-xs font-medium\"\n >\n {cancelLabel}\n </button>\n {hint && (\n <span className=\"ml-auto text-[11px] text-muted-foreground\">\n {hint}\n </span>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackFooter\n// ---------------------------------------------------------------------------\n\nexport interface FeedbackFooterProps {\n feedback: \"positive\" | \"negative\" | null\n onFeedbackChange: (value: \"positive\" | \"negative\" | null) => void\n onSubmit: (data: FeedbackSubmitData) => void\n metaText?: string\n positivePrompt?: string\n negativePrompt?: string\n negativeChips?: FeedbackChipTree[]\n positiveChips?: string[]\n className?: string\n}\n\nconst SENTIMENT_BUTTON_ACTIVE: Record<\"positive\" | \"negative\", string> = {\n negative: \"text-red-600 bg-red-50 border-red-200\",\n positive: \"text-foreground bg-muted border-border\",\n}\n\nconst SENTIMENT_BUTTON_IDLE =\n \"text-muted-foreground hover:text-foreground\"\n\nexport function FeedbackFooter({\n feedback,\n onFeedbackChange,\n onSubmit,\n metaText,\n positivePrompt = \"Thanks! Anything to keep about this score?\",\n negativePrompt = \"What's the issue?\",\n negativeChips = [],\n positiveChips = [],\n className,\n}: FeedbackFooterProps) {\n const [expanded, setExpanded] = React.useState(false)\n const [selectedTier1, setSelectedTier1] = React.useState<string | null>(null)\n const [selectedTier2, setSelectedTier2] = React.useState<string | null>(null)\n const [additionalPills, setAdditionalPills] = React.useState<string[]>([])\n const [detailText, setDetailText] = React.useState(\"\")\n const [activeTreeIndex, setActiveTreeIndex] = React.useState<number | null>(\n null,\n )\n\n // Reset state when feedback collapses\n const resetState = React.useCallback(() => {\n setExpanded(false)\n setSelectedTier1(null)\n setSelectedTier2(null)\n setAdditionalPills([])\n setDetailText(\"\")\n setActiveTreeIndex(null)\n }, [])\n\n const handleSentimentClick = React.useCallback(\n (sentiment: \"positive\" | \"negative\") => {\n onFeedbackChange(sentiment)\n // Reset chip state when switching sentiment, then expand\n resetState()\n setExpanded(true)\n },\n [onFeedbackChange, resetState],\n )\n\n const handleTier1Toggle = React.useCallback(\n (chipLabel: string) => {\n if (selectedTier1 === chipLabel) {\n // Deselect the tier-1 chip\n setSelectedTier1(null)\n setSelectedTier2(null)\n setActiveTreeIndex(null)\n } else if (selectedTier1 === null) {\n // First selection becomes the primary reasonTop\n setSelectedTier1(chipLabel)\n setSelectedTier2(null)\n // Find the chip's tree index to show sub-chips\n const idx = negativeChips.findIndex((c) => c.label === chipLabel)\n if (idx !== -1 && negativeChips[idx].subChips) {\n setActiveTreeIndex(idx)\n } else {\n setActiveTreeIndex(null)\n }\n } else {\n // Additional selections become pills\n setAdditionalPills((prev) =>\n prev.includes(chipLabel)\n ? prev.filter((p) => p !== chipLabel)\n : [...prev, chipLabel],\n )\n }\n },\n [selectedTier1, negativeChips],\n )\n\n const handleTier2Toggle = React.useCallback((subChip: string) => {\n setSelectedTier2((prev) => (prev === subChip ? null : subChip))\n }, [])\n\n const handlePositiveChipToggle = React.useCallback(\n (chip: string) => {\n if (selectedTier1 === chip) {\n setSelectedTier1(null)\n } else if (selectedTier1 === null) {\n setSelectedTier1(chip)\n } else {\n setAdditionalPills((prev) =>\n prev.includes(chip)\n ? prev.filter((p) => p !== chip)\n : [...prev, chip],\n )\n }\n },\n [selectedTier1],\n )\n\n const handleSubmit = React.useCallback(() => {\n if (!feedback) return\n onSubmit({\n sentiment: feedback,\n reasonTop: selectedTier1 ?? undefined,\n reasonSub: selectedTier2 ?? undefined,\n pills: additionalPills,\n detail: detailText,\n })\n resetState()\n }, [\n feedback,\n selectedTier1,\n selectedTier2,\n additionalPills,\n detailText,\n onSubmit,\n resetState,\n ])\n\n const handleCancel = React.useCallback(() => {\n resetState()\n onFeedbackChange(null)\n }, [resetState, onFeedbackChange])\n\n // Determine which chips are selected (combining tier1 + additionalPills)\n const allSelectedChips = React.useMemo(() => {\n const result: string[] = []\n if (selectedTier1) result.push(selectedTier1)\n result.push(...additionalPills)\n return result\n }, [selectedTier1, additionalPills])\n\n // Active tier-1 chip tree (for showing sub-chips)\n const activeTree =\n activeTreeIndex !== null ? negativeChips[activeTreeIndex] : null\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n {/* Sentiment buttons + meta text bar */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => handleSentimentClick(\"positive\")}\n className={cn(\n \"flex gap-1 items-center text-[11px] font-medium rounded-md px-2 py-1 transition-colors\",\n feedback === \"positive\"\n ? SENTIMENT_BUTTON_ACTIVE.positive\n : SENTIMENT_BUTTON_IDLE,\n )}\n >\n <ThumbsUp className=\"h-[11px] w-[11px]\" />\n Helpful\n </button>\n <button\n type=\"button\"\n onClick={() => handleSentimentClick(\"negative\")}\n className={cn(\n \"flex gap-1 items-center text-[11px] font-medium rounded-md px-2 py-1 transition-colors\",\n feedback === \"negative\"\n ? SENTIMENT_BUTTON_ACTIVE.negative\n : SENTIMENT_BUTTON_IDLE,\n )}\n >\n <ThumbsDown className=\"h-[11px] w-[11px]\" />\n Not helpful\n </button>\n </div>\n {metaText && (\n <span className=\"text-[11px] text-muted-foreground\">{metaText}</span>\n )}\n </div>\n\n {/* Expanded feedback area */}\n {expanded && feedback && (\n <div className=\"space-y-3\">\n {/* Prompt text */}\n <p className=\"text-xs text-muted-foreground\">\n {feedback === \"negative\" ? negativePrompt : positivePrompt}\n </p>\n\n {/* Chip area */}\n {feedback === \"negative\" && negativeChips.length > 0 && (\n <div className=\"space-y-2\">\n {/* Tier-1 chips */}\n <FeedbackChipGroup\n chips={negativeChips.map((c) => c.label)}\n selected={allSelectedChips}\n onToggle={handleTier1Toggle}\n flavor=\"negative\"\n />\n\n {/* Tier-2 sub-chips (shown when a tier-1 with sub-chips is active) */}\n {activeTree && activeTree.subChips && (\n <div className=\"pl-3 space-y-1.5\">\n {activeTree.subPrompt && (\n <p className=\"text-[11px] text-muted-foreground\">\n {activeTree.subPrompt}\n </p>\n )}\n <FeedbackChipGroup\n chips={activeTree.subChips}\n selected={selectedTier2 ? [selectedTier2] : []}\n onToggle={handleTier2Toggle}\n flavor=\"negative\"\n />\n </div>\n )}\n </div>\n )}\n\n {feedback === \"positive\" && positiveChips.length > 0 && (\n <FeedbackChipGroup\n chips={positiveChips}\n selected={allSelectedChips}\n onToggle={handlePositiveChipToggle}\n flavor=\"positive\"\n />\n )}\n\n {/* Detail text input */}\n <FeedbackInput\n placeholder=\"Add optional detail…\"\n value={detailText}\n onChange={setDetailText}\n onSubmit={handleSubmit}\n />\n\n {/* Action buttons */}\n <FeedbackActions onSubmit={handleSubmit} onCancel={handleCancel} />\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/feedback-primitives.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { ThumbsUp, ThumbsDown, Check, Pencil } from \"lucide-react\"\nimport { cn } from \"../lib/utils\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Structured feedback data shape.\n *\n * Preserves the tree structure for DB persistence:\n * reasonTop -> tier-1 chip label (maps to case_feedback.reason_top)\n * reasonSub -> tier-2 sub-chip label (maps to case_feedback.reason_sub)\n * pills -> any additional selected chips (maps to case_feedback.pills)\n * detail -> free-text input (maps to case_feedback.free_text)\n */\nexport interface FeedbackSubmitData {\n sentiment: \"positive\" | \"negative\"\n reasonTop?: string\n reasonSub?: string\n pills: string[]\n detail: string\n}\n\n/**\n * Persisted feedback data from a previous submission, used to hydrate the\n * footer into its \"already submitted\" visual state.\n */\nexport interface PersistedFeedbackData {\n sentiment: \"positive\" | \"negative\"\n reasonTop?: string\n reasonSub?: string\n pills?: string[]\n detail?: string\n ownershipLabel: \"Your feedback\" | \"Team feedback\"\n}\n\n/**\n * Defines a tier-1 chip that may have tier-2 sub-chips.\n */\nexport interface FeedbackChipTree {\n label: string\n subPrompt?: string\n subChips?: string[]\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackChipGroup\n// ---------------------------------------------------------------------------\n\nexport interface FeedbackChipGroupProps {\n chips: string[]\n selected: string[]\n onToggle: (chip: string) => void\n flavor: \"positive\" | \"negative\"\n className?: string\n}\n\nconst CHIP_SELECTED_CLASSES: Record<\"positive\" | \"negative\", string> = {\n negative: \"bg-red-50 text-red-700 border-red-200\",\n positive: \"bg-muted text-foreground border-border\",\n}\n\nconst CHIP_IDLE_CLASS =\n \"bg-background text-muted-foreground border-border hover:bg-muted/50\"\n\nexport function FeedbackChipGroup({\n chips,\n selected,\n onToggle,\n flavor,\n className,\n}: FeedbackChipGroupProps) {\n return (\n <div className={cn(\"flex flex-wrap gap-1.5\", className)}>\n {chips.map((chip) => {\n const isSelected = selected.includes(chip)\n return (\n <button\n key={chip}\n type=\"button\"\n onClick={() => onToggle(chip)}\n className={cn(\n \"rounded-md px-2.5 py-1 text-[11px] font-medium border transition-colors\",\n isSelected ? CHIP_SELECTED_CLASSES[flavor] : CHIP_IDLE_CLASS,\n )}\n >\n {chip}\n </button>\n )\n })}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackInput\n// ---------------------------------------------------------------------------\n\nexport interface FeedbackInputProps {\n placeholder?: string\n value: string\n onChange: (value: string) => void\n onSubmit?: () => void\n className?: string\n}\n\nexport function FeedbackInput({\n placeholder,\n value,\n onChange,\n onSubmit,\n className,\n}: FeedbackInputProps) {\n return (\n <input\n type=\"text\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && onSubmit) {\n e.preventDefault()\n onSubmit()\n }\n }}\n placeholder={placeholder}\n className={cn(\n \"w-full text-xs bg-background border border-border rounded-md px-2.5 py-2 text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-1 focus:ring-ring\",\n className,\n )}\n />\n )\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackActions\n// ---------------------------------------------------------------------------\n\nexport interface FeedbackActionsProps {\n onSubmit: () => void\n onCancel: () => void\n submitDisabled?: boolean\n submitLabel?: string\n cancelLabel?: string\n hint?: string\n className?: string\n}\n\nexport function FeedbackActions({\n onSubmit,\n onCancel,\n submitDisabled = false,\n submitLabel = \"Submit\",\n cancelLabel = \"Cancel\",\n hint,\n className,\n}: FeedbackActionsProps) {\n return (\n <div className={cn(\"flex items-center gap-2\", className)}>\n <button\n type=\"button\"\n onClick={onSubmit}\n disabled={submitDisabled}\n className=\"bg-foreground text-background rounded-md px-3 py-1.5 text-xs font-semibold disabled:opacity-50\"\n >\n {submitLabel}\n </button>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"border border-border rounded-md px-3 py-1.5 text-xs font-medium\"\n >\n {cancelLabel}\n </button>\n {hint && (\n <span className=\"ml-auto text-[11px] text-muted-foreground\">\n {hint}\n </span>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// FeedbackFooter\n// ---------------------------------------------------------------------------\n\nexport interface FeedbackFooterProps {\n feedback: \"positive\" | \"negative\" | null\n onFeedbackChange: (value: \"positive\" | \"negative\" | null) => void\n onSubmit: (data: FeedbackSubmitData) => void\n metaText?: string\n positivePrompt?: string\n negativePrompt?: string\n negativeChips?: FeedbackChipTree[]\n positiveChips?: string[]\n className?: string\n /** Pre-existing feedback to hydrate from (e.g. after page reload). */\n initialFeedback?: PersistedFeedbackData | null\n /** Label shown in the transient confirmation pill after submit. */\n submittedLabel?: string\n /** Stable key for syncing initialFeedback into local state. When this\n * changes, the component resets to the new initialFeedback value. */\n feedbackKey?: string\n}\n\nconst SENTIMENT_BUTTON_ACTIVE: Record<\"positive\" | \"negative\", string> = {\n negative: \"text-red-600 bg-red-50 border-red-200\",\n positive: \"text-foreground bg-muted border-border\",\n}\n\nconst SENTIMENT_BUTTON_IDLE =\n \"text-muted-foreground hover:text-foreground\"\n\nexport function FeedbackFooter({\n feedback,\n onFeedbackChange,\n onSubmit,\n metaText,\n positivePrompt = \"Thanks! Anything to keep about this score?\",\n negativePrompt = \"What's the issue?\",\n negativeChips = [],\n positiveChips = [],\n className,\n initialFeedback,\n submittedLabel = \"Saved\",\n feedbackKey,\n}: FeedbackFooterProps) {\n const [expanded, setExpanded] = React.useState(false)\n const [selectedTier1, setSelectedTier1] = React.useState<string | null>(null)\n const [selectedTier2, setSelectedTier2] = React.useState<string | null>(null)\n const [additionalPills, setAdditionalPills] = React.useState<string[]>([])\n const [detailText, setDetailText] = React.useState(\"\")\n const [activeTreeIndex, setActiveTreeIndex] = React.useState<number | null>(\n null,\n )\n /** Transient \"Saved\" confirmation — shown after successful submit. */\n const [submitted, setSubmitted] = React.useState(false)\n /** Persisted feedback shown as a clickable indicator (survives reload). */\n const [persisted, setPersisted] = React.useState<PersistedFeedbackData | null>(\n initialFeedback ?? null,\n )\n /** Tracks whether the user is actively editing (ref to guard against prop overwrites without triggering re-syncs). */\n const isEditingRef = React.useRef(false)\n /** Track the last synced feedbackKey to detect key changes. */\n const lastKeyRef = React.useRef<string | undefined>(feedbackKey)\n\n /** Helper to update the editing ref. */\n const setIsEditing = React.useCallback((value: boolean) => {\n isEditingRef.current = value\n }, [])\n\n // Sync initialFeedback into local state via useEffect keyed on feedbackKey.\n // When feedbackKey changes, reset to new target. Preserve active edits\n // when feedbackKey stays the same.\n React.useEffect(() => {\n const keyChanged = feedbackKey !== lastKeyRef.current\n lastKeyRef.current = feedbackKey\n\n if (keyChanged) {\n // Key changed — full reset to new target\n setPersisted(initialFeedback ?? null)\n setSubmitted(false)\n setExpanded(false)\n isEditingRef.current = false\n if (initialFeedback) {\n onFeedbackChange(initialFeedback.sentiment)\n } else {\n onFeedbackChange(null)\n }\n } else if (!isEditingRef.current) {\n // Same key, not actively editing — safe to sync\n setPersisted(initialFeedback ?? null)\n if (initialFeedback) {\n onFeedbackChange(initialFeedback.sentiment)\n }\n }\n }, [initialFeedback, feedbackKey, onFeedbackChange])\n\n // Reset state when feedback collapses\n const resetState = React.useCallback(() => {\n setExpanded(false)\n setSelectedTier1(null)\n setSelectedTier2(null)\n setAdditionalPills([])\n setDetailText(\"\")\n setActiveTreeIndex(null)\n setIsEditing(false)\n }, [setIsEditing])\n\n const handleSentimentClick = React.useCallback(\n (sentiment: \"positive\" | \"negative\") => {\n onFeedbackChange(sentiment)\n // Reset chip state when switching sentiment, then expand\n resetState()\n setExpanded(true)\n setSubmitted(false)\n setPersisted(null)\n setIsEditing(true)\n },\n [onFeedbackChange, resetState, setIsEditing],\n )\n\n /** Open the persisted indicator for editing. */\n const handlePersistedClick = React.useCallback(() => {\n if (!persisted) return\n onFeedbackChange(persisted.sentiment)\n setSelectedTier1(persisted.reasonTop ?? null)\n setSelectedTier2(persisted.reasonSub ?? null)\n setAdditionalPills(persisted.pills ?? [])\n setDetailText(persisted.detail ?? \"\")\n setExpanded(true)\n setSubmitted(false)\n setIsEditing(true)\n }, [persisted, onFeedbackChange, setIsEditing])\n\n const handleTier1Toggle = React.useCallback(\n (chipLabel: string) => {\n if (selectedTier1 === chipLabel) {\n // Deselect the tier-1 chip\n setSelectedTier1(null)\n setSelectedTier2(null)\n setActiveTreeIndex(null)\n } else if (selectedTier1 === null) {\n // First selection becomes the primary reasonTop\n setSelectedTier1(chipLabel)\n setSelectedTier2(null)\n // Find the chip's tree index to show sub-chips\n const idx = negativeChips.findIndex((c) => c.label === chipLabel)\n if (idx !== -1 && negativeChips[idx].subChips) {\n setActiveTreeIndex(idx)\n } else {\n setActiveTreeIndex(null)\n }\n } else {\n // Additional selections become pills\n setAdditionalPills((prev) =>\n prev.includes(chipLabel)\n ? prev.filter((p) => p !== chipLabel)\n : [...prev, chipLabel],\n )\n }\n },\n [selectedTier1, negativeChips],\n )\n\n const handleTier2Toggle = React.useCallback((subChip: string) => {\n setSelectedTier2((prev) => (prev === subChip ? null : subChip))\n }, [])\n\n const handlePositiveChipToggle = React.useCallback(\n (chip: string) => {\n if (selectedTier1 === chip) {\n setSelectedTier1(null)\n } else if (selectedTier1 === null) {\n setSelectedTier1(chip)\n } else {\n setAdditionalPills((prev) =>\n prev.includes(chip)\n ? prev.filter((p) => p !== chip)\n : [...prev, chip],\n )\n }\n },\n [selectedTier1],\n )\n\n const handleSubmit = React.useCallback(() => {\n if (!feedback) return\n onSubmit({\n sentiment: feedback,\n reasonTop: selectedTier1 ?? undefined,\n reasonSub: selectedTier2 ?? undefined,\n pills: additionalPills,\n detail: detailText,\n })\n // Show transient \"Saved\" confirmation\n setSubmitted(true)\n // Collapse expansion but keep sentiment visible\n resetState()\n }, [\n feedback,\n selectedTier1,\n selectedTier2,\n additionalPills,\n detailText,\n onSubmit,\n resetState,\n ])\n\n const handleCancel = React.useCallback(() => {\n resetState()\n onFeedbackChange(null)\n }, [resetState, onFeedbackChange])\n\n // Determine which chips are selected (combining tier1 + additionalPills)\n const allSelectedChips = React.useMemo(() => {\n const result: string[] = []\n if (selectedTier1) result.push(selectedTier1)\n result.push(...additionalPills)\n return result\n }, [selectedTier1, additionalPills])\n\n // Active tier-1 chip tree (for showing sub-chips)\n const activeTree =\n activeTreeIndex !== null ? negativeChips[activeTreeIndex] : null\n\n // Determine if we should show the persisted indicator instead of bare buttons\n const showPersistedIndicator = persisted && !expanded && !submitted\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n {/* Sentiment buttons + meta text bar */}\n <div className=\"flex items-center justify-between\">\n {showPersistedIndicator ? (\n /* Persisted feedback indicator — clickable to reopen editor */\n <button\n type=\"button\"\n onClick={handlePersistedClick}\n className=\"group flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors\"\n data-testid=\"persisted-feedback-indicator\"\n >\n <span className=\"font-medium\">{persisted.ownershipLabel}:</span>\n {persisted.sentiment === \"positive\" ? (\n <ThumbsUp className=\"h-[11px] w-[11px]\" />\n ) : (\n <ThumbsDown className=\"h-[11px] w-[11px]\" />\n )}\n {persisted.detail && (\n <span className=\"max-w-[200px] truncate text-muted-foreground/70\">\n {persisted.detail}\n </span>\n )}\n <Pencil className=\"h-[9px] w-[9px] opacity-0 group-hover:opacity-100 transition-opacity\" />\n </button>\n ) : (\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => handleSentimentClick(\"positive\")}\n className={cn(\n \"flex gap-1 items-center text-[11px] font-medium rounded-md px-2 py-1 transition-colors\",\n feedback === \"positive\"\n ? SENTIMENT_BUTTON_ACTIVE.positive\n : SENTIMENT_BUTTON_IDLE,\n )}\n >\n <ThumbsUp className=\"h-[11px] w-[11px]\" />\n Helpful\n </button>\n <button\n type=\"button\"\n onClick={() => handleSentimentClick(\"negative\")}\n className={cn(\n \"flex gap-1 items-center text-[11px] font-medium rounded-md px-2 py-1 transition-colors\",\n feedback === \"negative\"\n ? SENTIMENT_BUTTON_ACTIVE.negative\n : SENTIMENT_BUTTON_IDLE,\n )}\n >\n <ThumbsDown className=\"h-[11px] w-[11px]\" />\n Not helpful\n </button>\n {/* Transient \"Saved\" confirmation pill */}\n {submitted && feedback && (\n <span\n className=\"inline-flex items-center gap-1 text-[11px] font-medium text-emerald-600\"\n role=\"status\"\n data-testid=\"feedback-submitted-pill\"\n >\n <Check className=\"h-[11px] w-[11px]\" />\n {submittedLabel}\n </span>\n )}\n </div>\n )}\n {metaText && (\n <span className=\"text-[11px] text-muted-foreground\">{metaText}</span>\n )}\n </div>\n\n {/* Expanded feedback area */}\n {expanded && feedback && (\n <div className=\"space-y-3\">\n {/* Prompt text */}\n <p className=\"text-xs text-muted-foreground\">\n {feedback === \"negative\" ? negativePrompt : positivePrompt}\n </p>\n\n {/* Chip area */}\n {feedback === \"negative\" && negativeChips.length > 0 && (\n <div className=\"space-y-2\">\n {/* Tier-1 chips */}\n <FeedbackChipGroup\n chips={negativeChips.map((c) => c.label)}\n selected={allSelectedChips}\n onToggle={handleTier1Toggle}\n flavor=\"negative\"\n />\n\n {/* Tier-2 sub-chips (shown when a tier-1 with sub-chips is active) */}\n {activeTree && activeTree.subChips && (\n <div className=\"pl-3 space-y-1.5\">\n {activeTree.subPrompt && (\n <p className=\"text-[11px] text-muted-foreground\">\n {activeTree.subPrompt}\n </p>\n )}\n <FeedbackChipGroup\n chips={activeTree.subChips}\n selected={selectedTier2 ? [selectedTier2] : []}\n onToggle={handleTier2Toggle}\n flavor=\"negative\"\n />\n </div>\n )}\n </div>\n )}\n\n {feedback === \"positive\" && positiveChips.length > 0 && (\n <FeedbackChipGroup\n chips={positiveChips}\n selected={allSelectedChips}\n onToggle={handlePositiveChipToggle}\n flavor=\"positive\"\n />\n )}\n\n {/* Detail text input */}\n <FeedbackInput\n placeholder=\"Add optional detail…\"\n value={detailText}\n onChange={setDetailText}\n onSubmit={handleSubmit}\n />\n\n {/* Action buttons */}\n <FeedbackActions onSubmit={handleSubmit} onCancel={handleCancel} />\n </div>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// InlineFeedbackControl — shared thumb+detail inline feedback widget\n// ---------------------------------------------------------------------------\n\nexport interface InlineFeedbackControlProps {\n /** Unique key identifying the feedback target (e.g. factor key). */\n feedbackKey: string\n /** Persisted/initial feedback to hydrate from. */\n initialFeedback?: { type: \"up\" | \"down\"; detail: string; ownershipLabel?: string }\n /** Called when user submits or clears feedback. */\n onFeedback?: (key: string, type: \"up\" | \"down\" | null, detail?: string) => void\n /** Test ID prefix for all sub-elements. */\n testIdPrefix?: string\n}\n\n/**\n * Compact inline thumb-up/thumb-down feedback with optional detail text.\n * Used by PriorityFactorRow and any other component that needs\n * a lightweight feedback control.\n */\nexport function InlineFeedbackControl({\n feedbackKey,\n initialFeedback,\n onFeedback,\n testIdPrefix = \"inline-feedback\",\n}: InlineFeedbackControlProps) {\n const [thumbState, setThumbState] = React.useState<\"up\" | \"down\" | null>(\n initialFeedback?.type ?? null,\n )\n const [showInput, setShowInput] = React.useState(false)\n const [detailText, setDetailText] = React.useState(initialFeedback?.detail ?? \"\")\n const [saved, setSaved] = React.useState(!!initialFeedback)\n const [savedDetail, setSavedDetail] = React.useState(initialFeedback?.detail ?? \"\")\n const ownershipLabel = initialFeedback?.ownershipLabel ?? \"Your feedback\"\n\n // Sync with initialFeedback prop changes\n React.useEffect(() => {\n if (initialFeedback) {\n setThumbState(initialFeedback.type)\n setSaved(true)\n setSavedDetail(initialFeedback.detail)\n }\n }, [initialFeedback])\n\n const handleThumbClick = React.useCallback(\n (type: \"up\" | \"down\") => {\n if (thumbState === type) {\n // Toggle off\n setThumbState(null)\n setShowInput(false)\n setSaved(false)\n onFeedback?.(feedbackKey, null)\n } else {\n setThumbState(type)\n setShowInput(true)\n setSaved(false)\n }\n },\n [thumbState, feedbackKey, onFeedback],\n )\n\n const handleSubmitDetail = React.useCallback(() => {\n if (!thumbState) return\n const text = detailText.trim()\n onFeedback?.(feedbackKey, thumbState, text)\n setSaved(true)\n setSavedDetail(text)\n setShowInput(false)\n }, [thumbState, detailText, feedbackKey, onFeedback])\n\n return (\n <div>\n {saved && !showInput ? (\n /* Persisted / saved indicator */\n <button\n type=\"button\"\n onClick={() => {\n setDetailText(savedDetail)\n setShowInput(true)\n setSaved(false)\n }}\n className=\"group flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors\"\n data-testid={`${testIdPrefix}-feedback-persisted-${feedbackKey}`}\n >\n <span className=\"font-medium\">{ownershipLabel}:</span>\n {thumbState === \"up\" ? (\n <ThumbsUp className=\"h-[10px] w-[10px]\" />\n ) : (\n <ThumbsDown className=\"h-[10px] w-[10px]\" />\n )}\n {savedDetail && (\n <span className=\"max-w-[180px] truncate text-muted-foreground/70\">\n {savedDetail}\n </span>\n )}\n <Pencil className=\"h-[9px] w-[9px] opacity-0 group-hover:opacity-100 transition-opacity\" />\n </button>\n ) : (\n <div className=\"flex items-center gap-1.5\">\n {/* Inline thumb buttons */}\n <button\n type=\"button\"\n onClick={() => handleThumbClick(\"up\")}\n className={cn(\n \"p-1 rounded transition-colors\",\n thumbState === \"up\"\n ? \"text-foreground bg-muted\"\n : \"text-muted-foreground/40 hover:text-foreground hover:bg-muted/50\",\n )}\n title=\"This is accurate\"\n data-testid={`${testIdPrefix}-thumb-up-${feedbackKey}`}\n >\n <ThumbsUp className=\"h-[10px] w-[10px]\" />\n </button>\n <button\n type=\"button\"\n onClick={() => handleThumbClick(\"down\")}\n className={cn(\n \"p-1 rounded transition-colors\",\n thumbState === \"down\"\n ? \"text-red-600 bg-red-50\"\n : \"text-muted-foreground/40 hover:text-red-600 hover:bg-red-50/50\",\n )}\n title=\"Report issue\"\n data-testid={`${testIdPrefix}-thumb-down-${feedbackKey}`}\n >\n <ThumbsDown className=\"h-[10px] w-[10px]\" />\n </button>\n\n {/* Transient \"Saved\" pill */}\n {saved && (\n <span\n className=\"inline-flex items-center gap-1 text-[11px] font-medium text-emerald-600\"\n role=\"status\"\n data-testid={`${testIdPrefix}-saved-${feedbackKey}`}\n >\n <Check className=\"h-[10px] w-[10px]\" />\n Saved\n </span>\n )}\n </div>\n )}\n\n {/* Inline detail input */}\n {showInput && thumbState && (\n <div className=\"mt-1.5\">\n <input\n type=\"text\"\n value={detailText}\n onChange={(e) => setDetailText(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") handleSubmitDetail()\n if (e.key === \"Escape\") setShowInput(false)\n }}\n placeholder={\n thumbState === \"up\"\n ? \"What\\u2019s accurate? (optional)\"\n : \"What\\u2019s wrong? (optional)\"\n }\n className=\"w-full h-6 rounded border border-border bg-background px-2 text-[11px] text-foreground placeholder:text-muted-foreground/50 focus:outline-none focus:ring-1 focus:ring-ring\"\n data-testid={`${testIdPrefix}-detail-input-${feedbackKey}`}\n />\n <div className=\"mt-1 flex items-center gap-1.5\">\n <button\n type=\"button\"\n onClick={handleSubmitDetail}\n className=\"bg-foreground text-background rounded px-2 py-0.5 text-[10px] font-semibold\"\n data-testid={`${testIdPrefix}-submit-${feedbackKey}`}\n >\n Submit\n </button>\n <button\n type=\"button\"\n onClick={() => setShowInput(false)}\n className=\"border border-border rounded px-2 py-0.5 text-[10px] font-medium\"\n >\n Cancel\n </button>\n </div>\n </div>\n )}\n </div>\n )\n}\n"],"mappings":";AAiFU,cAgFN,YAhFM;AA/EV,YAAY,WAAW;AACvB,SAAS,UAAU,YAAY,OAAO,cAAc;AACpD,SAAS,UAAU;AAyDnB,MAAM,wBAAiE;AAAA,EACrE,UAAU;AAAA,EACV,UAAU;AACZ;AAEA,MAAM,kBACJ;AAEK,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,SACE,oBAAC,SAAI,WAAW,GAAG,0BAA0B,SAAS,GACnD,gBAAM,IAAI,CAAC,SAAS;AACnB,UAAM,aAAa,SAAS,SAAS,IAAI;AACzC,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,MAAK;AAAA,QACL,SAAS,MAAM,SAAS,IAAI;AAAA,QAC5B,WAAW;AAAA,UACT;AAAA,UACA,aAAa,sBAAsB,MAAM,IAAI;AAAA,QAC/C;AAAA,QAEC;AAAA;AAAA,MARI;AAAA,IASP;AAAA,EAEJ,CAAC,GACH;AAEJ;AAcO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL;AAAA,MACA,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,MACxC,WAAW,CAAC,MAAM;AAChB,YAAI,EAAE,QAAQ,WAAW,UAAU;AACjC,YAAE,eAAe;AACjB,mBAAS;AAAA,QACX;AAAA,MACF;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;AAgBO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,cAAc;AAAA,EACd;AAAA,EACA;AACF,GAAyB;AACvB,SACE,qBAAC,SAAI,WAAW,GAAG,2BAA2B,SAAS,GACrD;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,UAAU;AAAA,QACV,WAAU;AAAA,QAET;AAAA;AAAA,IACH;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,WAAU;AAAA,QAET;AAAA;AAAA,IACH;AAAA,IACC,QACC,oBAAC,UAAK,WAAU,6CACb,gBACH;AAAA,KAEJ;AAEJ;AAyBA,MAAM,0BAAmE;AAAA,EACvE,UAAU;AAAA,EACV,UAAU;AACZ;AAEA,MAAM,wBACJ;AAEK,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,gBAAgB,CAAC;AAAA,EACjB,gBAAgB,CAAC;AAAA,EACjB;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AACF,GAAwB;AACtB,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AACpD,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAwB,IAAI;AAC5E,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAwB,IAAI;AAC5E,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAmB,CAAC,CAAC;AACzE,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,EAAE;AACrD,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM;AAAA,IAClD;AAAA,EACF;AAEA,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AAEtD,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM;AAAA,IACtC,4CAAmB;AAAA,EACrB;AAEA,QAAM,eAAe,MAAM,OAAO,KAAK;AAEvC,QAAM,aAAa,MAAM,OAA2B,WAAW;AAG/D,QAAM,eAAe,MAAM,YAAY,CAAC,UAAmB;AACzD,iBAAa,UAAU;AAAA,EACzB,GAAG,CAAC,CAAC;AAKL,QAAM,UAAU,MAAM;AACpB,UAAM,aAAa,gBAAgB,WAAW;AAC9C,eAAW,UAAU;AAErB,QAAI,YAAY;AAEd,mBAAa,4CAAmB,IAAI;AACpC,mBAAa,KAAK;AAClB,kBAAY,KAAK;AACjB,mBAAa,UAAU;AACvB,UAAI,iBAAiB;AACnB,yBAAiB,gBAAgB,SAAS;AAAA,MAC5C,OAAO;AACL,yBAAiB,IAAI;AAAA,MACvB;AAAA,IACF,WAAW,CAAC,aAAa,SAAS;AAEhC,mBAAa,4CAAmB,IAAI;AACpC,UAAI,iBAAiB;AACnB,yBAAiB,gBAAgB,SAAS;AAAA,MAC5C;AAAA,IACF;AAAA,EACF,GAAG,CAAC,iBAAiB,aAAa,gBAAgB,CAAC;AAGnD,QAAM,aAAa,MAAM,YAAY,MAAM;AACzC,gBAAY,KAAK;AACjB,qBAAiB,IAAI;AACrB,qBAAiB,IAAI;AACrB,uBAAmB,CAAC,CAAC;AACrB,kBAAc,EAAE;AAChB,uBAAmB,IAAI;AACvB,iBAAa,KAAK;AAAA,EACpB,GAAG,CAAC,YAAY,CAAC;AAEjB,QAAM,uBAAuB,MAAM;AAAA,IACjC,CAAC,cAAuC;AACtC,uBAAiB,SAAS;AAE1B,iBAAW;AACX,kBAAY,IAAI;AAChB,mBAAa,KAAK;AAClB,mBAAa,IAAI;AACjB,mBAAa,IAAI;AAAA,IACnB;AAAA,IACA,CAAC,kBAAkB,YAAY,YAAY;AAAA,EAC7C;AAGA,QAAM,uBAAuB,MAAM,YAAY,MAAM;AAnTvD;AAoTI,QAAI,CAAC,UAAW;AAChB,qBAAiB,UAAU,SAAS;AACpC,sBAAiB,eAAU,cAAV,YAAuB,IAAI;AAC5C,sBAAiB,eAAU,cAAV,YAAuB,IAAI;AAC5C,wBAAmB,eAAU,UAAV,YAAmB,CAAC,CAAC;AACxC,mBAAc,eAAU,WAAV,YAAoB,EAAE;AACpC,gBAAY,IAAI;AAChB,iBAAa,KAAK;AAClB,iBAAa,IAAI;AAAA,EACnB,GAAG,CAAC,WAAW,kBAAkB,YAAY,CAAC;AAE9C,QAAM,oBAAoB,MAAM;AAAA,IAC9B,CAAC,cAAsB;AACrB,UAAI,kBAAkB,WAAW;AAE/B,yBAAiB,IAAI;AACrB,yBAAiB,IAAI;AACrB,2BAAmB,IAAI;AAAA,MACzB,WAAW,kBAAkB,MAAM;AAEjC,yBAAiB,SAAS;AAC1B,yBAAiB,IAAI;AAErB,cAAM,MAAM,cAAc,UAAU,CAAC,MAAM,EAAE,UAAU,SAAS;AAChE,YAAI,QAAQ,MAAM,cAAc,GAAG,EAAE,UAAU;AAC7C,6BAAmB,GAAG;AAAA,QACxB,OAAO;AACL,6BAAmB,IAAI;AAAA,QACzB;AAAA,MACF,OAAO;AAEL;AAAA,UAAmB,CAAC,SAClB,KAAK,SAAS,SAAS,IACnB,KAAK,OAAO,CAAC,MAAM,MAAM,SAAS,IAClC,CAAC,GAAG,MAAM,SAAS;AAAA,QACzB;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,eAAe,aAAa;AAAA,EAC/B;AAEA,QAAM,oBAAoB,MAAM,YAAY,CAAC,YAAoB;AAC/D,qBAAiB,CAAC,SAAU,SAAS,UAAU,OAAO,OAAQ;AAAA,EAChE,GAAG,CAAC,CAAC;AAEL,QAAM,2BAA2B,MAAM;AAAA,IACrC,CAAC,SAAiB;AAChB,UAAI,kBAAkB,MAAM;AAC1B,yBAAiB,IAAI;AAAA,MACvB,WAAW,kBAAkB,MAAM;AACjC,yBAAiB,IAAI;AAAA,MACvB,OAAO;AACL;AAAA,UAAmB,CAAC,SAClB,KAAK,SAAS,IAAI,IACd,KAAK,OAAO,CAAC,MAAM,MAAM,IAAI,IAC7B,CAAC,GAAG,MAAM,IAAI;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,aAAa;AAAA,EAChB;AAEA,QAAM,eAAe,MAAM,YAAY,MAAM;AAC3C,QAAI,CAAC,SAAU;AACf,aAAS;AAAA,MACP,WAAW;AAAA,MACX,WAAW,wCAAiB;AAAA,MAC5B,WAAW,wCAAiB;AAAA,MAC5B,OAAO;AAAA,MACP,QAAQ;AAAA,IACV,CAAC;AAED,iBAAa,IAAI;AAEjB,eAAW;AAAA,EACb,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,eAAe,MAAM,YAAY,MAAM;AAC3C,eAAW;AACX,qBAAiB,IAAI;AAAA,EACvB,GAAG,CAAC,YAAY,gBAAgB,CAAC;AAGjC,QAAM,mBAAmB,MAAM,QAAQ,MAAM;AAC3C,UAAM,SAAmB,CAAC;AAC1B,QAAI,cAAe,QAAO,KAAK,aAAa;AAC5C,WAAO,KAAK,GAAG,eAAe;AAC9B,WAAO;AAAA,EACT,GAAG,CAAC,eAAe,eAAe,CAAC;AAGnC,QAAM,aACJ,oBAAoB,OAAO,cAAc,eAAe,IAAI;AAG9D,QAAM,yBAAyB,aAAa,CAAC,YAAY,CAAC;AAE1D,SACE,qBAAC,SAAI,WAAW,GAAG,aAAa,SAAS,GAEvC;AAAA,yBAAC,SAAI,WAAU,qCACZ;AAAA;AAAA;AAAA,QAEC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAU;AAAA,YACV,eAAY;AAAA,YAEZ;AAAA,mCAAC,UAAK,WAAU,eAAe;AAAA,0BAAU;AAAA,gBAAe;AAAA,iBAAC;AAAA,cACxD,UAAU,cAAc,aACvB,oBAAC,YAAS,WAAU,qBAAoB,IAExC,oBAAC,cAAW,WAAU,qBAAoB;AAAA,cAE3C,UAAU,UACT,oBAAC,UAAK,WAAU,mDACb,oBAAU,QACb;AAAA,cAEF,oBAAC,UAAO,WAAU,wEAAuE;AAAA;AAAA;AAAA,QAC3F;AAAA,UAEA,qBAAC,SAAI,WAAU,2BACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,qBAAqB,UAAU;AAAA,YAC9C,WAAW;AAAA,cACT;AAAA,cACA,aAAa,aACT,wBAAwB,WACxB;AAAA,YACN;AAAA,YAEA;AAAA,kCAAC,YAAS,WAAU,qBAAoB;AAAA,cAAE;AAAA;AAAA;AAAA,QAE5C;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,qBAAqB,UAAU;AAAA,YAC9C,WAAW;AAAA,cACT;AAAA,cACA,aAAa,aACT,wBAAwB,WACxB;AAAA,YACN;AAAA,YAEA;AAAA,kCAAC,cAAW,WAAU,qBAAoB;AAAA,cAAE;AAAA;AAAA;AAAA,QAE9C;AAAA,QAEC,aAAa,YACZ;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,MAAK;AAAA,YACL,eAAY;AAAA,YAEZ;AAAA,kCAAC,SAAM,WAAU,qBAAoB;AAAA,cACpC;AAAA;AAAA;AAAA,QACH;AAAA,SAEJ;AAAA,MAED,YACC,oBAAC,UAAK,WAAU,qCAAqC,oBAAS;AAAA,OAElE;AAAA,IAGC,YAAY,YACX,qBAAC,SAAI,WAAU,aAEb;AAAA,0BAAC,OAAE,WAAU,iCACV,uBAAa,aAAa,iBAAiB,gBAC9C;AAAA,MAGC,aAAa,cAAc,cAAc,SAAS,KACjD,qBAAC,SAAI,WAAU,aAEb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,cAAc,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA,YACvC,UAAU;AAAA,YACV,UAAU;AAAA,YACV,QAAO;AAAA;AAAA,QACT;AAAA,QAGC,cAAc,WAAW,YACxB,qBAAC,SAAI,WAAU,oBACZ;AAAA,qBAAW,aACV,oBAAC,OAAE,WAAU,qCACV,qBAAW,WACd;AAAA,UAEF;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,WAAW;AAAA,cAClB,UAAU,gBAAgB,CAAC,aAAa,IAAI,CAAC;AAAA,cAC7C,UAAU;AAAA,cACV,QAAO;AAAA;AAAA,UACT;AAAA,WACF;AAAA,SAEJ;AAAA,MAGD,aAAa,cAAc,cAAc,SAAS,KACjD;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,UACP,UAAU;AAAA,UACV,UAAU;AAAA,UACV,QAAO;AAAA;AAAA,MACT;AAAA,MAIF;AAAA,QAAC;AAAA;AAAA,UACC,aAAY;AAAA,UACZ,OAAO;AAAA,UACP,UAAU;AAAA,UACV,UAAU;AAAA;AAAA,MACZ;AAAA,MAGA,oBAAC,mBAAgB,UAAU,cAAc,UAAU,cAAc;AAAA,OACnE;AAAA,KAEJ;AAEJ;AAsBO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AACjB,GAA+B;AA5jB/B;AA6jBE,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM;AAAA,KACxC,wDAAiB,SAAjB,YAAyB;AAAA,EAC3B;AACA,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AACtD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,UAAS,wDAAiB,WAAjB,YAA2B,EAAE;AAChF,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,CAAC,CAAC,eAAe;AAC1D,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,UAAS,wDAAiB,WAAjB,YAA2B,EAAE;AAClF,QAAM,kBAAiB,wDAAiB,mBAAjB,YAAmC;AAG1D,QAAM,UAAU,MAAM;AACpB,QAAI,iBAAiB;AACnB,oBAAc,gBAAgB,IAAI;AAClC,eAAS,IAAI;AACb,qBAAe,gBAAgB,MAAM;AAAA,IACvC;AAAA,EACF,GAAG,CAAC,eAAe,CAAC;AAEpB,QAAM,mBAAmB,MAAM;AAAA,IAC7B,CAAC,SAAwB;AACvB,UAAI,eAAe,MAAM;AAEvB,sBAAc,IAAI;AAClB,qBAAa,KAAK;AAClB,iBAAS,KAAK;AACd,iDAAa,aAAa;AAAA,MAC5B,OAAO;AACL,sBAAc,IAAI;AAClB,qBAAa,IAAI;AACjB,iBAAS,KAAK;AAAA,MAChB;AAAA,IACF;AAAA,IACA,CAAC,YAAY,aAAa,UAAU;AAAA,EACtC;AAEA,QAAM,qBAAqB,MAAM,YAAY,MAAM;AACjD,QAAI,CAAC,WAAY;AACjB,UAAM,OAAO,WAAW,KAAK;AAC7B,6CAAa,aAAa,YAAY;AACtC,aAAS,IAAI;AACb,mBAAe,IAAI;AACnB,iBAAa,KAAK;AAAA,EACpB,GAAG,CAAC,YAAY,YAAY,aAAa,UAAU,CAAC;AAEpD,SACE,qBAAC,SACE;AAAA,aAAS,CAAC;AAAA;AAAA,MAET;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM;AACb,0BAAc,WAAW;AACzB,yBAAa,IAAI;AACjB,qBAAS,KAAK;AAAA,UAChB;AAAA,UACA,WAAU;AAAA,UACV,eAAa,GAAG,YAAY,uBAAuB,WAAW;AAAA,UAE9D;AAAA,iCAAC,UAAK,WAAU,eAAe;AAAA;AAAA,cAAe;AAAA,eAAC;AAAA,YAC9C,eAAe,OACd,oBAAC,YAAS,WAAU,qBAAoB,IAExC,oBAAC,cAAW,WAAU,qBAAoB;AAAA,YAE3C,eACC,oBAAC,UAAK,WAAU,mDACb,uBACH;AAAA,YAEF,oBAAC,UAAO,WAAU,wEAAuE;AAAA;AAAA;AAAA,MAC3F;AAAA,QAEA,qBAAC,SAAI,WAAU,6BAEb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,iBAAiB,IAAI;AAAA,UACpC,WAAW;AAAA,YACT;AAAA,YACA,eAAe,OACX,6BACA;AAAA,UACN;AAAA,UACA,OAAM;AAAA,UACN,eAAa,GAAG,YAAY,aAAa,WAAW;AAAA,UAEpD,8BAAC,YAAS,WAAU,qBAAoB;AAAA;AAAA,MAC1C;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,iBAAiB,MAAM;AAAA,UACtC,WAAW;AAAA,YACT;AAAA,YACA,eAAe,SACX,2BACA;AAAA,UACN;AAAA,UACA,OAAM;AAAA,UACN,eAAa,GAAG,YAAY,eAAe,WAAW;AAAA,UAEtD,8BAAC,cAAW,WAAU,qBAAoB;AAAA;AAAA,MAC5C;AAAA,MAGC,SACC;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,MAAK;AAAA,UACL,eAAa,GAAG,YAAY,UAAU,WAAW;AAAA,UAEjD;AAAA,gCAAC,SAAM,WAAU,qBAAoB;AAAA,YAAE;AAAA;AAAA;AAAA,MAEzC;AAAA,OAEJ;AAAA,IAID,aAAa,cACZ,qBAAC,SAAI,WAAU,UACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,KAAK;AAAA,UAC7C,WAAW,CAAC,MAAM;AAChB,gBAAI,EAAE,QAAQ,QAAS,oBAAmB;AAC1C,gBAAI,EAAE,QAAQ,SAAU,cAAa,KAAK;AAAA,UAC5C;AAAA,UACA,aACE,eAAe,OACX,qCACA;AAAA,UAEN,WAAU;AAAA,UACV,eAAa,GAAG,YAAY,iBAAiB,WAAW;AAAA;AAAA,MAC1D;AAAA,MACA,qBAAC,SAAI,WAAU,kCACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAU;AAAA,YACV,eAAa,GAAG,YAAY,WAAW,WAAW;AAAA,YACnD;AAAA;AAAA,QAED;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,aAAa,KAAK;AAAA,YACjC,WAAU;AAAA,YACX;AAAA;AAAA,QAED;AAAA,SACF;AAAA,OACF;AAAA,KAEJ;AAEJ;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { Q as QueueItem, l as SignalScoreData, o as SignalScoreUrgencyLabel } from '../signal-priority-popover-
|
|
2
|
+
import { Q as QueueItem, l as SignalScoreData, o as SignalScoreUrgencyLabel } from '../signal-priority-popover-DWaAMhPI.js';
|
|
3
3
|
import './feedback-primitives.js';
|
|
4
4
|
import './quick-action-sidebar-nav.js';
|
|
5
5
|
import './quick-action-modal.js';
|
|
@@ -165,6 +165,7 @@ function StructuredSignalRow({ item, bucketKey, signal, tone, onOpenSignalBucket
|
|
|
165
165
|
const IconComponent = resolveIcon(signal.signalTypeName);
|
|
166
166
|
const toneClass = tone ? (_a = SIGNAL_TONE_CLASSES[tone]) != null ? _a : DEFAULT_TONE_CLASS : DEFAULT_TONE_CLASS;
|
|
167
167
|
const isCombined = signal.signalTypeName === "combined_signal" && signal.components && signal.components.length > 0;
|
|
168
|
+
const hasBalance = Boolean(signal.currentBalance || signal.balanceContext);
|
|
168
169
|
const rowContent = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
169
170
|
/* @__PURE__ */ jsx("div", { className: cn("flex h-5 w-5 shrink-0 items-center justify-center rounded", toneClass), children: /* @__PURE__ */ jsx(IconComponent, { className: "h-3 w-3" }) }),
|
|
170
171
|
/* @__PURE__ */ jsx("div", { className: "min-w-0", children: isCombined ? /* @__PURE__ */ jsx(CombinedSignalMiniChips, { components: signal.components }) : /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
|
|
@@ -173,7 +174,24 @@ function StructuredSignalRow({ item, bucketKey, signal, tone, onOpenSignalBucket
|
|
|
173
174
|
] }) }),
|
|
174
175
|
/* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsx("span", { className: "block truncate text-xs text-muted-foreground", children: slotValue(signal.counterparty) }) }),
|
|
175
176
|
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] text-muted-foreground/70", children: slotValue(signal.time) }),
|
|
176
|
-
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3 shrink-0 text-muted-foreground/60 transition-transform group-hover:translate-x-0.5 group-hover:text-foreground/50" })
|
|
177
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3 shrink-0 text-muted-foreground/60 transition-transform group-hover:translate-x-0.5 group-hover:text-foreground/50" }),
|
|
178
|
+
hasBalance && /* @__PURE__ */ jsxs(
|
|
179
|
+
"div",
|
|
180
|
+
{
|
|
181
|
+
className: "col-span-full mt-0.5 text-[10px] text-muted-foreground/70",
|
|
182
|
+
"data-testid": "balance-context-strip",
|
|
183
|
+
children: [
|
|
184
|
+
signal.currentBalance && /* @__PURE__ */ jsxs("span", { children: [
|
|
185
|
+
"Current balance ",
|
|
186
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-muted-foreground", children: signal.currentBalance })
|
|
187
|
+
] }),
|
|
188
|
+
signal.balanceContext && /* @__PURE__ */ jsxs("span", { children: [
|
|
189
|
+
signal.currentBalance ? " \xB7 " : "",
|
|
190
|
+
signal.balanceContext
|
|
191
|
+
] })
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
)
|
|
177
195
|
] });
|
|
178
196
|
if (signal.id && onOpenSignalBucket) {
|
|
179
197
|
return /* @__PURE__ */ jsx(
|
|
@@ -230,7 +248,7 @@ function hasStructuredData(signal) {
|
|
|
230
248
|
signal.primaryValue || signal.qualifier || signal.counterparty || signal.components && signal.components.length > 0
|
|
231
249
|
);
|
|
232
250
|
}
|
|
233
|
-
function WhyCard({ bucket, signals, item, panelId, onOpenSignalBucket, onBucketFeedback }) {
|
|
251
|
+
function WhyCard({ bucket, signals, item, panelId, onOpenSignalBucket, onBucketFeedback, initialBucketFeedback }) {
|
|
234
252
|
var _a;
|
|
235
253
|
const [showAll, setShowAll] = React.useState(false);
|
|
236
254
|
const [bucketFeedback, setBucketFeedback] = React.useState(null);
|
|
@@ -299,7 +317,9 @@ function WhyCard({ bucket, signals, item, panelId, onOpenSignalBucket, onBucketF
|
|
|
299
317
|
onSubmit: (data) => onBucketFeedback(bucket.key, data),
|
|
300
318
|
negativeChips: BUCKET_NEGATIVE_CHIPS,
|
|
301
319
|
negativePrompt: "Was this bucket useful?",
|
|
302
|
-
positivePrompt: "Thanks! What was useful about this bucket?"
|
|
320
|
+
positivePrompt: "Thanks! What was useful about this bucket?",
|
|
321
|
+
initialFeedback: initialBucketFeedback,
|
|
322
|
+
feedbackKey: bucket.key
|
|
303
323
|
}
|
|
304
324
|
) })
|
|
305
325
|
]
|
|
@@ -312,7 +332,7 @@ function ScoreWhyChips({
|
|
|
312
332
|
onOpenSignalBucket,
|
|
313
333
|
className
|
|
314
334
|
}) {
|
|
315
|
-
var _a;
|
|
335
|
+
var _a, _b;
|
|
316
336
|
const [selectedBucketKey, setSelectedBucketKey] = React.useState(null);
|
|
317
337
|
React.useEffect(() => {
|
|
318
338
|
setSelectedBucketKey(null);
|
|
@@ -352,7 +372,8 @@ function ScoreWhyChips({
|
|
|
352
372
|
item,
|
|
353
373
|
panelId: selectedPanelId,
|
|
354
374
|
onOpenSignalBucket,
|
|
355
|
-
onBucketFeedback: signalData.onBucketFeedback
|
|
375
|
+
onBucketFeedback: signalData.onBucketFeedback,
|
|
376
|
+
initialBucketFeedback: (_b = signalData.initialBucketFeedback) == null ? void 0 : _b[selectedBucket.key]
|
|
356
377
|
}
|
|
357
378
|
)
|
|
358
379
|
] });
|