@handled-ai/design-system 0.17.2 → 0.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/charts/chart.d.ts +1 -1
  2. package/dist/components/actor-byline.d.ts +3 -0
  3. package/dist/components/actor-byline.js +5 -0
  4. package/dist/components/actor-byline.js.map +1 -0
  5. package/dist/components/feedback-primitives.d.ts +21 -2
  6. package/dist/components/feedback-primitives.js +90 -6
  7. package/dist/components/feedback-primitives.js.map +1 -1
  8. package/dist/components/performance-metrics-table.d.ts +2 -1
  9. package/dist/components/performance-metrics-table.js +78 -46
  10. package/dist/components/performance-metrics-table.js.map +1 -1
  11. package/dist/components/score-why-chips.d.ts +1 -1
  12. package/dist/components/score-why-chips.js +26 -5
  13. package/dist/components/score-why-chips.js.map +1 -1
  14. package/dist/components/signal-priority-popover.d.ts +1 -1
  15. package/dist/components/signal-priority-popover.js +172 -7
  16. package/dist/components/signal-priority-popover.js.map +1 -1
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.js.map +1 -1
  19. package/dist/prototype/index.d.ts +1 -1
  20. package/dist/prototype/prototype-accounts-view.d.ts +1 -1
  21. package/dist/prototype/prototype-admin-view.d.ts +1 -1
  22. package/dist/prototype/prototype-config.d.ts +1 -1
  23. package/dist/prototype/prototype-inbox-view.d.ts +1 -1
  24. package/dist/prototype/prototype-inbox-view.js +4 -1
  25. package/dist/prototype/prototype-inbox-view.js.map +1 -1
  26. package/dist/prototype/prototype-insights-view.d.ts +1 -1
  27. package/dist/prototype/prototype-shell.d.ts +1 -1
  28. package/dist/{signal-priority-popover-DQ_VuHac.d.ts → signal-priority-popover-DWaAMhPI.d.ts} +26 -2
  29. package/package.json +3 -1
  30. package/src/components/__tests__/performance-metrics-table.test.tsx +54 -0
  31. package/src/components/__tests__/user-display.test.tsx +75 -0
  32. package/src/components/__tests__/wit-636-feedback-states.test.tsx +546 -0
  33. package/src/components/actor-byline.tsx +1 -0
  34. package/src/components/feedback-primitives.tsx +148 -26
  35. package/src/components/performance-metrics-table.tsx +99 -63
  36. package/src/components/score-why-chips.tsx +28 -2
  37. package/src/components/signal-priority-popover.tsx +194 -3
  38. package/src/index.ts +1 -1
  39. package/src/lib/__tests__/user-display.test.ts +53 -11
  40. package/src/prototype/prototype-config.ts +11 -1
  41. package/src/prototype/prototype-inbox-view.tsx +3 -0
@@ -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: typeof RechartsPrimitive.Legend;
51
+ declare const ChartLegend: React.MemoExoticComponent<(outsideProps: RechartsPrimitive.LegendProps) => React.ReactPortal | null>;
52
52
  type LegendPayloadItem = {
53
53
  value?: string;
54
54
  dataKey?: string;
@@ -0,0 +1,3 @@
1
+ export { ActorByline, ActorBylineProps } from './user-display.js';
2
+ import 'react';
3
+ import '../lib/user-display.js';
@@ -0,0 +1,5 @@
1
+ import { ActorByline } from "./user-display.js";
2
+ export {
3
+ ActorByline
4
+ };
5
+ //# sourceMappingURL=actor-byline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/actor-byline.tsx"],"sourcesContent":["export { ActorByline, type ActorBylineProps } from \"./user-display\"\n"],"mappings":"AAAA,SAAS,mBAA0C;","names":[]}
@@ -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,14 @@ 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;
63
82
  }
