feat(02-02): add thread hooks, UI store, tab navigation, and thread list
- Create useThreads/useCandidates TanStack Query hooks - Extend uiStore with candidate panel and resolve dialog state - Add ThreadTabs component for gear/planning tab switching - Add ThreadCard component with candidate count and price range chips - Refactor index.tsx to tabbed HomePage with PlanningView - Create placeholder thread detail route for navigation target
This commit is contained in:
61
src/client/hooks/useCandidates.ts
Normal file
61
src/client/hooks/useCandidates.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { apiPost, apiPut, apiDelete } from "../lib/api";
|
||||
import type { CreateCandidate, UpdateCandidate } from "../../shared/types";
|
||||
|
||||
interface CandidateResponse {
|
||||
id: number;
|
||||
threadId: number;
|
||||
name: string;
|
||||
weightGrams: number | null;
|
||||
priceCents: number | null;
|
||||
categoryId: number;
|
||||
notes: string | null;
|
||||
productUrl: string | null;
|
||||
imageFilename: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export function useCreateCandidate(threadId: number) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: CreateCandidate & { imageFilename?: string }) =>
|
||||
apiPost<CandidateResponse>(`/api/threads/${threadId}/candidates`, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["threads", threadId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["threads"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateCandidate(threadId: number) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
candidateId,
|
||||
...data
|
||||
}: UpdateCandidate & { candidateId: number; imageFilename?: string }) =>
|
||||
apiPut<CandidateResponse>(
|
||||
`/api/threads/${threadId}/candidates/${candidateId}`,
|
||||
data,
|
||||
),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["threads", threadId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["threads"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteCandidate(threadId: number) {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (candidateId: number) =>
|
||||
apiDelete<{ success: boolean }>(
|
||||
`/api/threads/${threadId}/candidates/${candidateId}`,
|
||||
),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["threads", threadId] });
|
||||
queryClient.invalidateQueries({ queryKey: ["threads"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user