@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.
- package/dist/charts/chart.d.ts +1 -1
- package/dist/components/actor-byline.d.ts +3 -0
- package/dist/components/actor-byline.js +5 -0
- package/dist/components/actor-byline.js.map +1 -0
- package/dist/components/feedback-primitives.d.ts +21 -2
- package/dist/components/feedback-primitives.js +90 -6
- package/dist/components/feedback-primitives.js.map +1 -1
- package/dist/components/performance-metrics-table.d.ts +2 -1
- package/dist/components/performance-metrics-table.js +78 -46
- package/dist/components/performance-metrics-table.js.map +1 -1
- package/dist/components/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 +172 -7
- package/dist/components/signal-priority-popover.js.map +1 -1
- package/dist/index.d.ts +2 -2
- 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 +1 -1
- package/dist/prototype/prototype-inbox-view.js +4 -1
- 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__/performance-metrics-table.test.tsx +54 -0
- package/src/components/__tests__/user-display.test.tsx +75 -0
- package/src/components/__tests__/wit-636-feedback-states.test.tsx +546 -0
- package/src/components/actor-byline.tsx +1 -0
- package/src/components/feedback-primitives.tsx +148 -26
- package/src/components/performance-metrics-table.tsx +99 -63
- package/src/components/score-why-chips.tsx +28 -2
- package/src/components/signal-priority-popover.tsx +194 -3
- package/src/index.ts +1 -1
- package/src/lib/__tests__/user-display.test.ts +53 -11
- package/src/prototype/prototype-config.ts +11 -1
- package/src/prototype/prototype-inbox-view.tsx +3 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/performance-metrics-table.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n AlertTriangle,\n CheckCircle2,\n ChevronDown,\n ChevronLeft,\n ChevronRight,\n} from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { Avatar, AvatarFallback } from \"./avatar\"\nimport { Button } from \"./button\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"./dropdown-menu\"\nimport { Input } from \"./input\"\nimport { ScrollArea, ScrollBar } from \"./scroll-area\"\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"./table\"\n\nexport interface PerformanceMetricsTableRow {\n id: string\n label: string\n avatarFallback: string\n role?: string\n primaryValue: number\n primaryTarget: number\n ratePercent: number\n metricOne: number\n metricTwo: string\n metricThree: number\n metricFour: number\n}\n\nexport interface PerformanceMetricsTableSortOption {\n id: \"primary-desc\" | \"primary-asc\" | \"rate-desc\" | \"metric-four-desc\"\n label: string\n}\n\ninterface PerformanceMetricsTableProps {\n title?: string\n entityColumnLabel?: string\n primaryMetricColumnLabel?: string\n rateColumnLabel?: string\n metricOneColumnLabel?: string\n metricTwoColumnLabel?: string\n metricThreeColumnLabel?: string\n metricFourColumnLabel?: string\n viewOptions?: string[]\n roleOptions?: string[]\n sortOptions?: PerformanceMetricsTableSortOption[]\n rows?: PerformanceMetricsTableRow[]\n pageSize?: number\n searchPlaceholder?: string\n}\n\nconst DEFAULT_ROWS: PerformanceMetricsTableRow[] = [\n {\n id: \"member-1\",\n label: \"Jennifer Davis\",\n avatarFallback: \"JD\",\n role: \"Senior Coordinator\",\n primaryValue: 188,\n primaryTarget: 200,\n ratePercent: 78,\n metricOne: 256,\n metricTwo: \"8.5h\",\n metricThree: 401,\n metricFour: 42,\n },\n {\n id: \"member-2\",\n label: \"Robert Taylor\",\n avatarFallback: \"RT\",\n role: \"Coordinator\",\n primaryValue: 168,\n primaryTarget: 200,\n ratePercent: 70,\n metricOne: 210,\n metricTwo: \"7.4h\",\n metricThree: 330,\n metricFour: 36,\n },\n {\n id: \"member-3\",\n label: \"Karen Park\",\n avatarFallback: \"KP\",\n role: \"Coordinator\",\n primaryValue: 165,\n primaryTarget: 200,\n ratePercent: 68,\n metricOne: 195,\n metricTwo: \"6.9h\",\n metricThree: 298,\n metricFour: 33,\n },\n {\n id: \"member-4\",\n label: \"Alex Chen\",\n avatarFallback: \"AC\",\n role: \"Junior Coordinator\",\n primaryValue: 142,\n primaryTarget: 200,\n ratePercent: 65,\n metricOne: 201,\n metricTwo: \"7.2h\",\n metricThree: 315,\n metricFour: 29,\n },\n {\n id: \"member-5\",\n label: \"Sarah Mitchell\",\n avatarFallback: \"SM\",\n role: \"Senior Coordinator\",\n primaryValue: 130,\n primaryTarget: 200,\n ratePercent: 76,\n metricOne: 247,\n metricTwo: \"8.2h\",\n metricThree: 389,\n metricFour: 31,\n },\n {\n id: \"member-6\",\n label: \"Mike Rodriguez\",\n avatarFallback: \"MR\",\n role: \"Coordinator\",\n primaryValue: 115,\n primaryTarget: 200,\n ratePercent: 72,\n metricOne: 218,\n metricTwo: \"7.8h\",\n metricThree: 342,\n metricFour: 25,\n },\n]\n\nconst DEFAULT_SORT_OPTIONS: PerformanceMetricsTableSortOption[] = [\n { id: \"primary-desc\", label: \"Primary Metric (High to Low)\" },\n { id: \"primary-asc\", label: \"Primary Metric (Low to High)\" },\n { id: \"rate-desc\", label: \"Rate (High to Low)\" },\n { id: \"metric-four-desc\", label: \"Metric Four (High to Low)\" },\n]\n\nfunction getProgressStatus(value: number, target: number) {\n const percent = (value / target) * 100\n\n if (percent >= 80) {\n return {\n color: \"bg-emerald-500\",\n textColor: \"text-emerald-700\",\n icon: <CheckCircle2 className=\"h-3.5 w-3.5 text-emerald-600\" />,\n }\n }\n\n if (percent >= 65) {\n return {\n color: \"bg-amber-500\",\n textColor: \"text-amber-700\",\n icon: <AlertTriangle className=\"h-3.5 w-3.5 text-amber-600\" />,\n }\n }\n\n return {\n color: \"bg-red-500\",\n textColor: \"text-red-700\",\n icon: <AlertTriangle className=\"h-3.5 w-3.5 text-red-600\" />,\n }\n}\n\nfunction sortRows(\n rows: PerformanceMetricsTableRow[],\n sortId: PerformanceMetricsTableSortOption[\"id\"]\n) {\n const copy = [...rows]\n switch (sortId) {\n case \"primary-asc\":\n return copy.sort((a, b) => a.primaryValue - b.primaryValue)\n case \"rate-desc\":\n return copy.sort((a, b) => b.ratePercent - a.ratePercent)\n case \"metric-four-desc\":\n return copy.sort((a, b) => b.metricFour - a.metricFour)\n case \"primary-desc\":\n default:\n return copy.sort((a, b) => b.primaryValue - a.primaryValue)\n }\n}\n\nexport function PerformanceMetricsTable({\n title = \"Performance Table\",\n entityColumnLabel = \"Entity\",\n primaryMetricColumnLabel = \"Primary Goal\",\n rateColumnLabel = \"Rate\",\n metricOneColumnLabel = \"Metric One\",\n metricTwoColumnLabel = \"Metric Two\",\n metricThreeColumnLabel = \"Metric Three\",\n metricFourColumnLabel = \"Metric Four\",\n viewOptions = [\"By Entity\"],\n roleOptions = [\"All\", \"Senior Coordinator\", \"Coordinator\", \"Junior Coordinator\"],\n sortOptions = DEFAULT_SORT_OPTIONS,\n rows = DEFAULT_ROWS,\n pageSize = 6,\n searchPlaceholder = \"Search rows...\",\n}: PerformanceMetricsTableProps) {\n const [view, setView] = React.useState(viewOptions[0] ?? \"By Entity\")\n const [sortId, setSortId] =\n React.useState<PerformanceMetricsTableSortOption[\"id\"]>(\n sortOptions[0]?.id ?? \"primary-desc\"\n )\n const [roleFilter, setRoleFilter] = React.useState(roleOptions[0] ?? \"All\")\n const [search, setSearch] = React.useState(\"\")\n const [page, setPage] = React.useState(1)\n\n const filteredRows = React.useMemo(() => {\n const normalized = search.trim().toLowerCase()\n return rows.filter((row) => {\n if (roleFilter !== \"All\" && row.role !== roleFilter) {\n return false\n }\n if (!normalized) {\n return true\n }\n return row.label.toLowerCase().includes(normalized)\n })\n }, [roleFilter, rows, search])\n\n const sortedRows = React.useMemo(\n () => sortRows(filteredRows, sortId),\n [filteredRows, sortId]\n )\n\n const pageCount = Math.max(1, Math.ceil(sortedRows.length / pageSize))\n const start = (page - 1) * pageSize\n const paginatedRows = sortedRows.slice(start, start + pageSize)\n\n React.useEffect(() => {\n setPage(1)\n }, [search, roleFilter, sortId, view])\n\n React.useEffect(() => {\n setPage((previous) => Math.min(previous, pageCount))\n }, [pageCount])\n\n const sortLabel =\n sortOptions.find((option) => option.id === sortId)?.label ?? \"Sort\"\n\n return (\n <div className=\"overflow-hidden rounded-xl border border-border bg-card\">\n <div className=\"flex items-center justify-between border-b border-border px-4 py-3\">\n <h3 className=\"text-sm font-semibold text-foreground\">{title}</h3>\n <div className=\"flex items-center gap-2\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"h-8 text-xs\">\n {view}\n <ChevronDown className=\"ml-2 h-3.5 w-3.5\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent>\n {viewOptions.map((option) => (\n <DropdownMenuItem key={option} onClick={() => setView(option)}>\n {option}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"h-8 text-xs\">\n Sort: {sortLabel.replace(/\\s*\\(.+\\)/, \"\")}\n <ChevronDown className=\"ml-2 h-3.5 w-3.5\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-56\">\n {sortOptions.map((option) => (\n <DropdownMenuItem key={option.id} onClick={() => setSortId(option.id)}>\n {option.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"h-8 text-xs\">\n Role: {roleFilter}\n <ChevronDown className=\"ml-2 h-3.5 w-3.5\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {roleOptions.map((option) => (\n <DropdownMenuItem key={option} onClick={() => setRoleFilter(option)}>\n {option}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n <Input\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n placeholder={searchPlaceholder}\n className=\"h-8 w-48 text-xs\"\n />\n </div>\n </div>\n\n <ScrollArea>\n <div className=\"min-w-[1180px]\">\n <Table>\n <TableHeader className=\"bg-muted/30\">\n <TableRow className=\"hover:bg-transparent\">\n <TableHead className=\"px-4 py-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {entityColumnLabel}\n </TableHead>\n <TableHead className=\"w-[260px] px-4 py-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {primaryMetricColumnLabel}\n </TableHead>\n <TableHead className=\"px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {rateColumnLabel}\n </TableHead>\n <TableHead className=\"px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {metricOneColumnLabel}\n </TableHead>\n <TableHead className=\"px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {metricTwoColumnLabel}\n </TableHead>\n <TableHead className=\"px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {metricThreeColumnLabel}\n </TableHead>\n <TableHead className=\"px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {metricFourColumnLabel}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {paginatedRows.map((row) => {\n const percentage = (row.primaryValue / row.primaryTarget) * 100\n const progress = getProgressStatus(row.primaryValue, row.primaryTarget)\n\n return (\n <TableRow key={row.id} className=\"hover:bg-muted/30\">\n <TableCell className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3\">\n <Avatar className=\"h-8 w-8 border border-border\">\n <AvatarFallback className=\"bg-emerald-100 text-[11px] font-medium text-emerald-700\">\n {row.avatarFallback}\n </AvatarFallback>\n </Avatar>\n <span className=\"text-sm font-medium text-foreground\">{row.label}</span>\n </div>\n </TableCell>\n\n <TableCell className=\"px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <span className=\"shrink-0\">{progress.icon}</span>\n <div className=\"w-full max-w-[180px]\">\n <div className=\"mb-1 text-sm font-bold text-foreground\">\n {row.primaryValue}/{row.primaryTarget}\n </div>\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <div\n className={cn(\"h-full rounded-full\", progress.color)}\n style={{ width: `${Math.min(100, percentage)}%` }}\n />\n </div>\n <div className={cn(\"mt-1 text-xs font-medium\", progress.textColor)}>\n {Math.round(percentage)}%\n </div>\n </div>\n </div>\n </TableCell>\n\n <TableCell className=\"px-4 py-3 text-right text-sm font-semibold text-emerald-600\">\n {row.ratePercent}%\n </TableCell>\n <TableCell className=\"px-4 py-3 text-right text-sm font-medium text-foreground\">\n {row.metricOne}\n </TableCell>\n <TableCell className=\"px-4 py-3 text-right text-sm text-muted-foreground\">\n {row.metricTwo}\n </TableCell>\n <TableCell className=\"px-4 py-3 text-right text-sm font-medium text-foreground\">\n {row.metricThree}\n </TableCell>\n <TableCell className=\"px-4 py-3 text-right text-sm font-medium text-foreground\">\n {row.metricFour}\n </TableCell>\n </TableRow>\n )\n })}\n </TableBody>\n </Table>\n </div>\n <ScrollBar orientation=\"horizontal\" />\n </ScrollArea>\n\n <div className=\"flex items-center justify-between border-t border-border bg-muted/20 px-4 py-3\">\n <span className=\"text-xs text-muted-foreground\">\n Showing {sortedRows.length === 0 ? 0 : start + 1} to{\" \"}\n {Math.min(start + pageSize, sortedRows.length)} of {sortedRows.length} rows\n </span>\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={page <= 1}\n onClick={() => setPage((previous) => Math.max(previous - 1, 1))}\n >\n <ChevronLeft className=\"mr-1 h-3.5 w-3.5\" />\n Previous\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={page >= pageCount}\n onClick={() =>\n setPage((previous) => Math.min(previous + 1, pageCount))\n }\n >\n Next\n <ChevronRight className=\"ml-1 h-3.5 w-3.5\" />\n </Button>\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/performance-metrics-table.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n AlertTriangle,\n CheckCircle2,\n ChevronDown,\n ChevronLeft,\n ChevronRight,\n} from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { Avatar, AvatarFallback } from \"./avatar\"\nimport { Button } from \"./button\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"./dropdown-menu\"\nimport { Input } from \"./input\"\nimport { ScrollArea, ScrollBar } from \"./scroll-area\"\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"./table\"\n\nexport interface PerformanceMetricsTableRow {\n id: string\n label: string\n avatarFallback: string\n role?: string\n primaryValue: number\n primaryTarget: number\n ratePercent: number\n metricOne: number\n metricTwo: string\n metricThree: number\n metricFour: number\n}\n\nexport interface PerformanceMetricsTableSortOption {\n id: \"primary-desc\" | \"primary-asc\" | \"rate-desc\" | \"metric-four-desc\"\n label: string\n}\n\ninterface PerformanceMetricsTableProps {\n title?: string\n entityColumnLabel?: string\n primaryMetricColumnLabel?: string\n primaryMetricDisplayMode?: \"progress\" | \"value\"\n rateColumnLabel?: string\n metricOneColumnLabel?: string\n metricTwoColumnLabel?: string\n metricThreeColumnLabel?: string\n metricFourColumnLabel?: string\n viewOptions?: string[]\n roleOptions?: string[]\n sortOptions?: PerformanceMetricsTableSortOption[]\n rows?: PerformanceMetricsTableRow[]\n pageSize?: number\n searchPlaceholder?: string\n}\n\nconst DEFAULT_ROWS: PerformanceMetricsTableRow[] = [\n {\n id: \"member-1\",\n label: \"Jennifer Davis\",\n avatarFallback: \"JD\",\n role: \"Senior Coordinator\",\n primaryValue: 188,\n primaryTarget: 200,\n ratePercent: 78,\n metricOne: 256,\n metricTwo: \"8.5h\",\n metricThree: 401,\n metricFour: 42,\n },\n {\n id: \"member-2\",\n label: \"Robert Taylor\",\n avatarFallback: \"RT\",\n role: \"Coordinator\",\n primaryValue: 168,\n primaryTarget: 200,\n ratePercent: 70,\n metricOne: 210,\n metricTwo: \"7.4h\",\n metricThree: 330,\n metricFour: 36,\n },\n {\n id: \"member-3\",\n label: \"Karen Park\",\n avatarFallback: \"KP\",\n role: \"Coordinator\",\n primaryValue: 165,\n primaryTarget: 200,\n ratePercent: 68,\n metricOne: 195,\n metricTwo: \"6.9h\",\n metricThree: 298,\n metricFour: 33,\n },\n {\n id: \"member-4\",\n label: \"Alex Chen\",\n avatarFallback: \"AC\",\n role: \"Junior Coordinator\",\n primaryValue: 142,\n primaryTarget: 200,\n ratePercent: 65,\n metricOne: 201,\n metricTwo: \"7.2h\",\n metricThree: 315,\n metricFour: 29,\n },\n {\n id: \"member-5\",\n label: \"Sarah Mitchell\",\n avatarFallback: \"SM\",\n role: \"Senior Coordinator\",\n primaryValue: 130,\n primaryTarget: 200,\n ratePercent: 76,\n metricOne: 247,\n metricTwo: \"8.2h\",\n metricThree: 389,\n metricFour: 31,\n },\n {\n id: \"member-6\",\n label: \"Mike Rodriguez\",\n avatarFallback: \"MR\",\n role: \"Coordinator\",\n primaryValue: 115,\n primaryTarget: 200,\n ratePercent: 72,\n metricOne: 218,\n metricTwo: \"7.8h\",\n metricThree: 342,\n metricFour: 25,\n },\n]\n\nconst DEFAULT_SORT_OPTIONS: PerformanceMetricsTableSortOption[] = [\n { id: \"primary-desc\", label: \"Primary Metric (High to Low)\" },\n { id: \"primary-asc\", label: \"Primary Metric (Low to High)\" },\n { id: \"rate-desc\", label: \"Rate (High to Low)\" },\n { id: \"metric-four-desc\", label: \"Metric Four (High to Low)\" },\n]\n\nfunction getProgressStatus(value: number, target: number) {\n const percent = (value / target) * 100\n\n if (percent >= 80) {\n return {\n color: \"bg-emerald-500\",\n textColor: \"text-emerald-700\",\n icon: <CheckCircle2 className=\"h-3.5 w-3.5 text-emerald-600\" />,\n }\n }\n\n if (percent >= 65) {\n return {\n color: \"bg-amber-500\",\n textColor: \"text-amber-700\",\n icon: <AlertTriangle className=\"h-3.5 w-3.5 text-amber-600\" />,\n }\n }\n\n return {\n color: \"bg-red-500\",\n textColor: \"text-red-700\",\n icon: <AlertTriangle className=\"h-3.5 w-3.5 text-red-600\" />,\n }\n}\n\nfunction PrimaryMetricCell({\n row,\n displayMode,\n}: {\n row: PerformanceMetricsTableRow\n displayMode: \"progress\" | \"value\"\n}) {\n if (displayMode === \"value\") {\n return (\n <div className=\"text-sm font-bold text-foreground\">\n {row.primaryValue}\n </div>\n )\n }\n\n const percentage = (row.primaryValue / row.primaryTarget) * 100\n const progress = getProgressStatus(row.primaryValue, row.primaryTarget)\n\n return (\n <div className=\"flex items-center gap-2\">\n <span className=\"shrink-0\">{progress.icon}</span>\n <div className=\"w-full max-w-[180px]\">\n <div className=\"mb-1 text-sm font-bold text-foreground\">\n {row.primaryValue}/{row.primaryTarget}\n </div>\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <div\n className={cn(\"h-full rounded-full\", progress.color)}\n style={{ width: `${Math.min(100, percentage)}%` }}\n />\n </div>\n <div className={cn(\"mt-1 text-xs font-medium\", progress.textColor)}>\n {Math.round(percentage)}%\n </div>\n </div>\n </div>\n )\n}\n\nfunction sortRows(\n rows: PerformanceMetricsTableRow[],\n sortId: PerformanceMetricsTableSortOption[\"id\"],\n) {\n const copy = [...rows]\n switch (sortId) {\n case \"primary-asc\":\n return copy.sort((a, b) => a.primaryValue - b.primaryValue)\n case \"rate-desc\":\n return copy.sort((a, b) => b.ratePercent - a.ratePercent)\n case \"metric-four-desc\":\n return copy.sort((a, b) => b.metricFour - a.metricFour)\n case \"primary-desc\":\n default:\n return copy.sort((a, b) => b.primaryValue - a.primaryValue)\n }\n}\n\nexport function PerformanceMetricsTable({\n title = \"Performance Table\",\n entityColumnLabel = \"Entity\",\n primaryMetricColumnLabel = \"Primary Goal\",\n primaryMetricDisplayMode = \"progress\",\n rateColumnLabel = \"Rate\",\n metricOneColumnLabel = \"Metric One\",\n metricTwoColumnLabel = \"Metric Two\",\n metricThreeColumnLabel = \"Metric Three\",\n metricFourColumnLabel = \"Metric Four\",\n viewOptions = [\"By Entity\"],\n roleOptions = [\n \"All\",\n \"Senior Coordinator\",\n \"Coordinator\",\n \"Junior Coordinator\",\n ],\n sortOptions = DEFAULT_SORT_OPTIONS,\n rows = DEFAULT_ROWS,\n pageSize = 6,\n searchPlaceholder = \"Search rows...\",\n}: PerformanceMetricsTableProps) {\n const [view, setView] = React.useState(viewOptions[0] ?? \"By Entity\")\n const [sortId, setSortId] = React.useState<\n PerformanceMetricsTableSortOption[\"id\"]\n >(sortOptions[0]?.id ?? \"primary-desc\")\n const [roleFilter, setRoleFilter] = React.useState(roleOptions[0] ?? \"All\")\n const [search, setSearch] = React.useState(\"\")\n const [page, setPage] = React.useState(1)\n\n const filteredRows = React.useMemo(() => {\n const normalized = search.trim().toLowerCase()\n return rows.filter((row) => {\n if (roleFilter !== \"All\" && row.role !== roleFilter) {\n return false\n }\n if (!normalized) {\n return true\n }\n return row.label.toLowerCase().includes(normalized)\n })\n }, [roleFilter, rows, search])\n\n const sortedRows = React.useMemo(\n () => sortRows(filteredRows, sortId),\n [filteredRows, sortId],\n )\n\n const pageCount = Math.max(1, Math.ceil(sortedRows.length / pageSize))\n const start = (page - 1) * pageSize\n const paginatedRows = sortedRows.slice(start, start + pageSize)\n\n React.useEffect(() => {\n setPage(1)\n }, [search, roleFilter, sortId, view])\n\n React.useEffect(() => {\n setPage((previous) => Math.min(previous, pageCount))\n }, [pageCount])\n\n const sortLabel =\n sortOptions.find((option) => option.id === sortId)?.label ?? \"Sort\"\n\n return (\n <div className=\"overflow-hidden rounded-xl border border-border bg-card\">\n <div className=\"flex items-center justify-between border-b border-border px-4 py-3\">\n <h3 className=\"text-sm font-semibold text-foreground\">{title}</h3>\n <div className=\"flex items-center gap-2\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"h-8 text-xs\">\n {view}\n <ChevronDown className=\"ml-2 h-3.5 w-3.5\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent>\n {viewOptions.map((option) => (\n <DropdownMenuItem key={option} onClick={() => setView(option)}>\n {option}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"h-8 text-xs\">\n Sort: {sortLabel.replace(/\\s*\\(.+\\)/, \"\")}\n <ChevronDown className=\"ml-2 h-3.5 w-3.5\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-56\">\n {sortOptions.map((option) => (\n <DropdownMenuItem\n key={option.id}\n onClick={() => setSortId(option.id)}\n >\n {option.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"h-8 text-xs\">\n Role: {roleFilter}\n <ChevronDown className=\"ml-2 h-3.5 w-3.5\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {roleOptions.map((option) => (\n <DropdownMenuItem\n key={option}\n onClick={() => setRoleFilter(option)}\n >\n {option}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n <Input\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n placeholder={searchPlaceholder}\n className=\"h-8 w-48 text-xs\"\n />\n </div>\n </div>\n\n <ScrollArea>\n <div className=\"min-w-[1180px]\">\n <Table>\n <TableHeader className=\"bg-muted/30\">\n <TableRow className=\"hover:bg-transparent\">\n <TableHead className=\"px-4 py-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {entityColumnLabel}\n </TableHead>\n <TableHead className=\"w-[260px] px-4 py-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {primaryMetricColumnLabel}\n </TableHead>\n <TableHead className=\"px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {rateColumnLabel}\n </TableHead>\n <TableHead className=\"px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {metricOneColumnLabel}\n </TableHead>\n <TableHead className=\"px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {metricTwoColumnLabel}\n </TableHead>\n <TableHead className=\"px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {metricThreeColumnLabel}\n </TableHead>\n <TableHead className=\"px-4 py-3 text-right text-xs font-semibold uppercase tracking-wider text-muted-foreground\">\n {metricFourColumnLabel}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {paginatedRows.map((row) => (\n <TableRow key={row.id} className=\"hover:bg-muted/30\">\n <TableCell className=\"px-4 py-3\">\n <div className=\"flex items-center gap-3\">\n <Avatar className=\"h-8 w-8 border border-border\">\n <AvatarFallback className=\"bg-emerald-100 text-[11px] font-medium text-emerald-700\">\n {row.avatarFallback}\n </AvatarFallback>\n </Avatar>\n <span className=\"text-sm font-medium text-foreground\">\n {row.label}\n </span>\n </div>\n </TableCell>\n\n <TableCell className=\"px-4 py-3\">\n <PrimaryMetricCell\n row={row}\n displayMode={primaryMetricDisplayMode}\n />\n </TableCell>\n\n <TableCell className=\"px-4 py-3 text-right text-sm font-semibold text-emerald-600\">\n {row.ratePercent}%\n </TableCell>\n <TableCell className=\"px-4 py-3 text-right text-sm font-medium text-foreground\">\n {row.metricOne}\n </TableCell>\n <TableCell className=\"px-4 py-3 text-right text-sm text-muted-foreground\">\n {row.metricTwo}\n </TableCell>\n <TableCell className=\"px-4 py-3 text-right text-sm font-medium text-foreground\">\n {row.metricThree}\n </TableCell>\n <TableCell className=\"px-4 py-3 text-right text-sm font-medium text-foreground\">\n {row.metricFour}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n <ScrollBar orientation=\"horizontal\" />\n </ScrollArea>\n\n <div className=\"flex items-center justify-between border-t border-border bg-muted/20 px-4 py-3\">\n <span className=\"text-xs text-muted-foreground\">\n Showing {sortedRows.length === 0 ? 0 : start + 1} to{\" \"}\n {Math.min(start + pageSize, sortedRows.length)} of {sortedRows.length}{\" \"}\n rows\n </span>\n <div className=\"flex items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={page <= 1}\n onClick={() => setPage((previous) => Math.max(previous - 1, 1))}\n >\n <ChevronLeft className=\"mr-1 h-3.5 w-3.5\" />\n Previous\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={page >= pageCount}\n onClick={() =>\n setPage((previous) => Math.min(previous + 1, pageCount))\n }\n >\n Next\n <ChevronRight className=\"ml-1 h-3.5 w-3.5\" />\n </Button>\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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","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
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/score-why-chips.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n ChevronDown,\n ChevronUp,\n ChevronRight,\n X,\n TrendingDown,\n ArrowUpRight,\n Radar,\n ArrowDownLeft,\n GitMerge,\n Activity,\n} from \"lucide-react\"\nimport type { LucideIcon } from \"lucide-react\"\nimport { FeedbackFooter } from \"./feedback-primitives\"\nimport type { FeedbackChipTree, FeedbackSubmitData } from \"./feedback-primitives\"\nimport { cn } from \"../lib/utils\"\nimport type {\n QueueItem,\n SignalScoreData,\n SignalScoreExplanationBucket,\n SignalScoreExplanationSignal,\n SignalScoreUrgencyLabel,\n} from \"../prototype/prototype-config\"\n\n// ---------------------------------------------------------------------------\n// Constants & helpers\n// ---------------------------------------------------------------------------\n\nexport function getSignalScoreUrgencyLabel(\n score: number,\n providedLabel?: SignalScoreUrgencyLabel,\n): SignalScoreUrgencyLabel {\n if (providedLabel) return providedLabel\n if (score >= 80) return \"Urgent\"\n if (score >= 60) return \"High\"\n if (score >= 35) return \"Medium\"\n return \"Low\"\n}\n\nexport function scoreRangeForUrgency(label: SignalScoreUrgencyLabel): string {\n switch (label) {\n case \"Urgent\":\n return \"80-100\"\n case \"High\":\n return \"60-79\"\n case \"Medium\":\n return \"35-59\"\n case \"Low\":\n return \"0-34\"\n }\n}\n\nfunction makeDomId(...parts: Array<string | undefined>): string {\n return parts\n .filter((part): part is string => Boolean(part))\n .join(\"-\")\n .replace(/[^A-Za-z0-9_-]+/g, \"-\")\n}\n\nfunction bucketHasSignalRows(bucket: SignalScoreExplanationBucket): boolean {\n return (\n (bucket.signals?.length ?? 0) > 0 ||\n (bucket.signalIds?.length ?? 0) > 0 ||\n Boolean(bucket.primarySignalId)\n )\n}\n\nfunction getSignalScoreBuckets(signalData: SignalScoreData): SignalScoreExplanationBucket[] {\n return (signalData.explanationBuckets ?? []).filter(\n (bucket) => bucket.kind !== \"factor\" && bucketHasSignalRows(bucket),\n )\n}\n\nfunction getBucketSignals(bucket: SignalScoreExplanationBucket): SignalScoreExplanationSignal[] {\n if (bucket.signals && bucket.signals.length > 0) return bucket.signals\n\n const signalIds = bucket.signalIds && bucket.signalIds.length > 0 ? bucket.signalIds : bucket.primarySignalId ? [bucket.primarySignalId] : []\n const uniqueSignalIds = Array.from(new Set(signalIds))\n\n return uniqueSignalIds.map((signalId) => ({\n id: signalId,\n label: `${bucket.label} signal`,\n }))\n}\n\n// ---------------------------------------------------------------------------\n// Signal type icon map - keyed by signal type name (Tailwind v4 source scanned)\n// ---------------------------------------------------------------------------\n\nconst SIGNAL_TYPE_ICONS: Record<string, LucideIcon> = {\n treasury_liquidation: TrendingDown,\n cumulative_treasury_outflow: ArrowUpRight,\n test_transaction: Radar,\n micro_deposit: ArrowDownLeft,\n combined_signal: GitMerge,\n}\n\nfunction resolveIcon(iconName?: string): LucideIcon {\n if (!iconName) return Activity\n return SIGNAL_TYPE_ICONS[iconName] ?? Activity\n}\n\n// ---------------------------------------------------------------------------\n// Static tone class maps (REQUIRED for Tailwind v4 source scanning)\n// ---------------------------------------------------------------------------\n\n/** Shared tone-to-class map. Re-exported for signal-priority-popover. */\nexport const SIGNAL_TONE_CLASSES: Record<string, string> = {\n alert: \"bg-red-50 text-red-600\",\n warn: \"bg-amber-50 text-amber-600\",\n info: \"bg-blue-50 text-blue-600\",\n}\n\n/** Default tone for missing/unknown tone values */\nexport const DEFAULT_TONE_CLASS = \"bg-muted text-muted-foreground\"\n\n// ---------------------------------------------------------------------------\n// Em-dash fallback for missing slot data\n// ---------------------------------------------------------------------------\n\nfunction slotValue(value: string | null | undefined): string {\n return value && value.trim().length > 0 ? value : \"\"\n}\n\n// ---------------------------------------------------------------------------\n// Bucket feedback chip config\n// ---------------------------------------------------------------------------\n\nconst BUCKET_NEGATIVE_CHIPS: FeedbackChipTree[] = [\n {\n label: \"Not relevant for this account\",\n subPrompt: \"Why isn't it relevant?\",\n subChips: [\n \"Business as usual for this account\",\n \"Account in maintenance mode\",\n \"Wrong contact for this signal\",\n \"Other\",\n ],\n },\n { label: \"Bad timing\" },\n {\n label: \"Inaccurate data\",\n subPrompt: \"Which field?\",\n subChips: [\"Balance figures\", \"Counterparty\", \"Timestamp\", \"Other\"],\n },\n { label: \"Wrong account\" },\n { label: \"Already handled\" },\n { label: \"Other\" },\n]\n\n// ---------------------------------------------------------------------------\n// Default visible row count for long lists\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_VISIBLE_ROWS = 8\n\n// ---------------------------------------------------------------------------\n// WhyPill - Bucket toggle button with icon, count badge, chevron, close\n// ---------------------------------------------------------------------------\n\ninterface WhyPillProps {\n bucket: SignalScoreExplanationBucket\n isSelected: boolean\n signalCount: number\n panelId: string\n onToggle: () => void\n onClose: () => void\n}\n\nfunction WhyPill({ bucket, isSelected, signalCount, panelId, onToggle, onClose }: WhyPillProps) {\n const IconComponent = resolveIcon(bucket.icon)\n\n const sharedClasses = cn(\n \"inline-flex items-center text-[11px] font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n isSelected\n ? \"border-border bg-muted text-foreground\"\n : \"border-border bg-background text-muted-foreground hover:bg-muted/60 hover:text-foreground\",\n )\n\n return (\n <div className=\"inline-flex h-[26px] items-stretch\">\n <button\n type=\"button\"\n onClick={onToggle}\n aria-expanded={isSelected}\n aria-controls={panelId}\n className={cn(\n sharedClasses,\n \"gap-1.5 rounded-lg border px-2.5 py-1\",\n isSelected && \"rounded-b-none rounded-r-none border-r-0\",\n )}\n >\n <IconComponent className=\"h-3 w-3 shrink-0\" />\n {bucket.label}\n {signalCount > 1 && (\n <span className={cn(\"rounded-full px-1.5 py-0 text-[10px]\", isSelected ? \"bg-background/60\" : \"bg-muted\")}>\n x{signalCount}\n </span>\n )}\n {isSelected ? (\n <ChevronUp className=\"h-3 w-3 shrink-0\" />\n ) : (\n <ChevronDown className=\"h-3 w-3 shrink-0\" />\n )}\n </button>\n {isSelected && (\n <button\n type=\"button\"\n aria-label={`Close ${bucket.label}`}\n onClick={onClose}\n className={cn(\n sharedClasses,\n \"rounded-lg rounded-b-none rounded-l-none border border-l-0 border-border px-1.5 py-1 hover:bg-background/60\",\n )}\n >\n <X className=\"h-3 w-3\" />\n </button>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// CombinedSignalMiniChips - renders component type chips for combined signals\n// ---------------------------------------------------------------------------\n\ninterface CombinedSignalMiniChipsProps {\n components: Array<{ type: string; count: number }>\n}\n\nfunction CombinedSignalMiniChips({ components }: CombinedSignalMiniChipsProps) {\n return (\n <div className=\"flex flex-wrap items-center gap-1\">\n {components.map((comp, idx) => {\n const CompIcon = resolveIcon(comp.type)\n return (\n <React.Fragment key={comp.type}>\n {idx > 0 && <span className=\"text-[10px] text-muted-foreground/60\">+</span>}\n <span className=\"inline-flex items-center gap-0.5 rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground\">\n <CompIcon className=\"h-2.5 w-2.5 shrink-0\" />\n {comp.type.replace(/_/g, \" \")} x{comp.count}\n </span>\n </React.Fragment>\n )\n })}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// StructuredSignalRow - CSS grid slot grammar signal row\n// ---------------------------------------------------------------------------\n\ninterface StructuredSignalRowProps {\n item: QueueItem\n bucketKey: string\n signal: SignalScoreExplanationSignal\n tone?: \"alert\" | \"warn\" | \"info\"\n onOpenSignalBucket?: ScoreWhyChipsProps[\"onOpenSignalBucket\"]\n}\n\nfunction StructuredSignalRow({ item, bucketKey, signal, tone, onOpenSignalBucket }: StructuredSignalRowProps) {\n const IconComponent = resolveIcon(signal.signalTypeName)\n const toneClass = tone ? (SIGNAL_TONE_CLASSES[tone] ?? DEFAULT_TONE_CLASS) : DEFAULT_TONE_CLASS\n const isCombined = signal.signalTypeName === \"combined_signal\" && signal.components && signal.components.length > 0\n\n const rowContent = (\n <>\n {/* Slot 1: Icon */}\n <div className={cn(\"flex h-5 w-5 shrink-0 items-center justify-center rounded\", toneClass)}>\n <IconComponent className=\"h-3 w-3\" />\n </div>\n\n {/* Slot 2: Primary value + qualifier */}\n <div className=\"min-w-0\">\n {isCombined ? (\n <CombinedSignalMiniChips components={signal.components!} />\n ) : (\n <div className=\"flex items-baseline gap-1.5\">\n <span className=\"text-sm font-semibold tabular-nums text-foreground\">\n {slotValue(signal.primaryValue)}\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {slotValue(signal.qualifier)}\n </span>\n </div>\n )}\n </div>\n\n {/* Slot 3: Counterparty */}\n <div className=\"min-w-0\">\n <span className=\"block truncate text-xs text-muted-foreground\">\n {slotValue(signal.counterparty)}\n </span>\n </div>\n\n {/* Slot 4: Time */}\n <span className=\"shrink-0 text-[11px] text-muted-foreground/70\">\n {slotValue(signal.time)}\n </span>\n\n {/* Slot 5: Chevron */}\n <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\" />\n </>\n )\n\n if (signal.id && onOpenSignalBucket) {\n return (\n <button\n type=\"button\"\n className=\"group grid w-full cursor-pointer items-center gap-x-3 gap-y-1 rounded-md px-3 py-2 text-left transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n style={{ gridTemplateColumns: \"20px minmax(0,1fr) minmax(0,1fr) auto 16px\" }}\n onClick={() => onOpenSignalBucket({ item, bucketKey, signalId: signal.id! })}\n >\n {rowContent}\n </button>\n )\n }\n\n return (\n <div\n className=\"grid w-full items-center gap-x-3 gap-y-1 rounded-md px-3 py-2\"\n style={{ gridTemplateColumns: \"20px minmax(0,1fr) minmax(0,1fr) auto 16px\" }}\n >\n {rowContent}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Legacy SignalRow (for signals without structured data)\n// ---------------------------------------------------------------------------\n\ninterface SignalRowProps {\n item: QueueItem\n bucketKey: string\n signal: SignalScoreExplanationSignal\n onOpenSignalBucket?: ScoreWhyChipsProps[\"onOpenSignalBucket\"]\n}\n\nfunction LegacySignalRow({ item, bucketKey, signal, onOpenSignalBucket }: SignalRowProps) {\n const isClickable = !!(signal.id && onOpenSignalBucket)\n const rowContent = (\n <>\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium text-foreground\">{signal.label}</p>\n {signal.description ? <p className=\"mt-1 leading-relaxed text-muted-foreground\">{signal.description}</p> : null}\n {(signal.source || signal.metric) && (\n <div className=\"mt-1.5 flex flex-wrap gap-1.5 text-[11px] text-muted-foreground/80\">\n {signal.source ? <span className=\"rounded-full bg-muted px-2 py-0.5\">{signal.source}</span> : null}\n {signal.metric ? <span className=\"rounded-full bg-muted px-2 py-0.5\">{signal.metric}</span> : null}\n </div>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {signal.time ? <span className=\"text-[11px] text-muted-foreground/70\">{signal.time}</span> : null}\n {isClickable && (\n <ChevronRight className=\"h-3 w-3 text-muted-foreground/60 transition-transform group-hover:translate-x-0.5 group-hover:text-foreground/50\" />\n )}\n </div>\n </div>\n </>\n )\n\n if (isClickable) {\n return (\n <button\n type=\"button\"\n className=\"group w-full cursor-pointer rounded-md bg-background/80 p-3 text-left text-xs transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n onClick={() => onOpenSignalBucket!({ item, bucketKey, signalId: signal.id! })}\n >\n {rowContent}\n </button>\n )\n }\n\n return <div className=\"rounded-md bg-background/80 p-3 text-xs\">{rowContent}</div>\n}\n\n/**\n * Determine whether a signal has structured slot data.\n * If it has primaryValue, counterparty, qualifier, or components, use the structured row.\n */\nfunction hasStructuredData(signal: SignalScoreExplanationSignal): boolean {\n return Boolean(\n signal.primaryValue ||\n signal.qualifier ||\n signal.counterparty ||\n (signal.components && signal.components.length > 0),\n )\n}\n\n// ---------------------------------------------------------------------------\n// WhyCard - Expanded panel under a pill\n// ---------------------------------------------------------------------------\n\ninterface WhyCardProps {\n bucket: SignalScoreExplanationBucket\n signals: SignalScoreExplanationSignal[]\n item: QueueItem\n panelId: string\n onOpenSignalBucket?: ScoreWhyChipsProps[\"onOpenSignalBucket\"]\n onBucketFeedback?: (bucketKey: string, data: FeedbackSubmitData) => void\n}\n\nfunction WhyCard({ bucket, signals, item, panelId, onOpenSignalBucket, onBucketFeedback }: WhyCardProps) {\n const [showAll, setShowAll] = React.useState(false)\n const [bucketFeedback, setBucketFeedback] = React.useState<\"positive\" | \"negative\" | null>(null)\n const totalCount = bucket.signalCount ?? signals.length\n const visibleSignals = showAll ? signals : signals.slice(0, DEFAULT_VISIBLE_ROWS)\n const hiddenCount = signals.length - DEFAULT_VISIBLE_ROWS\n\n // Determine whether to use structured rows (any signal has structured data)\n const useStructured = signals.some(hasStructuredData)\n\n return (\n <div\n id={panelId}\n className=\"rounded-lg rounded-t-none border border-t-0 border-border bg-muted/20 p-3\"\n role=\"region\"\n aria-label={`${bucket.label} details`}\n >\n {/* Card header */}\n <div className=\"mb-2 flex items-center justify-between\">\n <span className=\"text-[10px] font-bold uppercase tracking-wider text-muted-foreground\">\n {totalCount} signal{totalCount !== 1 ? \"s\" : \"\"} – {bucket.label}\n </span>\n </div>\n\n {/* Signal rows */}\n {visibleSignals.length > 0 ? (\n <ul className=\"divide-y divide-border/30\" aria-label=\"Matching signals\">\n {visibleSignals.map((signal, index) => (\n <li key={signal.id ?? `${bucket.key}-signal-${index}`}>\n {useStructured ? (\n <StructuredSignalRow\n item={item}\n bucketKey={bucket.key}\n signal={signal}\n tone={bucket.tone}\n onOpenSignalBucket={onOpenSignalBucket}\n />\n ) : (\n <LegacySignalRow\n item={item}\n bucketKey={bucket.key}\n signal={signal}\n onOpenSignalBucket={onOpenSignalBucket}\n />\n )}\n </li>\n ))}\n </ul>\n ) : bucket.evidence && bucket.evidence.length > 0 ? (\n <ul className=\"mt-3 space-y-1.5\" aria-label=\"Matching signals\">\n {bucket.evidence.map((evidence, index) => (\n <li key={`${bucket.key}-evidence-${index}`} className=\"flex gap-2 text-xs text-muted-foreground\">\n <span className=\"mt-1.5 h-1 w-1 shrink-0 rounded-full bg-primary\" />\n <span className=\"leading-relaxed\">{evidence}</span>\n </li>\n ))}\n </ul>\n ) : null}\n\n {/* \"Show N more\" button */}\n {!showAll && hiddenCount > 0 && (\n <button\n type=\"button\"\n onClick={() => setShowAll(true)}\n className=\"mt-2 flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n <ChevronDown className=\"h-3 w-3\" />\n Show {hiddenCount} more\n </button>\n )}\n\n {/* Bucket feedback footer */}\n {onBucketFeedback && (\n <div className=\"mt-3 border-t border-border/40 pt-3\">\n <FeedbackFooter\n feedback={bucketFeedback}\n onFeedbackChange={setBucketFeedback}\n onSubmit={(data) => onBucketFeedback(bucket.key, data)}\n negativeChips={BUCKET_NEGATIVE_CHIPS}\n negativePrompt=\"Was this bucket useful?\"\n positivePrompt=\"Thanks! What was useful about this bucket?\"\n />\n </div>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// ScoreWhyChips - Main export\n// ---------------------------------------------------------------------------\n\nexport interface ScoreWhyChipsProps {\n item: QueueItem\n signalData: SignalScoreData\n onOpenSignalBucket?: (args: { item: QueueItem; bucketKey: string; signalId: string }) => void\n className?: string\n}\n\nexport function ScoreWhyChips({\n item,\n signalData,\n onOpenSignalBucket,\n className,\n}: ScoreWhyChipsProps) {\n const [selectedBucketKey, setSelectedBucketKey] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n setSelectedBucketKey(null)\n }, [item.id])\n\n const reactId = React.useId()\n const idPrefix = makeDomId(\"score-why\", reactId, item.id)\n const buckets = React.useMemo(() => getSignalScoreBuckets(signalData), [signalData])\n const selectedBucket = buckets.find((bucket) => bucket.key === selectedBucketKey) ?? null\n const selectedBucketSignals = selectedBucket ? getBucketSignals(selectedBucket) : []\n const selectedPanelId = selectedBucket ? `${idPrefix}-panel-${makeDomId(selectedBucket.key)}` : undefined\n\n if (buckets.length === 0) return null\n\n return (\n <div className={cn(\"mt-4\", className)}>\n <div className=\"mb-2 flex items-center gap-2\">\n <span className=\"text-[10px] font-bold uppercase tracking-wider text-muted-foreground\">Why</span>\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {buckets.map((bucket) => {\n const isSelected = selectedBucketKey === bucket.key\n const panelId = `${idPrefix}-panel-${makeDomId(bucket.key)}`\n const signals = getBucketSignals(bucket)\n const signalCount = bucket.signalCount ?? signals.length\n return (\n <div key={bucket.key} className=\"flex flex-col\">\n <WhyPill\n bucket={bucket}\n isSelected={isSelected}\n signalCount={signalCount}\n panelId={panelId}\n onToggle={() => setSelectedBucketKey((prev) => (prev === bucket.key ? null : bucket.key))}\n onClose={() => setSelectedBucketKey(null)}\n />\n </div>\n )\n })}\n </div>\n\n {selectedBucket && selectedPanelId && (\n <WhyCard\n bucket={selectedBucket}\n signals={selectedBucketSignals}\n item={item}\n panelId={selectedPanelId}\n onOpenSignalBucket={onOpenSignalBucket}\n onBucketFeedback={signalData.onBucketFeedback}\n />\n )}\n </div>\n )\n}\n"],"mappings":"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","names":["_a"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/score-why-chips.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n ChevronDown,\n ChevronUp,\n ChevronRight,\n X,\n TrendingDown,\n ArrowUpRight,\n Radar,\n ArrowDownLeft,\n GitMerge,\n Activity,\n} from \"lucide-react\"\nimport type { LucideIcon } from \"lucide-react\"\nimport { FeedbackFooter } from \"./feedback-primitives\"\nimport type { FeedbackChipTree, FeedbackSubmitData, PersistedFeedbackData } from \"./feedback-primitives\"\nimport { cn } from \"../lib/utils\"\nimport type {\n QueueItem,\n SignalScoreData,\n SignalScoreExplanationBucket,\n SignalScoreExplanationSignal,\n SignalScoreUrgencyLabel,\n} from \"../prototype/prototype-config\"\n\n// ---------------------------------------------------------------------------\n// Constants & helpers\n// ---------------------------------------------------------------------------\n\nexport function getSignalScoreUrgencyLabel(\n score: number,\n providedLabel?: SignalScoreUrgencyLabel,\n): SignalScoreUrgencyLabel {\n if (providedLabel) return providedLabel\n if (score >= 80) return \"Urgent\"\n if (score >= 60) return \"High\"\n if (score >= 35) return \"Medium\"\n return \"Low\"\n}\n\nexport function scoreRangeForUrgency(label: SignalScoreUrgencyLabel): string {\n switch (label) {\n case \"Urgent\":\n return \"80-100\"\n case \"High\":\n return \"60-79\"\n case \"Medium\":\n return \"35-59\"\n case \"Low\":\n return \"0-34\"\n }\n}\n\nfunction makeDomId(...parts: Array<string | undefined>): string {\n return parts\n .filter((part): part is string => Boolean(part))\n .join(\"-\")\n .replace(/[^A-Za-z0-9_-]+/g, \"-\")\n}\n\nfunction bucketHasSignalRows(bucket: SignalScoreExplanationBucket): boolean {\n return (\n (bucket.signals?.length ?? 0) > 0 ||\n (bucket.signalIds?.length ?? 0) > 0 ||\n Boolean(bucket.primarySignalId)\n )\n}\n\nfunction getSignalScoreBuckets(signalData: SignalScoreData): SignalScoreExplanationBucket[] {\n return (signalData.explanationBuckets ?? []).filter(\n (bucket) => bucket.kind !== \"factor\" && bucketHasSignalRows(bucket),\n )\n}\n\nfunction getBucketSignals(bucket: SignalScoreExplanationBucket): SignalScoreExplanationSignal[] {\n if (bucket.signals && bucket.signals.length > 0) return bucket.signals\n\n const signalIds = bucket.signalIds && bucket.signalIds.length > 0 ? bucket.signalIds : bucket.primarySignalId ? [bucket.primarySignalId] : []\n const uniqueSignalIds = Array.from(new Set(signalIds))\n\n return uniqueSignalIds.map((signalId) => ({\n id: signalId,\n label: `${bucket.label} signal`,\n }))\n}\n\n// ---------------------------------------------------------------------------\n// Signal type icon map - keyed by signal type name (Tailwind v4 source scanned)\n// ---------------------------------------------------------------------------\n\nconst SIGNAL_TYPE_ICONS: Record<string, LucideIcon> = {\n treasury_liquidation: TrendingDown,\n cumulative_treasury_outflow: ArrowUpRight,\n test_transaction: Radar,\n micro_deposit: ArrowDownLeft,\n combined_signal: GitMerge,\n}\n\nfunction resolveIcon(iconName?: string): LucideIcon {\n if (!iconName) return Activity\n return SIGNAL_TYPE_ICONS[iconName] ?? Activity\n}\n\n// ---------------------------------------------------------------------------\n// Static tone class maps (REQUIRED for Tailwind v4 source scanning)\n// ---------------------------------------------------------------------------\n\n/** Shared tone-to-class map. Re-exported for signal-priority-popover. */\nexport const SIGNAL_TONE_CLASSES: Record<string, string> = {\n alert: \"bg-red-50 text-red-600\",\n warn: \"bg-amber-50 text-amber-600\",\n info: \"bg-blue-50 text-blue-600\",\n}\n\n/** Default tone for missing/unknown tone values */\nexport const DEFAULT_TONE_CLASS = \"bg-muted text-muted-foreground\"\n\n// ---------------------------------------------------------------------------\n// Em-dash fallback for missing slot data\n// ---------------------------------------------------------------------------\n\nfunction slotValue(value: string | null | undefined): string {\n return value && value.trim().length > 0 ? value : \"\"\n}\n\n// ---------------------------------------------------------------------------\n// Bucket feedback chip config\n// ---------------------------------------------------------------------------\n\nconst BUCKET_NEGATIVE_CHIPS: FeedbackChipTree[] = [\n {\n label: \"Not relevant for this account\",\n subPrompt: \"Why isn't it relevant?\",\n subChips: [\n \"Business as usual for this account\",\n \"Account in maintenance mode\",\n \"Wrong contact for this signal\",\n \"Other\",\n ],\n },\n { label: \"Bad timing\" },\n {\n label: \"Inaccurate data\",\n subPrompt: \"Which field?\",\n subChips: [\"Balance figures\", \"Counterparty\", \"Timestamp\", \"Other\"],\n },\n { label: \"Wrong account\" },\n { label: \"Already handled\" },\n { label: \"Other\" },\n]\n\n// ---------------------------------------------------------------------------\n// Default visible row count for long lists\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_VISIBLE_ROWS = 8\n\n// ---------------------------------------------------------------------------\n// WhyPill - Bucket toggle button with icon, count badge, chevron, close\n// ---------------------------------------------------------------------------\n\ninterface WhyPillProps {\n bucket: SignalScoreExplanationBucket\n isSelected: boolean\n signalCount: number\n panelId: string\n onToggle: () => void\n onClose: () => void\n}\n\nfunction WhyPill({ bucket, isSelected, signalCount, panelId, onToggle, onClose }: WhyPillProps) {\n const IconComponent = resolveIcon(bucket.icon)\n\n const sharedClasses = cn(\n \"inline-flex items-center text-[11px] font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n isSelected\n ? \"border-border bg-muted text-foreground\"\n : \"border-border bg-background text-muted-foreground hover:bg-muted/60 hover:text-foreground\",\n )\n\n return (\n <div className=\"inline-flex h-[26px] items-stretch\">\n <button\n type=\"button\"\n onClick={onToggle}\n aria-expanded={isSelected}\n aria-controls={panelId}\n className={cn(\n sharedClasses,\n \"gap-1.5 rounded-lg border px-2.5 py-1\",\n isSelected && \"rounded-b-none rounded-r-none border-r-0\",\n )}\n >\n <IconComponent className=\"h-3 w-3 shrink-0\" />\n {bucket.label}\n {signalCount > 1 && (\n <span className={cn(\"rounded-full px-1.5 py-0 text-[10px]\", isSelected ? \"bg-background/60\" : \"bg-muted\")}>\n x{signalCount}\n </span>\n )}\n {isSelected ? (\n <ChevronUp className=\"h-3 w-3 shrink-0\" />\n ) : (\n <ChevronDown className=\"h-3 w-3 shrink-0\" />\n )}\n </button>\n {isSelected && (\n <button\n type=\"button\"\n aria-label={`Close ${bucket.label}`}\n onClick={onClose}\n className={cn(\n sharedClasses,\n \"rounded-lg rounded-b-none rounded-l-none border border-l-0 border-border px-1.5 py-1 hover:bg-background/60\",\n )}\n >\n <X className=\"h-3 w-3\" />\n </button>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// CombinedSignalMiniChips - renders component type chips for combined signals\n// ---------------------------------------------------------------------------\n\ninterface CombinedSignalMiniChipsProps {\n components: Array<{ type: string; count: number }>\n}\n\nfunction CombinedSignalMiniChips({ components }: CombinedSignalMiniChipsProps) {\n return (\n <div className=\"flex flex-wrap items-center gap-1\">\n {components.map((comp, idx) => {\n const CompIcon = resolveIcon(comp.type)\n return (\n <React.Fragment key={comp.type}>\n {idx > 0 && <span className=\"text-[10px] text-muted-foreground/60\">+</span>}\n <span className=\"inline-flex items-center gap-0.5 rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground\">\n <CompIcon className=\"h-2.5 w-2.5 shrink-0\" />\n {comp.type.replace(/_/g, \" \")} x{comp.count}\n </span>\n </React.Fragment>\n )\n })}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// StructuredSignalRow - CSS grid slot grammar signal row\n// ---------------------------------------------------------------------------\n\ninterface StructuredSignalRowProps {\n item: QueueItem\n bucketKey: string\n signal: SignalScoreExplanationSignal\n tone?: \"alert\" | \"warn\" | \"info\"\n onOpenSignalBucket?: ScoreWhyChipsProps[\"onOpenSignalBucket\"]\n}\n\nfunction StructuredSignalRow({ item, bucketKey, signal, tone, onOpenSignalBucket }: StructuredSignalRowProps) {\n const IconComponent = resolveIcon(signal.signalTypeName)\n const toneClass = tone ? (SIGNAL_TONE_CLASSES[tone] ?? DEFAULT_TONE_CLASS) : DEFAULT_TONE_CLASS\n const isCombined = signal.signalTypeName === \"combined_signal\" && signal.components && signal.components.length > 0\n const hasBalance = Boolean(signal.currentBalance || signal.balanceContext)\n\n const rowContent = (\n <>\n {/* Slot 1: Icon */}\n <div className={cn(\"flex h-5 w-5 shrink-0 items-center justify-center rounded\", toneClass)}>\n <IconComponent className=\"h-3 w-3\" />\n </div>\n\n {/* Slot 2: Primary value + qualifier */}\n <div className=\"min-w-0\">\n {isCombined ? (\n <CombinedSignalMiniChips components={signal.components!} />\n ) : (\n <div className=\"flex items-baseline gap-1.5\">\n <span className=\"text-sm font-semibold tabular-nums text-foreground\">\n {slotValue(signal.primaryValue)}\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {slotValue(signal.qualifier)}\n </span>\n </div>\n )}\n </div>\n\n {/* Slot 3: Counterparty */}\n <div className=\"min-w-0\">\n <span className=\"block truncate text-xs text-muted-foreground\">\n {slotValue(signal.counterparty)}\n </span>\n </div>\n\n {/* Slot 4: Time */}\n <span className=\"shrink-0 text-[11px] text-muted-foreground/70\">\n {slotValue(signal.time)}\n </span>\n\n {/* Slot 5: Chevron */}\n <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\" />\n\n {/* Balance context strip — spans full row below grid columns */}\n {hasBalance && (\n <div\n className=\"col-span-full mt-0.5 text-[10px] text-muted-foreground/70\"\n data-testid=\"balance-context-strip\"\n >\n {signal.currentBalance && (\n <span>\n Current balance <span className=\"font-medium text-muted-foreground\">{signal.currentBalance}</span>\n </span>\n )}\n {signal.balanceContext && (\n <span>\n {signal.currentBalance ? \" · \" : \"\"}\n {signal.balanceContext}\n </span>\n )}\n </div>\n )}\n </>\n )\n\n if (signal.id && onOpenSignalBucket) {\n return (\n <button\n type=\"button\"\n className=\"group grid w-full cursor-pointer items-center gap-x-3 gap-y-1 rounded-md px-3 py-2 text-left transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n style={{ gridTemplateColumns: \"20px minmax(0,1fr) minmax(0,1fr) auto 16px\" }}\n onClick={() => onOpenSignalBucket({ item, bucketKey, signalId: signal.id! })}\n >\n {rowContent}\n </button>\n )\n }\n\n return (\n <div\n className=\"grid w-full items-center gap-x-3 gap-y-1 rounded-md px-3 py-2\"\n style={{ gridTemplateColumns: \"20px minmax(0,1fr) minmax(0,1fr) auto 16px\" }}\n >\n {rowContent}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Legacy SignalRow (for signals without structured data)\n// ---------------------------------------------------------------------------\n\ninterface SignalRowProps {\n item: QueueItem\n bucketKey: string\n signal: SignalScoreExplanationSignal\n onOpenSignalBucket?: ScoreWhyChipsProps[\"onOpenSignalBucket\"]\n}\n\nfunction LegacySignalRow({ item, bucketKey, signal, onOpenSignalBucket }: SignalRowProps) {\n const isClickable = !!(signal.id && onOpenSignalBucket)\n const rowContent = (\n <>\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium text-foreground\">{signal.label}</p>\n {signal.description ? <p className=\"mt-1 leading-relaxed text-muted-foreground\">{signal.description}</p> : null}\n {(signal.source || signal.metric) && (\n <div className=\"mt-1.5 flex flex-wrap gap-1.5 text-[11px] text-muted-foreground/80\">\n {signal.source ? <span className=\"rounded-full bg-muted px-2 py-0.5\">{signal.source}</span> : null}\n {signal.metric ? <span className=\"rounded-full bg-muted px-2 py-0.5\">{signal.metric}</span> : null}\n </div>\n )}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {signal.time ? <span className=\"text-[11px] text-muted-foreground/70\">{signal.time}</span> : null}\n {isClickable && (\n <ChevronRight className=\"h-3 w-3 text-muted-foreground/60 transition-transform group-hover:translate-x-0.5 group-hover:text-foreground/50\" />\n )}\n </div>\n </div>\n </>\n )\n\n if (isClickable) {\n return (\n <button\n type=\"button\"\n className=\"group w-full cursor-pointer rounded-md bg-background/80 p-3 text-left text-xs transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n onClick={() => onOpenSignalBucket!({ item, bucketKey, signalId: signal.id! })}\n >\n {rowContent}\n </button>\n )\n }\n\n return <div className=\"rounded-md bg-background/80 p-3 text-xs\">{rowContent}</div>\n}\n\n/**\n * Determine whether a signal has structured slot data.\n * If it has primaryValue, counterparty, qualifier, or components, use the structured row.\n */\nfunction hasStructuredData(signal: SignalScoreExplanationSignal): boolean {\n return Boolean(\n signal.primaryValue ||\n signal.qualifier ||\n signal.counterparty ||\n (signal.components && signal.components.length > 0),\n )\n}\n\n// ---------------------------------------------------------------------------\n// WhyCard - Expanded panel under a pill\n// ---------------------------------------------------------------------------\n\ninterface WhyCardProps {\n bucket: SignalScoreExplanationBucket\n signals: SignalScoreExplanationSignal[]\n item: QueueItem\n panelId: string\n onOpenSignalBucket?: ScoreWhyChipsProps[\"onOpenSignalBucket\"]\n onBucketFeedback?: (bucketKey: string, data: FeedbackSubmitData) => void\n /** Persisted bucket-level feedback to hydrate from. */\n initialBucketFeedback?: PersistedFeedbackData | null\n}\n\nfunction WhyCard({ bucket, signals, item, panelId, onOpenSignalBucket, onBucketFeedback, initialBucketFeedback }: WhyCardProps) {\n const [showAll, setShowAll] = React.useState(false)\n const [bucketFeedback, setBucketFeedback] = React.useState<\"positive\" | \"negative\" | null>(null)\n const totalCount = bucket.signalCount ?? signals.length\n const visibleSignals = showAll ? signals : signals.slice(0, DEFAULT_VISIBLE_ROWS)\n const hiddenCount = signals.length - DEFAULT_VISIBLE_ROWS\n\n // Determine whether to use structured rows (any signal has structured data)\n const useStructured = signals.some(hasStructuredData)\n\n return (\n <div\n id={panelId}\n className=\"rounded-lg rounded-t-none border border-t-0 border-border bg-muted/20 p-3\"\n role=\"region\"\n aria-label={`${bucket.label} details`}\n >\n {/* Card header */}\n <div className=\"mb-2 flex items-center justify-between\">\n <span className=\"text-[10px] font-bold uppercase tracking-wider text-muted-foreground\">\n {totalCount} signal{totalCount !== 1 ? \"s\" : \"\"} – {bucket.label}\n </span>\n </div>\n\n {/* Signal rows */}\n {visibleSignals.length > 0 ? (\n <ul className=\"divide-y divide-border/30\" aria-label=\"Matching signals\">\n {visibleSignals.map((signal, index) => (\n <li key={signal.id ?? `${bucket.key}-signal-${index}`}>\n {useStructured ? (\n <StructuredSignalRow\n item={item}\n bucketKey={bucket.key}\n signal={signal}\n tone={bucket.tone}\n onOpenSignalBucket={onOpenSignalBucket}\n />\n ) : (\n <LegacySignalRow\n item={item}\n bucketKey={bucket.key}\n signal={signal}\n onOpenSignalBucket={onOpenSignalBucket}\n />\n )}\n </li>\n ))}\n </ul>\n ) : bucket.evidence && bucket.evidence.length > 0 ? (\n <ul className=\"mt-3 space-y-1.5\" aria-label=\"Matching signals\">\n {bucket.evidence.map((evidence, index) => (\n <li key={`${bucket.key}-evidence-${index}`} className=\"flex gap-2 text-xs text-muted-foreground\">\n <span className=\"mt-1.5 h-1 w-1 shrink-0 rounded-full bg-primary\" />\n <span className=\"leading-relaxed\">{evidence}</span>\n </li>\n ))}\n </ul>\n ) : null}\n\n {/* \"Show N more\" button */}\n {!showAll && hiddenCount > 0 && (\n <button\n type=\"button\"\n onClick={() => setShowAll(true)}\n className=\"mt-2 flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground\"\n >\n <ChevronDown className=\"h-3 w-3\" />\n Show {hiddenCount} more\n </button>\n )}\n\n {/* Bucket feedback footer */}\n {onBucketFeedback && (\n <div className=\"mt-3 border-t border-border/40 pt-3\">\n <FeedbackFooter\n feedback={bucketFeedback}\n onFeedbackChange={setBucketFeedback}\n onSubmit={(data) => onBucketFeedback(bucket.key, data)}\n negativeChips={BUCKET_NEGATIVE_CHIPS}\n negativePrompt=\"Was this bucket useful?\"\n positivePrompt=\"Thanks! What was useful about this bucket?\"\n initialFeedback={initialBucketFeedback}\n feedbackKey={bucket.key}\n />\n </div>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// ScoreWhyChips - Main export\n// ---------------------------------------------------------------------------\n\nexport interface ScoreWhyChipsProps {\n item: QueueItem\n signalData: SignalScoreData\n onOpenSignalBucket?: (args: { item: QueueItem; bucketKey: string; signalId: string }) => void\n className?: string\n}\n\nexport function ScoreWhyChips({\n item,\n signalData,\n onOpenSignalBucket,\n className,\n}: ScoreWhyChipsProps) {\n const [selectedBucketKey, setSelectedBucketKey] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n setSelectedBucketKey(null)\n }, [item.id])\n\n const reactId = React.useId()\n const idPrefix = makeDomId(\"score-why\", reactId, item.id)\n const buckets = React.useMemo(() => getSignalScoreBuckets(signalData), [signalData])\n const selectedBucket = buckets.find((bucket) => bucket.key === selectedBucketKey) ?? null\n const selectedBucketSignals = selectedBucket ? getBucketSignals(selectedBucket) : []\n const selectedPanelId = selectedBucket ? `${idPrefix}-panel-${makeDomId(selectedBucket.key)}` : undefined\n\n if (buckets.length === 0) return null\n\n return (\n <div className={cn(\"mt-4\", className)}>\n <div className=\"mb-2 flex items-center gap-2\">\n <span className=\"text-[10px] font-bold uppercase tracking-wider text-muted-foreground\">Why</span>\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {buckets.map((bucket) => {\n const isSelected = selectedBucketKey === bucket.key\n const panelId = `${idPrefix}-panel-${makeDomId(bucket.key)}`\n const signals = getBucketSignals(bucket)\n const signalCount = bucket.signalCount ?? signals.length\n return (\n <div key={bucket.key} className=\"flex flex-col\">\n <WhyPill\n bucket={bucket}\n isSelected={isSelected}\n signalCount={signalCount}\n panelId={panelId}\n onToggle={() => setSelectedBucketKey((prev) => (prev === bucket.key ? null : bucket.key))}\n onClose={() => setSelectedBucketKey(null)}\n />\n </div>\n )\n })}\n </div>\n\n {selectedBucket && selectedPanelId && (\n <WhyCard\n bucket={selectedBucket}\n signals={selectedBucketSignals}\n item={item}\n panelId={selectedPanelId}\n onOpenSignalBucket={onOpenSignalBucket}\n onBucketFeedback={signalData.onBucketFeedback}\n initialBucketFeedback={signalData.initialBucketFeedback?.[selectedBucket.key]}\n />\n )}\n </div>\n )\n}\n"],"mappings":"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","names":["_a"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import 'react';
|
|
2
2
|
import './feedback-primitives.js';
|
|
3
|
-
export { P as PriorityFactor, S as SignalPriorityPopover, k as SignalPriorityPopoverProps } from '../signal-priority-popover-
|
|
3
|
+
export { P as PriorityFactor, S as SignalPriorityPopover, k as SignalPriorityPopoverProps } from '../signal-priority-popover-DWaAMhPI.js';
|
|
4
4
|
import './quick-action-sidebar-nav.js';
|
|
5
5
|
import './quick-action-modal.js';
|
|
6
6
|
import './score-breakdown.js';
|
|
@@ -17,7 +17,11 @@ import {
|
|
|
17
17
|
Minus,
|
|
18
18
|
ChevronDown,
|
|
19
19
|
ChevronUp,
|
|
20
|
-
Info
|
|
20
|
+
Info,
|
|
21
|
+
ThumbsUp,
|
|
22
|
+
ThumbsDown,
|
|
23
|
+
Check,
|
|
24
|
+
Pencil
|
|
21
25
|
} from "lucide-react";
|
|
22
26
|
import { cn } from "../lib/utils.js";
|
|
23
27
|
import { FeedbackFooter } from "./feedback-primitives.js";
|
|
@@ -80,12 +84,50 @@ function DirectionIcon({ direction }) {
|
|
|
80
84
|
return /* @__PURE__ */ jsx(Minus, { className: cls });
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
|
-
function PriorityFactorRow({ factor }) {
|
|
84
|
-
var _a;
|
|
87
|
+
function PriorityFactorRow({ factor, initialFeedback, onFactorFeedback }) {
|
|
88
|
+
var _a, _b, _c, _d, _e;
|
|
85
89
|
const IconComponent = (_a = FACTOR_ICONS[factor.icon]) != null ? _a : Activity;
|
|
86
90
|
const toneClasses = TONE_ICON_CLASSES[factor.tone];
|
|
87
91
|
const directionClasses = DIRECTION_CLASSES[factor.direction];
|
|
88
92
|
const directionLabel = factor.direction === "raises" ? "Raises" : factor.direction === "lowers" ? "Lowers" : "Neutral";
|
|
93
|
+
const [thumbState, setThumbState] = React.useState(
|
|
94
|
+
(_b = initialFeedback == null ? void 0 : initialFeedback.type) != null ? _b : null
|
|
95
|
+
);
|
|
96
|
+
const [showInput, setShowInput] = React.useState(false);
|
|
97
|
+
const [detailText, setDetailText] = React.useState((_c = initialFeedback == null ? void 0 : initialFeedback.detail) != null ? _c : "");
|
|
98
|
+
const [saved, setSaved] = React.useState(!!initialFeedback);
|
|
99
|
+
const [savedDetail, setSavedDetail] = React.useState((_d = initialFeedback == null ? void 0 : initialFeedback.detail) != null ? _d : "");
|
|
100
|
+
const ownershipLabel = (_e = initialFeedback == null ? void 0 : initialFeedback.ownershipLabel) != null ? _e : "Your feedback";
|
|
101
|
+
React.useEffect(() => {
|
|
102
|
+
if (initialFeedback) {
|
|
103
|
+
setThumbState(initialFeedback.type);
|
|
104
|
+
setSaved(true);
|
|
105
|
+
setSavedDetail(initialFeedback.detail);
|
|
106
|
+
}
|
|
107
|
+
}, [initialFeedback]);
|
|
108
|
+
const handleThumbClick = React.useCallback(
|
|
109
|
+
(type) => {
|
|
110
|
+
if (thumbState === type) {
|
|
111
|
+
setThumbState(null);
|
|
112
|
+
setShowInput(false);
|
|
113
|
+
setSaved(false);
|
|
114
|
+
onFactorFeedback == null ? void 0 : onFactorFeedback(factor.key, null);
|
|
115
|
+
} else {
|
|
116
|
+
setThumbState(type);
|
|
117
|
+
setShowInput(true);
|
|
118
|
+
setSaved(false);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
[thumbState, factor.key, onFactorFeedback]
|
|
122
|
+
);
|
|
123
|
+
const handleSubmitDetail = React.useCallback(() => {
|
|
124
|
+
if (!thumbState) return;
|
|
125
|
+
const text = detailText.trim();
|
|
126
|
+
onFactorFeedback == null ? void 0 : onFactorFeedback(factor.key, thumbState, text);
|
|
127
|
+
setSaved(true);
|
|
128
|
+
setSavedDetail(text);
|
|
129
|
+
setShowInput(false);
|
|
130
|
+
}, [thumbState, detailText, factor.key, onFactorFeedback]);
|
|
89
131
|
return /* @__PURE__ */ jsxs(
|
|
90
132
|
"div",
|
|
91
133
|
{
|
|
@@ -133,7 +175,116 @@ function PriorityFactorRow({ factor }) {
|
|
|
133
175
|
style: { width: `${Math.min(100, Math.max(0, factor.score))}%` }
|
|
134
176
|
}
|
|
135
177
|
) }),
|
|
136
|
-
/* @__PURE__ */ jsx("div", {})
|
|
178
|
+
/* @__PURE__ */ jsx("div", {}),
|
|
179
|
+
onFactorFeedback && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
180
|
+
/* @__PURE__ */ jsx("div", {}),
|
|
181
|
+
/* @__PURE__ */ jsxs("div", { className: "col-span-2 mt-1", children: [
|
|
182
|
+
saved && !showInput ? (
|
|
183
|
+
/* Persisted / saved indicator */
|
|
184
|
+
/* @__PURE__ */ jsxs(
|
|
185
|
+
"button",
|
|
186
|
+
{
|
|
187
|
+
type: "button",
|
|
188
|
+
onClick: () => {
|
|
189
|
+
setDetailText(savedDetail);
|
|
190
|
+
setShowInput(true);
|
|
191
|
+
setSaved(false);
|
|
192
|
+
},
|
|
193
|
+
className: "group flex items-center gap-1.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors",
|
|
194
|
+
"data-testid": `factor-feedback-persisted-${factor.key}`,
|
|
195
|
+
children: [
|
|
196
|
+
/* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
197
|
+
ownershipLabel,
|
|
198
|
+
":"
|
|
199
|
+
] }),
|
|
200
|
+
thumbState === "up" ? /* @__PURE__ */ jsx(ThumbsUp, { className: "h-[10px] w-[10px]" }) : /* @__PURE__ */ jsx(ThumbsDown, { className: "h-[10px] w-[10px]" }),
|
|
201
|
+
savedDetail && /* @__PURE__ */ jsx("span", { className: "max-w-[180px] truncate text-muted-foreground/70", children: savedDetail }),
|
|
202
|
+
/* @__PURE__ */ jsx(Pencil, { className: "h-[9px] w-[9px] opacity-0 group-hover:opacity-100 transition-opacity" })
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
207
|
+
/* @__PURE__ */ jsx(
|
|
208
|
+
"button",
|
|
209
|
+
{
|
|
210
|
+
type: "button",
|
|
211
|
+
onClick: () => handleThumbClick("up"),
|
|
212
|
+
className: cn(
|
|
213
|
+
"p-1 rounded transition-colors",
|
|
214
|
+
thumbState === "up" ? "text-foreground bg-muted" : "text-muted-foreground/40 hover:text-foreground hover:bg-muted/50"
|
|
215
|
+
),
|
|
216
|
+
title: "This factor is accurate",
|
|
217
|
+
"data-testid": `factor-thumb-up-${factor.key}`,
|
|
218
|
+
children: /* @__PURE__ */ jsx(ThumbsUp, { className: "h-[10px] w-[10px]" })
|
|
219
|
+
}
|
|
220
|
+
),
|
|
221
|
+
/* @__PURE__ */ jsx(
|
|
222
|
+
"button",
|
|
223
|
+
{
|
|
224
|
+
type: "button",
|
|
225
|
+
onClick: () => handleThumbClick("down"),
|
|
226
|
+
className: cn(
|
|
227
|
+
"p-1 rounded transition-colors",
|
|
228
|
+
thumbState === "down" ? "text-red-600 bg-red-50" : "text-muted-foreground/40 hover:text-red-600 hover:bg-red-50/50"
|
|
229
|
+
),
|
|
230
|
+
title: "Report issue with this factor",
|
|
231
|
+
"data-testid": `factor-thumb-down-${factor.key}`,
|
|
232
|
+
children: /* @__PURE__ */ jsx(ThumbsDown, { className: "h-[10px] w-[10px]" })
|
|
233
|
+
}
|
|
234
|
+
),
|
|
235
|
+
saved && /* @__PURE__ */ jsxs(
|
|
236
|
+
"span",
|
|
237
|
+
{
|
|
238
|
+
className: "inline-flex items-center gap-1 text-[11px] font-medium text-emerald-600",
|
|
239
|
+
role: "status",
|
|
240
|
+
"data-testid": `factor-saved-${factor.key}`,
|
|
241
|
+
children: [
|
|
242
|
+
/* @__PURE__ */ jsx(Check, { className: "h-[10px] w-[10px]" }),
|
|
243
|
+
"Saved"
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
] }),
|
|
248
|
+
showInput && thumbState && /* @__PURE__ */ jsxs("div", { className: "mt-1.5", children: [
|
|
249
|
+
/* @__PURE__ */ jsx(
|
|
250
|
+
"input",
|
|
251
|
+
{
|
|
252
|
+
type: "text",
|
|
253
|
+
value: detailText,
|
|
254
|
+
onChange: (e) => setDetailText(e.target.value),
|
|
255
|
+
onKeyDown: (e) => {
|
|
256
|
+
if (e.key === "Enter") handleSubmitDetail();
|
|
257
|
+
if (e.key === "Escape") setShowInput(false);
|
|
258
|
+
},
|
|
259
|
+
placeholder: thumbState === "up" ? "What\u2019s accurate? (optional)" : "What\u2019s wrong? (optional)",
|
|
260
|
+
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",
|
|
261
|
+
"data-testid": `factor-detail-input-${factor.key}`
|
|
262
|
+
}
|
|
263
|
+
),
|
|
264
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-1.5", children: [
|
|
265
|
+
/* @__PURE__ */ jsx(
|
|
266
|
+
"button",
|
|
267
|
+
{
|
|
268
|
+
type: "button",
|
|
269
|
+
onClick: handleSubmitDetail,
|
|
270
|
+
className: "bg-foreground text-background rounded px-2 py-0.5 text-[10px] font-semibold",
|
|
271
|
+
"data-testid": `factor-submit-${factor.key}`,
|
|
272
|
+
children: "Submit"
|
|
273
|
+
}
|
|
274
|
+
),
|
|
275
|
+
/* @__PURE__ */ jsx(
|
|
276
|
+
"button",
|
|
277
|
+
{
|
|
278
|
+
type: "button",
|
|
279
|
+
onClick: () => setShowInput(false),
|
|
280
|
+
className: "border border-border rounded px-2 py-0.5 text-[10px] font-medium",
|
|
281
|
+
children: "Cancel"
|
|
282
|
+
}
|
|
283
|
+
)
|
|
284
|
+
] })
|
|
285
|
+
] })
|
|
286
|
+
] })
|
|
287
|
+
] })
|
|
137
288
|
]
|
|
138
289
|
}
|
|
139
290
|
);
|
|
@@ -146,7 +297,10 @@ function SignalPriorityPopover({
|
|
|
146
297
|
metaText,
|
|
147
298
|
feedbackChips,
|
|
148
299
|
onFeedbackSubmit,
|
|
149
|
-
className
|
|
300
|
+
className,
|
|
301
|
+
initialFactorFeedback,
|
|
302
|
+
onFactorFeedback,
|
|
303
|
+
initialPriorityFeedback
|
|
150
304
|
}) {
|
|
151
305
|
const urgencyLabel = getSignalScoreUrgencyLabel(score, providedLabel);
|
|
152
306
|
const scoreRange = scoreRangeForUrgency(urgencyLabel);
|
|
@@ -155,6 +309,7 @@ function SignalPriorityPopover({
|
|
|
155
309
|
const triggerDefault = URGENCY_TRIGGER_DEFAULT[urgencyLabel];
|
|
156
310
|
const triggerHover = URGENCY_TRIGGER_HOVER[urgencyLabel];
|
|
157
311
|
const triggerOpen = URGENCY_TRIGGER_OPEN[urgencyLabel];
|
|
312
|
+
const footerFeedbackKey = `priority-${score}-${urgencyLabel}`;
|
|
158
313
|
return /* @__PURE__ */ jsxs(PopoverPrimitive.Root, { open, onOpenChange: setOpen, children: [
|
|
159
314
|
/* @__PURE__ */ jsx(PopoverPrimitive.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
160
315
|
"button",
|
|
@@ -222,7 +377,15 @@ function SignalPriorityPopover({
|
|
|
222
377
|
"Score = weighted sum"
|
|
223
378
|
] })
|
|
224
379
|
] }),
|
|
225
|
-
/* @__PURE__ */ jsx("div", { className: "divide-y divide-border/40", children: factors.map((factor) => /* @__PURE__ */ jsx(
|
|
380
|
+
/* @__PURE__ */ jsx("div", { className: "divide-y divide-border/40", children: factors.map((factor) => /* @__PURE__ */ jsx(
|
|
381
|
+
PriorityFactorRow,
|
|
382
|
+
{
|
|
383
|
+
factor,
|
|
384
|
+
initialFeedback: initialFactorFeedback == null ? void 0 : initialFactorFeedback[factor.key],
|
|
385
|
+
onFactorFeedback
|
|
386
|
+
},
|
|
387
|
+
factor.key
|
|
388
|
+
)) })
|
|
226
389
|
] }),
|
|
227
390
|
onFeedbackSubmit && /* @__PURE__ */ jsx("div", { className: "border-t border-border", children: /* @__PURE__ */ jsx(
|
|
228
391
|
FeedbackFooter,
|
|
@@ -233,7 +396,9 @@ function SignalPriorityPopover({
|
|
|
233
396
|
metaText,
|
|
234
397
|
negativeChips: feedbackChips != null ? feedbackChips : DEFAULT_PRIORITY_FEEDBACK_CHIPS,
|
|
235
398
|
positivePrompt: "Thanks. Anything to keep about this score?",
|
|
236
|
-
className: "px-4 py-3"
|
|
399
|
+
className: "px-4 py-3",
|
|
400
|
+
initialFeedback: initialPriorityFeedback,
|
|
401
|
+
feedbackKey: footerFeedbackKey
|
|
237
402
|
}
|
|
238
403
|
) })
|
|
239
404
|
]
|