feat(11-02): add view toggle, Reorder.Group drag-to-reorder, and rank badges in grid view

- Thread detail page: list/grid view toggle with LayoutList/LayoutGrid icons
- List view (active threads): Reorder.Group with CandidateListItem for drag-to-reorder
- List view (resolved threads): static CandidateListItem with rank badges, no drag handles
- Grid view: CandidateCard components with rank badges (gold/silver/bronze)
- tempItems pattern prevents React Query flicker during drag
- handleDragEnd fires PATCH /candidates/reorder after drag completes
- View toggle defaults to list view via uiStore candidateViewMode
This commit is contained in:
2026-03-16 22:28:53 +01:00
parent acfa99516d
commit 94c07e79c2
2 changed files with 112 additions and 8 deletions

View File

@@ -3,6 +3,7 @@ import { useWeightUnit } from "../hooks/useWeightUnit";
import { formatPrice, formatWeight } from "../lib/formatters";
import { LucideIcon } from "../lib/iconData";
import { useUIStore } from "../stores/uiStore";
import { RankBadge } from "./CandidateListItem";
import { StatusBadge } from "./StatusBadge";
interface CandidateCardProps {
@@ -20,6 +21,7 @@ interface CandidateCardProps {
onStatusChange: (status: "researching" | "ordered" | "arrived") => void;
pros?: string | null;
cons?: string | null;
rank?: number;
}
export function CandidateCard({
@@ -37,6 +39,7 @@ export function CandidateCard({
onStatusChange,
pros,
cons,
rank,
}: CandidateCardProps) {
const unit = useWeightUnit();
const currency = useCurrency();
@@ -159,6 +162,7 @@ export function CandidateCard({
{name}
</h3>
<div className="flex flex-wrap gap-1.5">
{rank != null && <RankBadge rank={rank} />}
{weightGrams != null && (
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-400">
{formatWeight(weightGrams, unit)}