--- phase: 09-weight-classification-and-visualization plan: 02 type: execute wave: 2 depends_on: ["09-01"] files_modified: - src/client/components/WeightSummaryCard.tsx - src/client/routes/setups/$setupId.tsx - package.json autonomous: false requirements: [CLAS-02, VIZZ-01, VIZZ-02, VIZZ-03] must_haves: truths: - "Setup detail view shows separate weight subtotals for base weight, worn weight, consumable weight, and total" - "User can view a donut chart showing weight distribution by category in the setup" - "User can toggle the chart between category breakdown and classification breakdown via pill toggle" - "Hovering a chart segment shows category/classification name, weight in selected unit, and percentage" - "Total weight displayed in the center of the donut hole" artifacts: - path: "src/client/components/WeightSummaryCard.tsx" provides: "Summary card with weight subtotals, donut chart, pill toggle, and tooltips" min_lines: 100 - path: "src/client/routes/setups/$setupId.tsx" provides: "WeightSummaryCard rendered below sticky bar when setup has items" - path: "package.json" provides: "recharts dependency installed" contains: "recharts" key_links: - from: "src/client/components/WeightSummaryCard.tsx" to: "recharts" via: "PieChart, Pie, Cell, Tooltip, Label, ResponsiveContainer imports" pattern: "from.*recharts" - from: "src/client/components/WeightSummaryCard.tsx" to: "src/client/lib/formatters.ts" via: "formatWeight for subtotals and tooltip display" pattern: "formatWeight" - from: "src/client/routes/setups/$setupId.tsx" to: "src/client/components/WeightSummaryCard.tsx" via: "WeightSummaryCard rendered with setup.items prop" pattern: "WeightSummaryCard" --- Add the WeightSummaryCard component with classification weight subtotals, a donut chart for weight distribution, and a pill toggle for switching between category and classification views. Purpose: Users need to visualize how weight is distributed across their setup -- both by gear category (shelter, sleep, cook) and by classification (base weight, worn, consumable). The donut chart with tooltips makes weight analysis intuitive. Output: A summary card below the setup sticky bar showing Base | Worn | Consumable | Total weight columns alongside a donut chart with interactive tooltips, togglable between category and classification breakdowns. @/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md @/home/jlmak/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/09-weight-classification-and-visualization/09-CONTEXT.md @.planning/phases/09-weight-classification-and-visualization/09-RESEARCH.md @.planning/phases/09-weight-classification-and-visualization/09-01-SUMMARY.md From src/client/hooks/useSetups.ts (after Plan 01): ```typescript interface SetupItemWithCategory { id: number; name: string; weightGrams: number | null; priceCents: number | null; categoryId: number; notes: string | null; productUrl: string | null; imageFilename: string | null; createdAt: string; updatedAt: string; categoryName: string; categoryIcon: string; classification: string; // "base" | "worn" | "consumable" -- added by Plan 01 } ``` From src/client/lib/formatters.ts: ```typescript export type WeightUnit = "g" | "oz" | "lb" | "kg"; export function formatWeight(grams: number | null | undefined, unit: WeightUnit = "g"): string; ``` From src/client/hooks/useWeightUnit.ts: ```typescript export function useWeightUnit(): WeightUnit; ``` From 09-RESEARCH.md (Recharts pattern): ```typescript import { PieChart, Pie, Cell, Tooltip, Label, ResponsiveContainer } from "recharts"; // Use Cell for per-slice colors (still functional in v3, deprecated for v4) // Use fixed numeric height on ResponsiveContainer (e.g., height={200}) // Filter out zero-weight entries before passing to chart ``` From 09-RESEARCH.md (color palettes): ```typescript const CATEGORY_COLORS = [ "#6366f1", "#f59e0b", "#10b981", "#ef4444", "#8b5cf6", "#06b6d4", "#f97316", "#ec4899", "#14b8a6", "#84cc16", ]; const CLASSIFICATION_COLORS = { base: "#6366f1", // indigo worn: "#f59e0b", // amber consumable: "#10b981", // emerald }; ``` From 09-CONTEXT.md (locked decisions): - Summary card below sticky bar, always visible when setup has items - Card with columns layout: Base | Worn | Consumable | Total - Donut chart inside the summary card alongside weight subtotals - Pill toggle above the chart: "Category" / "Classification" (same style as weight unit selector) - Total weight in center of donut hole - Hover tooltips: segment name, weight in selected unit, percentage - Chart library: Recharts (PieChart + Pie with innerRadius) Task 1: Install Recharts, create WeightSummaryCard, wire into setup detail page src/client/components/WeightSummaryCard.tsx, src/client/routes/setups/$setupId.tsx, package.json 1. **Install Recharts**: Run `bun add recharts`. This adds recharts to package.json. React and react-dom are already peer deps in the project. 2. **Create WeightSummaryCard component** (`src/client/components/WeightSummaryCard.tsx`): **Props interface:** ```typescript interface WeightSummaryCardProps { items: SetupItemWithCategory[]; // from useSetups hook (includes classification field) } ``` Import `SetupItemWithCategory` from `../hooks/useSetups`. **State:** `viewMode: "category" | "classification"` -- local React state, default "category". **Weight subtotals computation** (derive from items array): ```typescript const baseWeight = items.reduce((sum, i) => i.classification === "base" ? sum + (i.weightGrams ?? 0) : sum, 0); const wornWeight = items.reduce((sum, i) => i.classification === "worn" ? sum + (i.weightGrams ?? 0) : sum, 0); const consumableWeight = items.reduce((sum, i) => i.classification === "consumable" ? sum + (i.weightGrams ?? 0) : sum, 0); const totalWeight = baseWeight + wornWeight + consumableWeight; ``` **Chart data transformation:** - `buildCategoryChartData(items)`: Group by `categoryName`, sum `weightGrams`, compute percentage. Filter out zero-weight groups. Return `Array<{ name: string, weight: number, percent: number }>`. - `buildClassificationChartData(items)`: Group by classification using labels ("Base Weight", "Worn", "Consumable"), sum weights, compute percentage. Filter out zero-weight groups. - Select data source based on `viewMode`. **Render structure:** ```

Weight Summary

{chartData.map((entry, index) => ( ))} } />
``` **Pill toggle** (inline component or extracted): - Two buttons in a `bg-gray-100 rounded-full` container: "Category" and "Classification". - Active state: `bg-white text-gray-700 shadow-sm font-medium`. Inactive: `text-gray-400 hover:text-gray-600`. - Same pattern as TotalsBar weight unit selector. **SubtotalColumn** (inline component): - Vertical stack: colored dot (if color provided), label in text-xs text-gray-500, weight value in text-sm font-semibold text-gray-900. **CustomTooltip:** - Props: `active`, `payload`, `unit` (WeightUnit). - When active and payload exists, show: segment name (bold), weight formatted with `formatWeight()`, percentage as `(XX.X%)`. - Styled: `bg-white border border-gray-200 rounded-lg shadow-lg px-3 py-2 text-sm`. **Color selection:** - When `viewMode === "category"`: use `CATEGORY_COLORS` array (cycle through for many categories). - When `viewMode === "classification"`: use `CLASSIFICATION_COLORS` object (keyed by classification value). **Edge cases:** - If all items have null/zero weight, show a placeholder message ("No weight data to display") instead of the chart. - If items array is empty, component should not render (handled by parent). 3. **Wire into setup detail page** (`src/client/routes/setups/$setupId.tsx`): - Import `WeightSummaryCard` from `../../components/WeightSummaryCard`. - Render `` between the actions bar and the items grid (before the `{itemCount > 0 && (` block), but INSIDE the `itemCount > 0` condition so it only shows when there are items. - Exact placement: after the actions `
` and before the items-grouped-by-category `
`, within the `{itemCount > 0 && (...)}` block. 4. **Verify**: Run `bun run build` to ensure no TypeScript errors and Recharts imports resolve correctly. bun run build - WeightSummaryCard renders below sticky bar when setup has items - Shows 4 columns: Base | Worn | Consumable | Total with correct weight values in selected unit - Donut chart renders with colored segments for weight distribution - Pill toggle switches between category view and classification view - Hovering chart segments shows tooltip with name, weight, and percentage - Total weight displayed in center of donut hole - Empty/zero-weight items handled gracefully - Build succeeds with no TypeScript errors Task 2: Visual verification of complete weight classification and visualization N/A Present the user with verification steps for the complete Phase 9 feature set. This checkpoint covers both Plan 01 (classification badges) and Plan 02 (summary card + chart) together. Complete weight classification and visualization system: 1. Classification badges on every item card in setup view (click to cycle: base weight / worn / consumable) 2. Weight summary card with Base | Worn | Consumable | Total subtotals 3. Donut chart with category/classification toggle and hover tooltips 4. Total weight in the center of the donut hole 1. Start dev servers: `bun run dev:server` and `bun run dev:client` 2. Open http://localhost:5173 and navigate to a setup with items (or create one and add items) 3. **Classification badges**: Verify each item card shows a gray pill badge. Click it and confirm it cycles: "Base Weight" -> "Worn" -> "Consumable" -> "Base Weight". Confirm clicking the badge does NOT open the item edit panel. 4. **Classification persistence**: Refresh the page. Confirm classifications are preserved. 5. **Weight subtotals**: With items classified differently, verify the summary card shows correct subtotals for Base, Worn, Consumable, and Total columns. 6. **Donut chart (Category view)**: Verify the donut chart shows colored segments grouped by category. Hover segments to see tooltip with category name, weight, and percentage. 7. **Donut chart (Classification view)**: Click the "Classification" pill toggle. Verify chart segments change to show base/worn/consumable breakdown with different colors. Hover to verify tooltips. 8. **Donut center**: Confirm total weight is displayed in the center of the donut hole in the selected weight unit. 9. **Weight unit**: Toggle the weight unit in the top bar (if available). Confirm all subtotals, chart center, and tooltips update to the new unit. 10. **Add/remove items**: Add another item to the setup. Verify it appears with default "Base Weight" badge and the chart updates. Remove an item and verify classifications for remaining items are preserved. Visual verification by user following steps above User confirms all classification badges, weight subtotals, donut chart, toggle, and tooltips work correctly Type "approved" to complete Phase 9, or describe any issues to address ```bash # Full test suite passes bun test # Build succeeds bun run build # Lint passes bun run lint ``` - WeightSummaryCard visible below sticky bar on setup detail page (only when items exist) - Four weight columns (Base, Worn, Consumable, Total) show correct values in selected unit - Donut chart renders with colored segments proportional to weight distribution - Pill toggle switches between category and classification chart views - Tooltip on hover shows segment name, formatted weight, and percentage - Total weight displayed in center of donut hole - Chart handles edge cases (no weight data, single category, etc.) - User confirms visual appearance matches expectations After completion, create `.planning/phases/09-weight-classification-and-visualization/09-02-SUMMARY.md`