feat: add setup impact preview UI with delta badges across all views

Adds SetupImpactSelector dropdown and ImpactDeltaBadge inline badge, wired into the thread detail page. Delta badges appear on CandidateListItem, CandidateCard, and ComparisonTable (Weight Impact / Price Impact rows) whenever a setup is selected for comparison.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 18:11:57 +02:00
parent b9a06dd244
commit 8c1fe47a99
6 changed files with 153 additions and 1 deletions

View File

@@ -4,10 +4,13 @@ import { useEffect, useState } from "react";
import { CandidateCard } from "../../components/CandidateCard";
import { CandidateListItem } from "../../components/CandidateListItem";
import { ComparisonTable } from "../../components/ComparisonTable";
import { SetupImpactSelector } from "../../components/SetupImpactSelector";
import {
useReorderCandidates,
useUpdateCandidate,
} from "../../hooks/useCandidates";
import { useImpactDeltas } from "../../hooks/useImpactDeltas";
import { useSetup } from "../../hooks/useSetups";
import { useThread } from "../../hooks/useThreads";
import { LucideIcon } from "../../lib/iconData";
import { useUIStore } from "../../stores/uiStore";
@@ -23,8 +26,15 @@ function ThreadDetailPage() {
const openCandidateAddPanel = useUIStore((s) => s.openCandidateAddPanel);
const candidateViewMode = useUIStore((s) => s.candidateViewMode);
const setCandidateViewMode = useUIStore((s) => s.setCandidateViewMode);
const selectedSetupId = useUIStore((s) => s.selectedSetupId);
const updateCandidate = useUpdateCandidate(threadId);
const reorderMutation = useReorderCandidates(threadId);
const { data: setupData } = useSetup(selectedSetupId);
const { deltas } = useImpactDeltas(
thread?.candidates ?? [],
setupData?.items,
thread?.categoryId ?? 0,
);
const [tempItems, setTempItems] =
useState<typeof thread extends { candidates: infer C } ? C : never | null>(
@@ -120,7 +130,7 @@ function ThreadDetailPage() {
)}
{/* Toolbar: Add candidate + view toggle */}
<div className="mb-6 flex items-center gap-3">
<div className="mb-6 flex items-center gap-3 flex-wrap">
{isActive && candidateViewMode !== "compare" && (
<button
type="button"
@@ -185,6 +195,7 @@ function ThreadDetailPage() {
)}
</div>
)}
<SetupImpactSelector threadStatus={thread.status} />
</div>
{/* Candidates */}
@@ -208,6 +219,7 @@ function ThreadDetailPage() {
<ComparisonTable
candidates={displayItems}
resolvedCandidateId={thread.resolvedCandidateId}
deltas={deltas}
/>
) : candidateViewMode === "list" ? (
isActive ? (
@@ -230,6 +242,7 @@ function ThreadDetailPage() {
status: newStatus,
})
}
delta={deltas[candidate.id]}
/>
))}
</Reorder.Group>
@@ -247,6 +260,7 @@ function ThreadDetailPage() {
status: newStatus,
})
}
delta={deltas[candidate.id]}
/>
))}
</div>
@@ -276,6 +290,7 @@ function ThreadDetailPage() {
pros={candidate.pros}
cons={candidate.cons}
rank={index + 1}
delta={deltas[candidate.id]}
/>
))}
</div>