64
- declare function FeedbackFooter({ feedback, onFeedbackChange, onSubmit, metaText, positivePrompt, negativePrompt, negativeChips, positiveChips, className, }: FeedbackFooterProps): React.JSX.Element;
83
+ declare function FeedbackFooter({ feedback, onFeedbackChange, onSubmit, metaText, positivePrompt, negativePrompt, negativeChips, positiveChips, className, initialFeedback, submittedLabel, feedbackKey, }: FeedbackFooterProps): React.JSX.Element;
65
84
 
66
- export { FeedbackActions, type FeedbackActionsProps, FeedbackChipGroup, type FeedbackChipGroupProps, type FeedbackChipTree, FeedbackFooter, type FeedbackFooterProps, FeedbackInput, type FeedbackInputProps, type FeedbackSubmitData };
85
+ export { FeedbackActions, type FeedbackActionsProps, FeedbackChipGroup, type FeedbackChipGroupProps, type FeedbackChipTree, FeedbackFooter, type FeedbackFooterProps, FeedbackInput, type FeedbackInputProps, type FeedbackSubmitData, 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,32 @@ 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 [isEditing, setIsEditing] = React.useState(false);
128
+ const lastKeyRef = React.useRef(feedbackKey);
129
+ React.useEffect(() => {
130
+ const keyChanged = feedbackKey !== lastKeyRef.current;
131
+ lastKeyRef.current = feedbackKey;
132
+ if (keyChanged) {
133
+ setPersisted(initialFeedback != null ? initialFeedback : null);
134
+ setSubmitted(false);
135
+ setExpanded(false);
136
+ setIsEditing(false);
137
+ if (initialFeedback) {
138
+ onFeedbackChange(initialFeedback.sentiment);
139
+ } else {
140
+ onFeedbackChange(null);
141
+ }
142
+ } else if (!isEditing) {
143
+ setPersisted(initialFeedback != null ? initialFeedback : null);
144
+ if (initialFeedback) {
145
+ onFeedbackChange(initialFeedback.sentiment);
146
+ }
147
+ }
148
+ }, [initialFeedback, feedbackKey]);
120
149
  const resetState = React.useCallback(() => {
121
150
  setExpanded(false);
122
151
  setSelectedTier1(null);
@@ -124,15 +153,31 @@ function FeedbackFooter({
124
153
  setAdditionalPills([]);
125
154
  setDetailText("");
126
155
  setActiveTreeIndex(null);
156
+ setIsEditing(false);
127
157
  }, []);
128
158
  const handleSentimentClick = React.useCallback(
129
159
  (sentiment) => {
130
160
  onFeedbackChange(sentiment);
131
161
  resetState();
132
162
  setExpanded(true);
163
+ setSubmitted(false);
164
+ setPersisted(null);
165
+ setIsEditing(true);
133
166
  },
134
167
  [onFeedbackChange, resetState]
135
168
  );
169
+ const handlePersistedClick = React.useCallback(() => {
170
+ var _a, _b, _c, _d;
171
+ if (!persisted) return;
172
+ onFeedbackChange(persisted.sentiment);
173
+ setSelectedTier1((_a = persisted.reasonTop) != null ? _a : null);
174
+ setSelectedTier2((_b = persisted.reasonSub) != null ? _b : null);
175
+ setAdditionalPills((_c = persisted.pills) != null ? _c : []);
176
+ setDetailText((_d = persisted.detail) != null ? _d : "");
177
+ setExpanded(true);
178
+ setSubmitted(false);
179
+ setIsEditing(true);
180
+ }, [persisted, onFeedbackChange]);
136
181
  const handleTier1Toggle = React.useCallback(
137
182
  (chipLabel) => {
138
183
  if (selectedTier1 === chipLabel) {
@@ -182,15 +227,21 @@ function FeedbackFooter({
182
227
  pills: additionalPills,
183
228
  detail: detailText
184
229
  });
185
- resetState();
230
+ setSubmitted(true);
231
+ setExpanded(false);
232
+ setSelectedTier1(null);
233
+ setSelectedTier2(null);
234
+ setAdditionalPills([]);
235
+ setDetailText("");
236
+ setActiveTreeIndex(null);
237
+ setIsEditing(false);
186
238
  }, [
187
239
  feedback,
188
240
  selectedTier1,
189
241
  selectedTier2,
190
242
  additionalPills,
191
243
  detailText,
192
- onSubmit,
193
- resetState
244
+ onSubmit
194
245
  ]);
195
246
  const handleCancel = React.useCallback(() => {
196
247
  resetState();
@@ -203,9 +254,30 @@ function FeedbackFooter({
203
254
  return result;
204
255
  }, [selectedTier1, additionalPills]);
205
256
  const activeTree = activeTreeIndex !== null ? negativeChips[activeTreeIndex] : null;
257
+ const showPersistedIndicator = persisted && !expanded && !submitted;
206
258
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
207
259
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
208
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
260
+ showPersistedIndicator ? (
261
+ /* Persisted feedback indicator — clickable to reopen editor */
262
+ /* @__PURE__ */ jsxs(
263
+ "button",
264
+ {
265
+ type: "button",
266
+ onClick: handlePersistedClick,
267
+ className: "group flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors",
268
+ "data-testid": "persisted-feedback-indicator",
269
+ children: [
270
+ /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
271
+ persisted.ownershipLabel,
272
+ ":"
273
+ ] }),
274
+ persisted.sentiment === "positive" ? /* @__PURE__ */ jsx(ThumbsUp, { className: "h-[11px] w-[11px]" }) : /* @__PURE__ */ jsx(ThumbsDown, { className: "h-[11px] w-[11px]" }),
275
+ persisted.detail && /* @__PURE__ */ jsx("span", { className: "max-w-[200px] truncate text-muted-foreground/70", children: persisted.detail }),
276
+ /* @__PURE__ */ jsx(Pencil, { className: "h-[9px] w-[9px] opacity-0 group-hover:opacity-100 transition-opacity" })
277
+ ]
278
+ }
279
+ )
280
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
209
281
  /* @__PURE__ */ jsxs(
210
282
  "button",
211
283
  {
@@ -235,6 +307,18 @@ function FeedbackFooter({
235
307
  "Not helpful"
236
308
  ]
237
309
  }
310
+ ),
311
+ submitted && feedback && /* @__PURE__ */ jsxs(
312
+ "span",
313
+ {
314
+ className: "inline-flex items-center gap-1 text-[11px] font-medium text-emerald-600",
315
+ role: "status",
316
+ "data-testid": "feedback-submitted-pill",
317
+ children: [
318
+ /* @__PURE__ */ jsx(Check, { className: "h-[11px] w-[11px]" }),
319
+ submittedLabel
320
+ ]
321
+ }
238
322
  )
239
323
  ] }),
240
324
  metaText && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: metaText })
