From faa437896fe796922f51e841f43fce91986a19a3 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Mon, 16 Mar 2026 12:23:19 +0100 Subject: [PATCH] feat(07-02): add weight unit toggle and wire all formatWeight call sites - Add segmented g/oz/lb/kg toggle to TotalsBar with settings persistence - Pass unit parameter to all 8 formatWeight call sites across components and routes - Import useWeightUnit hook in ItemCard, CandidateCard, CategoryHeader, SetupCard, ItemPicker, Dashboard, SetupDetail Co-Authored-By: Claude Opus 4.6 --- src/client/components/CandidateCard.tsx | 4 +- src/client/components/CategoryHeader.tsx | 4 +- src/client/components/ItemCard.tsx | 4 +- src/client/components/ItemPicker.tsx | 4 +- src/client/components/SetupCard.tsx | 4 +- src/client/components/TotalsBar.tsx | 58 ++++++++++++++++++------ src/client/routes/index.tsx | 4 +- src/client/routes/setups/$setupId.tsx | 4 +- 8 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/client/components/CandidateCard.tsx b/src/client/components/CandidateCard.tsx index 86df439..f994e1b 100644 --- a/src/client/components/CandidateCard.tsx +++ b/src/client/components/CandidateCard.tsx @@ -1,3 +1,4 @@ +import { useWeightUnit } from "../hooks/useWeightUnit"; import { formatPrice, formatWeight } from "../lib/formatters"; import { LucideIcon } from "../lib/iconData"; import { useUIStore } from "../stores/uiStore"; @@ -27,6 +28,7 @@ export function CandidateCard({ threadId, isActive, }: CandidateCardProps) { + const unit = useWeightUnit(); const openCandidateEditPanel = useUIStore((s) => s.openCandidateEditPanel); const openConfirmDeleteCandidate = useUIStore( (s) => s.openConfirmDeleteCandidate, @@ -88,7 +90,7 @@ export function CandidateCard({
{weightGrams != null && ( - {formatWeight(weightGrams)} + {formatWeight(weightGrams, unit)} )} {priceCents != null && ( diff --git a/src/client/components/CategoryHeader.tsx b/src/client/components/CategoryHeader.tsx index c24731a..33ee346 100644 --- a/src/client/components/CategoryHeader.tsx +++ b/src/client/components/CategoryHeader.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; import { useDeleteCategory, useUpdateCategory } from "../hooks/useCategories"; +import { useWeightUnit } from "../hooks/useWeightUnit"; import { formatPrice, formatWeight } from "../lib/formatters"; import { LucideIcon } from "../lib/iconData"; import { IconPicker } from "./IconPicker"; @@ -21,6 +22,7 @@ export function CategoryHeader({ totalCost, itemCount, }: CategoryHeaderProps) { + const unit = useWeightUnit(); const [isEditing, setIsEditing] = useState(false); const [editName, setEditName] = useState(name); const [editIcon, setEditIcon] = useState(icon); @@ -85,7 +87,7 @@ export function CategoryHeader({

{name}

{itemCount} {itemCount === 1 ? "item" : "items"} ·{" "} - {formatWeight(totalWeight)} · {formatPrice(totalCost)} + {formatWeight(totalWeight, unit)} · {formatPrice(totalCost)} {!isUncategorized && (
diff --git a/src/client/components/ItemCard.tsx b/src/client/components/ItemCard.tsx index e5fc5c4..d11bd02 100644 --- a/src/client/components/ItemCard.tsx +++ b/src/client/components/ItemCard.tsx @@ -1,3 +1,4 @@ +import { useWeightUnit } from "../hooks/useWeightUnit"; import { formatPrice, formatWeight } from "../lib/formatters"; import { LucideIcon } from "../lib/iconData"; import { useUIStore } from "../stores/uiStore"; @@ -25,6 +26,7 @@ export function ItemCard({ productUrl, onRemove, }: ItemCardProps) { + const unit = useWeightUnit(); const openEditPanel = useUIStore((s) => s.openEditPanel); const openExternalLink = useUIStore((s) => s.openExternalLink); @@ -122,7 +124,7 @@ export function ItemCard({
{weightGrams != null && ( - {formatWeight(weightGrams)} + {formatWeight(weightGrams, unit)} )} {priceCents != null && ( diff --git a/src/client/components/ItemPicker.tsx b/src/client/components/ItemPicker.tsx index af2b16a..d1b4267 100644 --- a/src/client/components/ItemPicker.tsx +++ b/src/client/components/ItemPicker.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from "react"; import { useItems } from "../hooks/useItems"; import { useSyncSetupItems } from "../hooks/useSetups"; +import { useWeightUnit } from "../hooks/useWeightUnit"; import { formatPrice, formatWeight } from "../lib/formatters"; import { LucideIcon } from "../lib/iconData"; import { SlideOutPanel } from "./SlideOutPanel"; @@ -20,6 +21,7 @@ export function ItemPicker({ }: ItemPickerProps) { const { data: items } = useItems(); const syncItems = useSyncSetupItems(setupId); + const unit = useWeightUnit(); const [selectedIds, setSelectedIds] = useState>(new Set()); // Reset selected IDs when panel opens @@ -114,7 +116,7 @@ export function ItemPicker({ {item.weightGrams != null && - formatWeight(item.weightGrams)} + formatWeight(item.weightGrams, unit)} {item.weightGrams != null && item.priceCents != null && " · "} diff --git a/src/client/components/SetupCard.tsx b/src/client/components/SetupCard.tsx index 68cd6a6..8dd87da 100644 --- a/src/client/components/SetupCard.tsx +++ b/src/client/components/SetupCard.tsx @@ -1,4 +1,5 @@ import { Link } from "@tanstack/react-router"; +import { useWeightUnit } from "../hooks/useWeightUnit"; import { formatPrice, formatWeight } from "../lib/formatters"; interface SetupCardProps { @@ -16,6 +17,7 @@ export function SetupCard({ totalWeight, totalCost, }: SetupCardProps) { + const unit = useWeightUnit(); return (
- {formatWeight(totalWeight)} + {formatWeight(totalWeight, unit)} {formatPrice(totalCost)} diff --git a/src/client/components/TotalsBar.tsx b/src/client/components/TotalsBar.tsx index f7a126c..b6fa5a9 100644 --- a/src/client/components/TotalsBar.tsx +++ b/src/client/components/TotalsBar.tsx @@ -1,8 +1,12 @@ import { Link } from "@tanstack/react-router"; +import { useUpdateSetting } from "../hooks/useSettings"; import { useTotals } from "../hooks/useTotals"; -import { formatPrice, formatWeight } from "../lib/formatters"; +import { useWeightUnit } from "../hooks/useWeightUnit"; +import { formatPrice, formatWeight, type WeightUnit } from "../lib/formatters"; import { LucideIcon } from "../lib/iconData"; +const UNITS: WeightUnit[] = ["g", "oz", "lb", "kg"]; + interface TotalsBarProps { title?: string; stats?: Array<{ label: string; value: string }>; @@ -15,6 +19,8 @@ export function TotalsBar({ linkTo, }: TotalsBarProps) { const { data } = useTotals(); + const unit = useWeightUnit(); + const updateSetting = useUpdateSetting(); // When no stats provided, use global totals (backward compatible) const displayStats = @@ -22,12 +28,15 @@ export function TotalsBar({ (data?.global ? [ { label: "items", value: String(data.global.itemCount) }, - { label: "total", value: formatWeight(data.global.totalWeight) }, + { + label: "total", + value: formatWeight(data.global.totalWeight, unit), + }, { label: "spent", value: formatPrice(data.global.totalCost) }, ] : [ { label: "items", value: "0" }, - { label: "total", value: formatWeight(null) }, + { label: "total", value: formatWeight(null, unit) }, { label: "spent", value: formatPrice(null) }, ]); @@ -57,18 +66,41 @@ export function TotalsBar({
{titleElement} - {showStats && ( -
- {displayStats.map((stat) => ( - - - {stat.value} - {" "} - {stat.label} - +
+
+ {UNITS.map((u) => ( + ))}
- )} + {showStats && ( +
+ {displayStats.map((stat) => ( + + + {stat.value} + {" "} + {stat.label} + + ))} +
+ )} +
diff --git a/src/client/routes/index.tsx b/src/client/routes/index.tsx index f3ee600..ef70f9d 100644 --- a/src/client/routes/index.tsx +++ b/src/client/routes/index.tsx @@ -3,6 +3,7 @@ import { DashboardCard } from "../components/DashboardCard"; import { useSetups } from "../hooks/useSetups"; import { useThreads } from "../hooks/useThreads"; import { useTotals } from "../hooks/useTotals"; +import { useWeightUnit } from "../hooks/useWeightUnit"; import { formatPrice, formatWeight } from "../lib/formatters"; export const Route = createFileRoute("/")({ @@ -13,6 +14,7 @@ function DashboardPage() { const { data: totals } = useTotals(); const { data: threads } = useThreads(false); const { data: setups } = useSetups(); + const unit = useWeightUnit(); const global = totals?.global; const activeThreadCount = threads?.length ?? 0; @@ -29,7 +31,7 @@ function DashboardPage() { { label: "Items", value: String(global?.itemCount ?? 0) }, { label: "Weight", - value: formatWeight(global?.totalWeight ?? null), + value: formatWeight(global?.totalWeight ?? null, unit), }, { label: "Cost", value: formatPrice(global?.totalCost ?? null) }, ]} diff --git a/src/client/routes/setups/$setupId.tsx b/src/client/routes/setups/$setupId.tsx index 6dd3b71..7a48e05 100644 --- a/src/client/routes/setups/$setupId.tsx +++ b/src/client/routes/setups/$setupId.tsx @@ -8,6 +8,7 @@ import { useRemoveSetupItem, useSetup, } from "../../hooks/useSetups"; +import { useWeightUnit } from "../../hooks/useWeightUnit"; import { formatPrice, formatWeight } from "../../lib/formatters"; import { LucideIcon } from "../../lib/iconData"; @@ -17,6 +18,7 @@ export const Route = createFileRoute("/setups/$setupId")({ function SetupDetailPage() { const { setupId } = Route.useParams(); + const unit = useWeightUnit(); const navigate = useNavigate(); const numericId = Number(setupId); const { data: setup, isLoading } = useSetup(numericId); @@ -105,7 +107,7 @@ function SetupDetailPage() {
- {formatWeight(totalWeight)} + {formatWeight(totalWeight, unit)} {" "} total