+ <>
{Array.from(groupedItems.entries()).map(
([categoryId, { items: categoryItems, categoryName, categoryEmoji }]) => {
const catTotals = categoryTotalsMap.get(categoryId);
@@ -133,6 +159,94 @@ function CollectionPage() {
);
},
)}
+ >
+ );
+}
+
+function PlanningView() {
+ const [showResolved, setShowResolved] = useState(false);
+ const [newThreadName, setNewThreadName] = useState("");
+ const { data: threads, isLoading } = useThreads(showResolved);
+ const createThread = useCreateThread();
+
+ function handleCreateThread(e: React.FormEvent) {
+ e.preventDefault();
+ const name = newThreadName.trim();
+ if (!name) return;
+ createThread.mutate(
+ { name },
+ { onSuccess: () => setNewThreadName("") },
+ );
+ }
+
+ if (isLoading) {
+ return (
+
+ {[1, 2].map((i) => (
+
+ ))}
+
+ );
+ }
+
+ return (
+
+ {/* Create thread form */}
+
+
+ {/* Show resolved toggle */}
+
+
+ {/* Thread list */}
+ {!threads || threads.length === 0 ? (
+
+
🔍
+
+ No planning threads yet
+
+
+ Start one to research your next purchase.
+
+
+ ) : (
+
+ {threads.map((thread) => (
+
+ ))}
+
+ )}
);
}
diff --git a/src/client/routes/threads/$threadId.tsx b/src/client/routes/threads/$threadId.tsx
new file mode 100644
index 0000000..8f88d39
--- /dev/null
+++ b/src/client/routes/threads/$threadId.tsx
@@ -0,0 +1,15 @@
+import { createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/threads/$threadId")({
+ component: ThreadDetailPage,
+});
+
+function ThreadDetailPage() {
+ const { threadId } = Route.useParams();
+
+ return (
+
+
Thread {threadId} - detail page placeholder
+
+ );
+}
diff --git a/src/client/stores/uiStore.ts b/src/client/stores/uiStore.ts
index 1588038..2b82fdd 100644
--- a/src/client/stores/uiStore.ts
+++ b/src/client/stores/uiStore.ts
@@ -1,6 +1,7 @@
import { create } from "zustand";
interface UIState {
+ // Item panel state
panelMode: "closed" | "add" | "edit";
editingItemId: number | null;
confirmDeleteItemId: number | null;
@@ -10,9 +11,28 @@ interface UIState {
closePanel: () => void;
openConfirmDelete: (itemId: number) => void;
closeConfirmDelete: () => void;
+
+ // Candidate panel state
+ candidatePanelMode: "closed" | "add" | "edit";
+ editingCandidateId: number | null;
+ confirmDeleteCandidateId: number | null;
+
+ openCandidateAddPanel: () => void;
+ openCandidateEditPanel: (id: number) => void;
+ closeCandidatePanel: () => void;
+ openConfirmDeleteCandidate: (id: number) => void;
+ closeConfirmDeleteCandidate: () => void;
+
+ // Resolution dialog state
+ resolveThreadId: number | null;
+ resolveCandidateId: number | null;
+
+ openResolveDialog: (threadId: number, candidateId: number) => void;
+ closeResolveDialog: () => void;
}
export const useUIStore = create
((set) => ({
+ // Item panel
panelMode: "closed",
editingItemId: null,
confirmDeleteItemId: null,
@@ -22,4 +42,29 @@ export const useUIStore = create((set) => ({
closePanel: () => set({ panelMode: "closed", editingItemId: null }),
openConfirmDelete: (itemId) => set({ confirmDeleteItemId: itemId }),
closeConfirmDelete: () => set({ confirmDeleteItemId: null }),
+
+ // Candidate panel
+ candidatePanelMode: "closed",
+ editingCandidateId: null,
+ confirmDeleteCandidateId: null,
+
+ openCandidateAddPanel: () =>
+ set({ candidatePanelMode: "add", editingCandidateId: null }),
+ openCandidateEditPanel: (id) =>
+ set({ candidatePanelMode: "edit", editingCandidateId: id }),
+ closeCandidatePanel: () =>
+ set({ candidatePanelMode: "closed", editingCandidateId: null }),
+ openConfirmDeleteCandidate: (id) =>
+ set({ confirmDeleteCandidateId: id }),
+ closeConfirmDeleteCandidate: () =>
+ set({ confirmDeleteCandidateId: null }),
+
+ // Resolution dialog
+ resolveThreadId: null,
+ resolveCandidateId: null,
+
+ openResolveDialog: (threadId, candidateId) =>
+ set({ resolveThreadId: threadId, resolveCandidateId: candidateId }),
+ closeResolveDialog: () =>
+ set({ resolveThreadId: null, resolveCandidateId: null }),
}));