@@ -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 (to guard against prop overwrites). */\n const [isEditing, setIsEditing] = React.useState(false)\n /** Track the last synced feedbackKey to detect key changes. */\n const lastKeyRef = React.useRef<string | undefined>(feedbackKey)\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 setIsEditing(false)\n if (initialFeedback) {\n onFeedbackChange(initialFeedback.sentiment)\n } else {\n onFeedbackChange(null)\n }\n } else if (!isEditing) {\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]) // eslint-disable-line react-hooks/exhaustive-deps -- reads isEditing as guard, not trigger\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 }, [])\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],\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])\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 setExpanded(false)\n setSelectedTier1(null)\n setSelectedTier2(null)\n setAdditionalPills([])\n setDetailText(\"\")\n setActiveTreeIndex(null)\n setIsEditing(false)\n }, [\n feedback,\n selectedTier1,\n selectedTier2,\n additionalPills,\n detailText,\n onSubmit,\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"],"mappings":"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","names":[]}
@@ -21,6 +21,7 @@ interface PerformanceMetricsTableProps {
21
21
  title?: string;
22
22
  entityColumnLabel?: string;
23
23
  primaryMetricColumnLabel?: string;
24
+ primaryMetricDisplayMode?: "progress" | "value";
24
25
  rateColumnLabel?: string;
25
26
  metricOneColumnLabel?: string;
26
27
  metricTwoColumnLabel?: string;
@@ -33,6 +34,6 @@ interface PerformanceMetricsTableProps {
33
34
  pageSize?: number;
34
35
  searchPlaceholder?: string;
35
36
  }
36
- declare function PerformanceMetricsTable({ title, entityColumnLabel, primaryMetricColumnLabel, rateColumnLabel, metricOneColumnLabel, metricTwoColumnLabel, metricThreeColumnLabel, metricFourColumnLabel, viewOptions, roleOptions, sortOptions, rows, pageSize, searchPlaceholder, }: PerformanceMetricsTableProps): React.JSX.Element;
37
+ declare function PerformanceMetricsTable({ title, entityColumnLabel, primaryMetricColumnLabel, primaryMetricDisplayMode, rateColumnLabel, metricOneColumnLabel, metricTwoColumnLabel, metricThreeColumnLabel, metricFourColumnLabel, viewOptions, roleOptions, sortOptions, rows, pageSize, searchPlaceholder, }: PerformanceMetricsTableProps): React.JSX.Element;
37
38
 
38
39
  export { PerformanceMetricsTable, type PerformanceMetricsTableRow, type PerformanceMetricsTableSortOption };
@@ -137,6 +137,37 @@ function getProgressStatus(value, target) {
137
137
  icon: /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 text-red-600" })
138
138
  };
139
139
  }
