- Add useReorderCandidates mutation hook with apiPatch to /candidates/reorder endpoint - Add candidateViewMode (list|grid) state and setCandidateViewMode to uiStore - Create CandidateListItem component with drag handle, rank badge, horizontal layout - Export RankBadge helper (gold/silver/bronze medal icons for top 3) - Add style prop support to LucideIcon component - Add pros/cons fields to CandidateWithCategory in useThreads.ts
79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import type { CreateCandidate, UpdateCandidate } from "../../shared/types";
|
|
import { apiDelete, apiPatch, apiPost, apiPut } from "../lib/api";
|
|
|
|
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;
|
|
status: "researching" | "ordered" | "arrived";
|
|
pros: string | null;
|
|
cons: 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"] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useReorderCandidates(threadId: number) {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (data: { orderedIds: number[] }) =>
|
|
apiPatch<{ success: boolean }>(
|
|
`/api/threads/${threadId}/candidates/reorder`,
|
|
data,
|
|
),
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["threads", threadId] });
|
|
},
|
|
});
|
|
}
|