feat(03-02): navigation restructure, TotalsBar refactor, and setup hooks

- Move collection view from / to /collection with gear/planning tabs
- Rewrite / as dashboard with three summary cards (Collection, Planning, Setups)
- Refactor TotalsBar to accept optional stats/linkTo/title props
- Create DashboardCard component for dashboard summary cards
- Create useSetups hooks (CRUD + sync/remove item mutations)
- Update __root.tsx with route-aware TotalsBar, FAB visibility, resolve navigation
- Add item picker and setup delete UI state to uiStore
- Invalidate setups queries on item update/delete for stale data prevention
- Update routeTree.gen.ts with new collection/setups routes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 12:49:03 +01:00
parent c2b8985d37
commit 86a7a0def1
11 changed files with 637 additions and 270 deletions

View File

@@ -59,14 +59,38 @@ function RootLayout() {
const isItemPanelOpen = panelMode !== "closed";
const isCandidatePanelOpen = candidatePanelMode !== "closed";
// Detect if we're on a thread detail page to get the threadId for candidate forms
// Route matching for contextual behavior
const matchRoute = useMatchRoute();
const threadMatch = matchRoute({
to: "/threads/$threadId",
fuzzy: true,
}) as { threadId?: string } | false;
const currentThreadId = threadMatch ? Number(threadMatch.threadId) : null;
const isDashboard = !!matchRoute({ to: "/" });
const isCollection = !!matchRoute({ to: "/collection", fuzzy: true });
const isSetupDetail = !!matchRoute({ to: "/setups/$setupId", fuzzy: true });
// Determine TotalsBar props based on current route
const totalsBarProps = isDashboard
? { stats: [] as Array<{ label: string; value: string }> } // Title only, no stats, no link
: isSetupDetail
? { linkTo: "/" } // Setup detail will render its own local bar; root bar just has link
: { linkTo: "/" }; // All other pages: default stats + link to dashboard
// On dashboard, don't show the default global stats - pass empty stats
// On collection, let TotalsBar fetch its own global stats (default behavior)
const finalTotalsProps = isDashboard
? { stats: [] as Array<{ label: string; value: string }> }
: isCollection
? { linkTo: "/" }
: { linkTo: "/" };
// FAB visibility: only show on /collection route when gear tab is active
const collectionSearch = matchRoute({ to: "/collection" }) as { tab?: string } | false;
const showFab = isCollection && (!collectionSearch || (collectionSearch as Record<string, string>).tab !== "planning");
// Show a minimal loading state while checking onboarding status
if (onboardingLoading) {
return (
@@ -78,7 +102,7 @@ function RootLayout() {
return (
<div className="min-h-screen bg-gray-50">
<TotalsBar />
<TotalsBar {...finalTotalsProps} />
<Outlet />
{/* Item Slide-out Panel */}
@@ -135,13 +159,13 @@ function RootLayout() {
onClose={closeResolveDialog}
onResolved={() => {
closeResolveDialog();
navigate({ to: "/", search: { tab: "planning" } });
navigate({ to: "/collection", search: { tab: "planning" } });
}}
/>
)}
{/* Floating Add Button - only on gear tab */}
{!threadMatch && (
{/* Floating Add Button - only on collection gear tab */}
{showFab && (
<button
type="button"
onClick={openAddPanel}