140
+ function PrimaryMetricCell({
141
+ row,
142
+ displayMode
143
+ }) {
144
+ if (displayMode === "value") {
145
+ return /* @__PURE__ */ jsx("div", { className: "text-sm font-bold text-foreground", children: row.primaryValue });
146
+ }
147
+ const percentage = row.primaryValue / row.primaryTarget * 100;
148
+ const progress = getProgressStatus(row.primaryValue, row.primaryTarget);
149
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
150
+ /* @__PURE__ */ jsx("span", { className: "shrink-0", children: progress.icon }),
151
+ /* @__PURE__ */ jsxs("div", { className: "w-full max-w-[180px]", children: [
152
+ /* @__PURE__ */ jsxs("div", { className: "mb-1 text-sm font-bold text-foreground", children: [
153
+ row.primaryValue,
154
+ "/",
155
+ row.primaryTarget
156
+ ] }),
157
+ /* @__PURE__ */ jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
158
+ "div",
159
+ {
160
+ className: cn("h-full rounded-full", progress.color),
161
+ style: { width: `${Math.min(100, percentage)}%` }
162
+ }
163
+ ) }),
164
+ /* @__PURE__ */ jsxs("div", { className: cn("mt-1 text-xs font-medium", progress.textColor), children: [
165
+ Math.round(percentage),
166
+ "%"
167
+ ] })
168
+ ] })
169
+ ] });
170
+ }
140
171
  function sortRows(rows, sortId) {
141
172
  const copy = [...rows];
142
173
  switch (sortId) {
@@ -155,13 +186,19 @@ function PerformanceMetricsTable({
155
186
  title = "Performance Table",
156
187
  entityColumnLabel = "Entity",
157
188
  primaryMetricColumnLabel = "Primary Goal",
189
+ primaryMetricDisplayMode = "progress",
158
190
  rateColumnLabel = "Rate",
159
191
  metricOneColumnLabel = "Metric One",
160
192
  metricTwoColumnLabel = "Metric Two",
161
193
  metricThreeColumnLabel = "Metric Three",
162
194
  metricFourColumnLabel = "Metric Four",
163
195
  viewOptions = ["By Entity"],
164
- roleOptions = ["All", "Senior Coordinator", "Coordinator", "Junior Coordinator"],
196
+ roleOptions = [
197
+ "All",
198
+ "Senior Coordinator",
199
+ "Coordinator",
200
+ "Junior Coordinator"
201
+ ],
165
202
  sortOptions = DEFAULT_SORT_OPTIONS,
166
203
  rows = DEFAULT_ROWS,
167
204
  pageSize = 6,
@@ -169,9 +206,7 @@ function PerformanceMetricsTable({
169
206
  }) {
170
207
  var _a, _b, _c, _d, _e, _f;
171
208
  const [view, setView] = React.useState((_a = viewOptions[0]) != null ? _a : "By Entity");
172
- const [sortId, setSortId] = React.useState(
173
- (_c = (_b = sortOptions[0]) == null ? void 0 : _b.id) != null ? _c : "primary-desc"
174
- );
209
+ const [sortId, setSortId] = React.useState((_c = (_b = sortOptions[0]) == null ? void 0 : _b.id) != null ? _c : "primary-desc");
175
210
  const [roleFilter, setRoleFilter] = React.useState((_d = roleOptions[0]) != null ? _d : "All");
176
211
  const [search, setSearch] = React.useState("");
177
212
  const [page, setPage] = React.useState(1);
@@ -218,7 +253,14 @@ function PerformanceMetricsTable({
218
253
  sortLabel.replace(/\s*\(.+\)/, ""),
219
254
  /* @__PURE__ */ jsx(ChevronDown, { className: "ml-2 h-3.5 w-3.5" })
220
255
  ] }) }),
221
- /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", className: "w-56", children: sortOptions.map((option) => /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => setSortId(option.id), children: option.label }, option.id)) })
256
+ /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", className: "w-56", children: sortOptions.map((option) => /* @__PURE__ */ jsx(
257
+ DropdownMenuItem,
258
+ {
259
+ onClick: () => setSortId(option.id),
260
+ children: option.label
261
+ },
262
+ option.id
263
+ )) })
222
264
  ] }),
