import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; import { z } from "zod"; import { CategoryHeader } from "../../components/CategoryHeader"; import { CreateThreadModal } from "../../components/CreateThreadModal"; import { ItemCard } from "../../components/ItemCard"; import { ThreadCard } from "../../components/ThreadCard"; import { ThreadTabs } from "../../components/ThreadTabs"; import { useCategories } from "../../hooks/useCategories"; import { useItems } from "../../hooks/useItems"; import { useThreads } from "../../hooks/useThreads"; import { useTotals } from "../../hooks/useTotals"; import { LucideIcon } from "../../lib/iconData"; import { useUIStore } from "../../stores/uiStore"; const searchSchema = z.object({ tab: z.enum(["gear", "planning"]).catch("gear"), }); export const Route = createFileRoute("/collection/")({ validateSearch: searchSchema, component: CollectionPage, }); function CollectionPage() { const { tab } = Route.useSearch(); const navigate = useNavigate(); function handleTabChange(newTab: "gear" | "planning") { navigate({ to: "/collection", search: { tab: newTab } }); } return (
{tab === "gear" ? : }
); } function CollectionView() { const { data: items, isLoading: itemsLoading } = useItems(); const { data: totals } = useTotals(); const openAddPanel = useUIStore((s) => s.openAddPanel); if (itemsLoading) { return (
{[1, 2, 3].map((i) => (
))}
); } if (!items || items.length === 0) { return (

Your collection is empty

Start cataloging your gear by adding your first item. Track weight, price, and organize by category.

); } // Group items by categoryId const groupedItems = new Map< number, { items: typeof items; categoryName: string; categoryIcon: string } >(); for (const item of items) { const group = groupedItems.get(item.categoryId); if (group) { group.items.push(item); } else { groupedItems.set(item.categoryId, { items: [item], categoryName: item.categoryName, categoryIcon: item.categoryIcon, }); } } // Build category totals lookup const categoryTotalsMap = new Map< number, { totalWeight: number; totalCost: number; itemCount: number } >(); if (totals?.categories) { for (const ct of totals.categories) { categoryTotalsMap.set(ct.categoryId, { totalWeight: ct.totalWeight, totalCost: ct.totalCost, itemCount: ct.itemCount, }); } } return ( <> {Array.from(groupedItems.entries()).map( ([ categoryId, { items: categoryItems, categoryName, categoryIcon }, ]) => { const catTotals = categoryTotalsMap.get(categoryId); return (
{categoryItems.map((item) => ( ))}
); }, )} ); } function PlanningView() { const [activeTab, setActiveTab] = useState<"active" | "resolved">("active"); const [categoryFilter, setCategoryFilter] = useState(null); const openCreateThreadModal = useUIStore((s) => s.openCreateThreadModal); const { data: categories } = useCategories(); const { data: threads, isLoading } = useThreads(activeTab === "resolved"); if (isLoading) { return (
{[1, 2].map((i) => (
))}
); } // Filter threads by active tab and category const filteredThreads = (threads ?? []) .filter((t) => t.status === activeTab) .filter((t) => (categoryFilter ? t.categoryId === categoryFilter : true)); // Determine if we should show the educational empty state const isEmptyNoFilters = filteredThreads.length === 0 && activeTab === "active" && categoryFilter === null && (!threads || threads.length === 0); return (
{/* Header row */}

Planning Threads

{/* Filter row */}
{/* Pill tabs */}
{/* Category filter */}
{/* Content: empty state or thread grid */} {isEmptyNoFilters ? (

Plan your next purchase

1

Create a thread

Start a research thread for gear you're considering

2

Add candidates

Add products you're comparing with prices and weights

3

Pick a winner

Resolve the thread and the winner joins your collection

) : filteredThreads.length === 0 ? (

No threads found

) : (
{filteredThreads.map((thread) => ( ))}
)}
); }