"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { RefreshCw, Plus, Pencil } from "lucide-react"; import Image from "next/image"; import { format } from "date-fns"; import { ptBR } from "date-fns/locale"; import { ItemCreationModal, type ItemFormPayload } from "@/components/admin/items/ItemCreationModal"; export interface AdminItemRecord { item_id: number; source_table_id: number; source_table_name: string; item_name: string; category: string; image_local_path: string | null; image_url: string | null; rarity: string | null; level: number | null; weight: number | null; slots: number | null; skill: string | null; metadata: Record; created_at: string | Date; updated_at: string | Date; servers: string[]; tags: string[]; description?: string | null; } export interface AdminItemsResponse { items: AdminItemRecord[]; total: number; page: number; pageSize: number; servers: string[]; categories: string[]; } interface AdminItemManagerProps { initialData: AdminItemsResponse; } export function AdminItemManager({ initialData }: AdminItemManagerProps) { const [data, setData] = useState(initialData); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [serverFilter, setServerFilter] = useState("all"); const [search, setSearch] = useState(""); const [categoryFilter, setCategoryFilter] = useState("all"); const [rarityFilter, setRarityFilter] = useState("all"); const [page, setPage] = useState(initialData.page ?? 1); const pageSize = initialData.pageSize ?? 50; const [modalOpen, setModalOpen] = useState(false); const [modalMode, setModalMode] = useState<"create" | "edit">("create"); const [selectedItem, setSelectedItem] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const searchDebounce = useRef(undefined); const fetchItems = useCallback(async () => { setLoading(true); setError(null); try { const params = new URLSearchParams(); if (search.trim()) params.set("q", search.trim()); if (serverFilter !== "all") params.set("server", serverFilter); if (categoryFilter !== "all") params.set("category", categoryFilter); if (rarityFilter !== "all") params.set("rarity", rarityFilter); params.set("page", String(page)); params.set("pageSize", String(pageSize)); const res = await fetch(`/api/admin/items?${params.toString()}`, { credentials: "include", }); if (!res.ok) { throw new Error("Não foi possível carregar os itens."); } const json = (await res.json()) as AdminItemsResponse; setData(json); } catch (err) { console.error(err); setError(err instanceof Error ? err.message : "Falha ao carregar itens."); } finally { setLoading(false); } }, [search, serverFilter, categoryFilter, rarityFilter, page, pageSize]); useEffect(() => { if (searchDebounce.current !== undefined) { window.clearTimeout(searchDebounce.current); } searchDebounce.current = window.setTimeout(() => { fetchItems(); }, 350); return () => { if (searchDebounce.current !== undefined) { window.clearTimeout(searchDebounce.current); searchDebounce.current = undefined; } }; }, [fetchItems]); const rarityOptions = useMemo(() => { const set = new Set(); data.items.forEach((item) => { if (item.rarity) { set.add(item.rarity); } }); return Array.from(set).sort((a, b) => a.localeCompare(b, "pt-BR", { sensitivity: "base" })); }, [data.items]); const openCreateModal = () => { setModalMode("create"); setSelectedItem(null); setModalOpen(true); }; const openEditModal = (item: AdminItemRecord) => { setModalMode("edit"); setSelectedItem(item); setModalOpen(true); }; const handleSubmit = async (payload: ItemFormPayload) => { setIsSubmitting(true); try { const method = modalMode === "edit" ? "PUT" : "POST"; const body: Record = { id: payload.id, name: payload.name, category: payload.category, rarity: payload.rarity, imageUrl: payload.imageUrl, imageLocalPath: payload.imageLocalPath, level: payload.level, weight: payload.weight, slots: payload.slots, skill: payload.skill, description: payload.description, servers: payload.servers, tags: payload.tags, metadata: payload.metadata, fields: payload.fields, }; const res = await fetch("/api/admin/items", { method, credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!res.ok) { const error = await res.json().catch(() => ({ error: "Erro desconhecido" })); throw new Error(error?.error || error?.message || "Falha ao salvar item"); } await fetchItems(); setModalOpen(false); } catch (err) { console.error(err); setError(err instanceof Error ? err.message : "Erro ao salvar item."); } finally { setIsSubmitting(false); } }; const itemsForDisplay = data.items; const serverTabs = ["all", ...data.servers]; return (
Todos os itens cadastrados Visualize e edite os registros unificados importados do database do Tibia Wiki. Itens específicos de servidores podem ser marcados através das tags.
setSearch(event.target.value)} />
{loading ? "Carregando..." : `${data.total} itens encontrados`}
{error && ( {error} )} {loading ? (
) : ( { setServerFilter(value); setPage(1); }} className="space-y-4" > {serverTabs.map((server) => ( {server === "all" ? "Todos" : server} ))} {serverTabs.map((server) => (
{itemsForDisplay.length === 0 ? ( ) : ( itemsForDisplay.map((item) => ( )) )}
Imagem Nome Categoria Raridade Origem Servidores Atualizado em Ações
Nenhum item encontrado com os filtros atuais.
{item.image_url ? (
{item.item_name}
) : ( Sem imagem )}
{item.item_name}
#{item.item_id}
{item.category} {item.rarity ? ( {item.rarity} ) : ( - )}
{item.source_table_name}
ID origem: {item.source_table_id}
{item.servers.length > 0 ? (
{item.servers.map((serverTag) => ( {serverTag} ))}
) : ( Global )}
{format(new Date(item.updated_at), "dd/MM/yyyy HH:mm", { locale: ptBR })}
Página {page} • Total de {Math.max(1, Math.ceil(data.total / pageSize))}
))}
)}
); }