223
265
  /* @__PURE__ */ jsxs(DropdownMenu, { children: [
224
266
  /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "h-8 text-xs", children: [
@@ -226,7 +268,14 @@ function PerformanceMetricsTable({
226
268
  roleFilter,
227
269
  /* @__PURE__ */ jsx(ChevronDown, { className: "ml-2 h-3.5 w-3.5" })
228
270
  ] }) }),
229
- /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", children: roleOptions.map((option) => /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => setRoleFilter(option), children: option }, option)) })
271
+ /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", children: roleOptions.map((option) => /* @__PURE__ */ jsx(
272
+ DropdownMenuItem,
273
+ {
274
+ onClick: () => setRoleFilter(option),
275
+ children: option
276
+ },
277
+ option
278
+ )) })
230
279
  ] }),
231
280
  /* @__PURE__ */ jsx(
232
281
  Input,
@@ -250,45 +299,27 @@ function PerformanceMetricsTable({
250
299
  /* @__PURE__ */ jsx(TableHead, { className: "px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: metricThreeColumnLabel }),
251
300
  /* @__PURE__ */ jsx(TableHead, { className: "px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: metricFourColumnLabel })
252
301
  ] }) }),
253
- /* @__PURE__ */ jsx(TableBody, { children: paginatedRows.map((row) => {
254
- const percentage = row.primaryValue / row.primaryTarget * 100;
255
- const progress = getProgressStatus(row.primaryValue, row.primaryTarget);
256
- return /* @__PURE__ */ jsxs(TableRow, { className: "hover:bg-muted/30", children: [
257
- /* @__PURE__ */ jsx(TableCell, { className: "px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
258
- /* @__PURE__ */ jsx(Avatar, { className: "h-8 w-8 border border-border", children: /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-emerald-100 text-[11px] font-medium text-emerald-700", children: row.avatarFallback }) }),
259
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: row.label })
260
- ] }) }),
261
- /* @__PURE__ */ jsx(TableCell, { className: "px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
262
- /* @__PURE__ */ jsx("span", { className: "shrink-0", children: progress.icon }),
263
- /* @__PURE__ */ jsxs("div", { className: "w-full max-w-[180px]", children: [
264
- /* @__PURE__ */ jsxs("div", { className: "mb-1 text-sm font-bold text-foreground", children: [
265
- row.primaryValue,
266
- "/",
267
- row.primaryTarget
268
- ] }),
269
- /* @__PURE__ */ jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
270
- "div",
271
- {
272
- className: cn("h-full rounded-full", progress.color),
273
- style: { width: `${Math.min(100, percentage)}%` }
274
- }
275
- ) }),
276
- /* @__PURE__ */ jsxs("div", { className: cn("mt-1 text-xs font-medium", progress.textColor), children: [
277
- Math.round(percentage),
278
- "%"
279
- ] })
280
- ] })
281
- ] }) }),
282
- /* @__PURE__ */ jsxs(TableCell, { className: "px-4 py-3 text-right text-sm font-semibold text-emerald-600", children: [
283
- row.ratePercent,
284
- "%"
285
- ] }),
286
- /* @__PURE__ */ jsx(TableCell, { className: "px-4 py-3 text-right text-sm font-medium text-foreground", children: row.metricOne }),
287
- /* @__PURE__ */ jsx(TableCell, { className: "px-4 py-3 text-right text-sm text-muted-foreground", children: row.metricTwo }),
288
- /* @__PURE__ */ jsx(TableCell, { className: "px-4 py-3 text-right text-sm font-medium text-foreground", children: row.metricThree }),
289
- /* @__PURE__ */ jsx(TableCell, { className: "px-4 py-3 text-right text-sm font-medium text-foreground", children: row.metricFour })
290
- ] }, row.id);
291
- }) })
302
+ /* @__PURE__ */ jsx(TableBody, { children: paginatedRows.map((row) => /* @__PURE__ */ jsxs(TableRow, { className: "hover:bg-muted/30", children: [
303
+ /* @__PURE__ */ jsx(TableCell, { className: "px-4 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
304
+ /* @__PURE__ */ jsx(Avatar, { className: "h-8 w-8 border border-border", children: /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-emerald-100 text-[11px] font-medium text-emerald-700", children: row.avatarFallback }) }),
305
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: row.label })
306
+ ] }) }),
307
+ /* @__PURE__ */ jsx(TableCell, { className: "px-4 py-3", children: /* @__PURE__ */ jsx(
308
+ PrimaryMetricCell,
309
+ {
310
+ row,
311
+ displayMode: primaryMetricDisplayMode
312
+ }
313
+ ) }),
314
+ /* @__PURE__ */ jsxs(TableCell, { className: "px-4 py-3 text-right text-sm font-semibold text-emerald-600", children: [
315
+ row.ratePercent,
316
+ "%"
317
+ ] }),
318
+ /* @__PURE__ */ jsx(TableCell, { className: "px-4 py-3 text-right text-sm font-medium text-foreground", children: row.metricOne }),
319
+ /* @__PURE__ */ jsx(TableCell, { className: "px-4 py-3 text-right text-sm text-muted-foreground", children: row.metricTwo }),
320
+ /* @__PURE__ */ jsx(TableCell, { className: "px-4 py-3 text-right text-sm font-medium text-foreground", children: row.metricThree }),
321
+ /* @__PURE__ */ jsx(TableCell, { className: "px-4 py-3 text-right text-sm font-medium text-foreground", children: row.metricFour })
322
+ ] }, row.id)) })
292
323
  ] }) }),
293
324
  /* @__PURE__ */ jsx(ScrollBar, { orientation: "horizontal" })
294
325
  ] }),
@@ -301,7 +332,8 @@ function PerformanceMetricsTable({
301
332
  Math.min(start + pageSize, sortedRows.length),
302
333
  " of ",
303
334
  sortedRows.length,
304
- " rows"
335
+ " ",
336
+ "rows"
305
337
  ] }),
306
338
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
307
339
  /* @__PURE__ */ jsxs(