/** * useItems Hook * Manages item data fetching and mutations for the items management interface. */ import { useState, useCallback, useEffect } from "react"; import { toast } from "sonner"; // Full item type matching the database schema export interface ItemWithUsage { id: number; name: string; description: string | null; rarity: string | null; type: string | null; price: bigint | null; iconUrl: string | null; imageUrl: string | null; usageData: { consume: boolean; effects: Array<{ type: string; [key: string]: any; }>; } | null; } export interface ItemFilters { search?: string; type?: string; rarity?: string; limit?: number; offset?: number; } export interface ItemsResponse { items: ItemWithUsage[]; total: number; } export interface CreateItemData { name: string; description?: string | null; rarity?: 'C' | 'R' | 'SR' | 'SSR'; type: 'MATERIAL' | 'CONSUMABLE' | 'EQUIPMENT' | 'QUEST'; price?: string | null; iconUrl?: string; imageUrl?: string; usageData?: { consume: boolean; effects: Array<{ type: string;[key: string]: any }>; } | null; } export interface UpdateItemData extends Partial { } export function useItems(initialFilters: ItemFilters = {}) { const [items, setItems] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(true); const [filters, setFilters] = useState(initialFilters); const fetchItems = useCallback(async (newFilters?: ItemFilters) => { setLoading(true); try { const params = new URLSearchParams(); const activeFilters = newFilters ?? filters; if (activeFilters.search) params.set("search", activeFilters.search); if (activeFilters.type) params.set("type", activeFilters.type); if (activeFilters.rarity) params.set("rarity", activeFilters.rarity); if (activeFilters.limit) params.set("limit", String(activeFilters.limit)); if (activeFilters.offset) params.set("offset", String(activeFilters.offset)); const response = await fetch(`/api/items?${params.toString()}`); if (!response.ok) throw new Error("Failed to fetch items"); const data: ItemsResponse = await response.json(); setItems(data.items); setTotal(data.total); } catch (error) { console.error("Error fetching items:", error); toast.error("Failed to load items", { description: error instanceof Error ? error.message : "Unknown error" }); } finally { setLoading(false); } }, [filters]); useEffect(() => { fetchItems(); }, [fetchItems]); const updateFilters = useCallback((newFilters: Partial) => { setFilters(prev => ({ ...prev, ...newFilters })); }, []); const clearFilters = useCallback(() => { setFilters({}); }, []); return { items, total, loading, filters, fetchItems, updateFilters, clearFilters, }; } export function useItem(id: number | null) { const [item, setItem] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchItem = useCallback(async () => { if (id === null) { setItem(null); return; } setLoading(true); setError(null); try { const response = await fetch(`/api/items/${id}`); if (!response.ok) { if (response.status === 404) { throw new Error("Item not found"); } throw new Error("Failed to fetch item"); } const data = await response.json(); setItem(data); } catch (err) { const message = err instanceof Error ? err.message : "Unknown error"; setError(message); toast.error("Failed to load item", { description: message }); } finally { setLoading(false); } }, [id]); useEffect(() => { fetchItem(); }, [fetchItem]); return { item, loading, error, refetch: fetchItem }; } export function useCreateItem() { const [loading, setLoading] = useState(false); const createItem = useCallback(async (data: CreateItemData, imageFile?: File): Promise => { setLoading(true); try { let response: Response; if (imageFile) { // Multipart form with image const formData = new FormData(); formData.append("data", JSON.stringify(data)); formData.append("image", imageFile); response = await fetch("/api/items", { method: "POST", body: formData, }); } else { // JSON-only request response = await fetch("/api/items", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); } if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.error || "Failed to create item"); } const result = await response.json(); toast.success("Item created", { description: `"${result.item.name}" has been created successfully.` }); return result.item; } catch (err) { const message = err instanceof Error ? err.message : "Unknown error"; toast.error("Failed to create item", { description: message }); return null; } finally { setLoading(false); } }, []); return { createItem, loading }; } export function useUpdateItem() { const [loading, setLoading] = useState(false); const updateItem = useCallback(async (id: number, data: UpdateItemData): Promise => { setLoading(true); try { const response = await fetch(`/api/items/${id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.error || "Failed to update item"); } const result = await response.json(); toast.success("Item updated", { description: `"${result.item.name}" has been updated successfully.` }); return result.item; } catch (err) { const message = err instanceof Error ? err.message : "Unknown error"; toast.error("Failed to update item", { description: message }); return null; } finally { setLoading(false); } }, []); return { updateItem, loading }; } export function useDeleteItem() { const [loading, setLoading] = useState(false); const deleteItem = useCallback(async (id: number, name?: string): Promise => { setLoading(true); try { const response = await fetch(`/api/items/${id}`, { method: "DELETE", }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.error || "Failed to delete item"); } toast.success("Item deleted", { description: name ? `"${name}" has been deleted.` : "Item has been deleted." }); return true; } catch (err) { const message = err instanceof Error ? err.message : "Unknown error"; toast.error("Failed to delete item", { description: message }); return false; } finally { setLoading(false); } }, []); return { deleteItem, loading }; } export function useUploadItemIcon() { const [loading, setLoading] = useState(false); const uploadIcon = useCallback(async (itemId: number, imageFile: File): Promise => { setLoading(true); try { const formData = new FormData(); formData.append("image", imageFile); const response = await fetch(`/api/items/${itemId}/icon`, { method: "POST", body: formData, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error(errorData.error || "Failed to upload icon"); } const result = await response.json(); toast.success("Icon uploaded", { description: "Item icon has been updated successfully." }); return result.item; } catch (err) { const message = err instanceof Error ? err.message : "Unknown error"; toast.error("Failed to upload icon", { description: message }); return null; } finally { setLoading(false); } }, []); return { uploadIcon, loading }; }