- 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 <noreply@anthropic.com>
109 lines
3.0 KiB
TypeScript
109 lines
3.0 KiB
TypeScript
import { Link } from "@tanstack/react-router";
|
|
import { useUpdateSetting } from "../hooks/useSettings";
|
|
import { useTotals } from "../hooks/useTotals";
|
|
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 }>;
|
|
linkTo?: string;
|
|
}
|
|
|
|
export function TotalsBar({
|
|
title = "GearBox",
|
|
stats,
|
|
linkTo,
|
|
}: TotalsBarProps) {
|
|
const { data } = useTotals();
|
|
const unit = useWeightUnit();
|
|
const updateSetting = useUpdateSetting();
|
|
|
|
// When no stats provided, use global totals (backward compatible)
|
|
const displayStats =
|
|
stats ??
|
|
(data?.global
|
|
? [
|
|
{ label: "items", value: String(data.global.itemCount) },
|
|
{
|
|
label: "total",
|
|
value: formatWeight(data.global.totalWeight, unit),
|
|
},
|
|
{ label: "spent", value: formatPrice(data.global.totalCost) },
|
|
]
|
|
: [
|
|
{ label: "items", value: "0" },
|
|
{ label: "total", value: formatWeight(null, unit) },
|
|
{ label: "spent", value: formatPrice(null) },
|
|
]);
|
|
|
|
const titleContent = (
|
|
<span className="flex items-center gap-2">
|
|
<LucideIcon name="package" size={20} className="text-gray-500" />
|
|
{title}
|
|
</span>
|
|
);
|
|
|
|
const titleElement = linkTo ? (
|
|
<Link
|
|
to={linkTo}
|
|
className="text-lg font-semibold text-gray-900 hover:text-gray-600 transition-colors"
|
|
>
|
|
{titleContent}
|
|
</Link>
|
|
) : (
|
|
<h1 className="text-lg font-semibold text-gray-900">{titleContent}</h1>
|
|
);
|
|
|
|
// If stats prop is explicitly an empty array, show title only (dashboard mode)
|
|
const showStats = stats === undefined || stats.length > 0;
|
|
|
|
return (
|
|
<div className="sticky top-0 z-10 bg-white border-b border-gray-100">
|
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
<div className="flex items-center justify-between h-14">
|
|
{titleElement}
|
|
<div className="flex items-center gap-4">
|
|
<div className="flex items-center gap-1 bg-gray-100 rounded-full px-1 py-0.5">
|
|
{UNITS.map((u) => (
|
|
<button
|
|
key={u}
|
|
type="button"
|
|
onClick={() =>
|
|
updateSetting.mutate({
|
|
key: "weightUnit",
|
|
value: u,
|
|
})
|
|
}
|
|
className={`px-2 py-0.5 text-xs rounded-full transition-colors ${
|
|
unit === u
|
|
? "bg-white text-gray-700 shadow-sm font-medium"
|
|
: "text-gray-400 hover:text-gray-600"
|
|
}`}
|
|
>
|
|
{u}
|
|
</button>
|
|
))}
|
|
</div>
|
|
{showStats && (
|
|
<div className="flex items-center gap-6 text-sm text-gray-500">
|
|
{displayStats.map((stat) => (
|
|
<span key={stat.label}>
|
|
<span className="font-medium text-gray-700">
|
|
{stat.value}
|
|
</span>{" "}
|
|
{stat.label}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|