- Rename categoryEmoji to categoryIcon in ItemCard, CandidateCard, ThreadCard, ItemPicker - Import and render LucideIcon at appropriate sizes (36px placeholder, 14-16px badges) - Update hook interfaces to match server API (categoryIcon instead of categoryEmoji) - Rename iconData.ts to iconData.tsx (contains JSX) - Update useCategories mutation type to use icon instead of emoji Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
117 lines
3.0 KiB
TypeScript
117 lines
3.0 KiB
TypeScript
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import { apiGet, apiPost, apiPut, apiDelete } from "../lib/api";
|
|
|
|
interface ThreadListItem {
|
|
id: number;
|
|
name: string;
|
|
status: "active" | "resolved";
|
|
resolvedCandidateId: number | null;
|
|
categoryId: number;
|
|
categoryName: string;
|
|
categoryIcon: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
candidateCount: number;
|
|
minPriceCents: number | null;
|
|
maxPriceCents: number | null;
|
|
}
|
|
|
|
interface CandidateWithCategory {
|
|
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;
|
|
categoryName: string;
|
|
categoryIcon: string;
|
|
}
|
|
|
|
interface ThreadWithCandidates {
|
|
id: number;
|
|
name: string;
|
|
status: "active" | "resolved";
|
|
resolvedCandidateId: number | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
candidates: CandidateWithCategory[];
|
|
}
|
|
|
|
export function useThreads(includeResolved = false) {
|
|
return useQuery({
|
|
queryKey: ["threads", { includeResolved }],
|
|
queryFn: () =>
|
|
apiGet<ThreadListItem[]>(
|
|
`/api/threads${includeResolved ? "?includeResolved=true" : ""}`,
|
|
),
|
|
});
|
|
}
|
|
|
|
export function useThread(threadId: number | null) {
|
|
return useQuery({
|
|
queryKey: ["threads", threadId],
|
|
queryFn: () => apiGet<ThreadWithCandidates>(`/api/threads/${threadId}`),
|
|
enabled: threadId != null,
|
|
});
|
|
}
|
|
|
|
export function useCreateThread() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (data: { name: string; categoryId: number }) =>
|
|
apiPost<ThreadListItem>("/api/threads", data),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["threads"] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUpdateThread() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ id, ...data }: { id: number; name?: string }) =>
|
|
apiPut<ThreadListItem>(`/api/threads/${id}`, data),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["threads"] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useDeleteThread() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (id: number) =>
|
|
apiDelete<{ success: boolean }>(`/api/threads/${id}`),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["threads"] });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useResolveThread() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({
|
|
threadId,
|
|
candidateId,
|
|
}: {
|
|
threadId: number;
|
|
candidateId: number;
|
|
}) =>
|
|
apiPost<{ success: boolean; item: unknown }>(
|
|
`/api/threads/${threadId}/resolve`,
|
|
{ candidateId },
|
|
),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["threads"] });
|
|
queryClient.invalidateQueries({ queryKey: ["items"] });
|
|
queryClient.invalidateQueries({ queryKey: ["totals"] });
|
|
},
|
|
});
|
|
}
|