Files

15 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
09-weight-classification-and-visualization 02 execute 2
09-01
src/client/components/WeightSummaryCard.tsx
src/client/routes/setups/$setupId.tsx
package.json
false
CLAS-02
VIZZ-01
VIZZ-02
VIZZ-03
truths artifacts key_links
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
path provides min_lines
src/client/components/WeightSummaryCard.tsx Summary card with weight subtotals, donut chart, pill toggle, and tooltips 100
path provides
src/client/routes/setups/$setupId.tsx WeightSummaryCard rendered below sticky bar when setup has items
path provides contains
package.json recharts dependency installed recharts
from to via pattern
src/client/components/WeightSummaryCard.tsx recharts PieChart, Pie, Cell, Tooltip, Label, ResponsiveContainer imports from.*recharts
from to via pattern
src/client/components/WeightSummaryCard.tsx src/client/lib/formatters.ts formatWeight for subtotals and tooltip display formatWeight
from to via pattern
src/client/routes/setups/$setupId.tsx src/client/components/WeightSummaryCard.tsx WeightSummaryCard rendered with setup.items prop 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.

<execution_context> @/home/jlmak/.claude/get-shit-done/workflows/execute-plan.md @/home/jlmak/.claude/get-shit-done/templates/summary.md </execution_context>

@.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):

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:

export type WeightUnit = "g" | "oz" | "lb" | "kg";
export function formatWeight(grams: number | null | undefined, unit: WeightUnit = "g"): string;

From src/client/hooks/useWeightUnit.ts:

export function useWeightUnit(): WeightUnit;

From 09-RESEARCH.md (Recharts pattern):

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):

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:**
   ```
   <div className="bg-white rounded-xl border border-gray-100 p-5 mb-6">
     <!-- Pill toggle: Category | Classification -->
     <div className="flex items-center justify-between mb-4">
       <h3 className="text-sm font-medium text-gray-700">Weight Summary</h3>
       <PillToggle viewMode={viewMode} onChange={setViewMode} />
     </div>

     <!-- Main content: chart + subtotals side by side -->
     <div className="flex items-center gap-8">
       <!-- Donut chart -->
       <div className="flex-shrink-0" style={{ width: 180, height: 180 }}>
         <ResponsiveContainer width="100%" height={180}>
           <PieChart>
             <Pie data={chartData} dataKey="weight" nameKey="name"
               cx="50%" cy="50%" innerRadius={55} outerRadius={80} paddingAngle={2}>
               {chartData.map((entry, index) => (
                 <Cell key={entry.name} fill={colors[index % colors.length]} />
               ))}
               <Label value={formatWeight(totalWeight, unit)} position="center"
                 style={{ fontSize: "14px", fontWeight: 600, fill: "#374151" }} />
             </Pie>
             <Tooltip content={<CustomTooltip unit={unit} />} />
           </PieChart>
         </ResponsiveContainer>
       </div>

       <!-- Weight subtotals columns -->
       <div className="flex-1 grid grid-cols-4 gap-4">
         <SubtotalColumn label="Base" weight={baseWeight} unit={unit} color="#6366f1" />
         <SubtotalColumn label="Worn" weight={wornWeight} unit={unit} color="#f59e0b" />
         <SubtotalColumn label="Consumable" weight={consumableWeight} unit={unit} color="#10b981" />
         <SubtotalColumn label="Total" weight={totalWeight} unit={unit} />
       </div>
     </div>
   </div>
   ```

   **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 `<WeightSummaryCard items={setup.items} />` 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 `<div>` and before the items-grouped-by-category `<div>`, 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

</verification>

<success_criteria>
- 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
</success_criteria>

<output>
After completion, create `.planning/phases/09-weight-classification-and-visualization/09-02-SUMMARY.md`
</output>