"use client"; import { useEffect, useMemo, useState, useCallback, useRef } from "react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Checkbox } from "@/components/ui/checkbox"; interface CategoryColumn { name: string; type: "TEXT" | "INTEGER" | "REAL" | "BOOLEAN"; notNull?: boolean; editable?: boolean; } interface CategoryTemplate { name: string; columns: CategoryColumn[]; imagePath: string; } const BASE_COLUMN_NAMES = ["id", "nome", "image_url", "image_local_path"]; function generateTempId() { return Math.random().toString(36).slice(2, 10); } export interface ItemFormPayload { id?: number; name: string; category: string; rarity?: string | null; imageUrl?: string | null; imageLocalPath?: string | null; level?: number | null; weight?: number | null; slots?: number | null; skill?: string | null; description?: string | null; servers: string[]; tags: string[]; metadata?: Record; fields?: Record; } interface ItemCreationModalProps { open: boolean; onOpenChange: (open: boolean) => void; mode: "create" | "edit"; item?: { item_id: number; item_name: string; category: string; rarity: string | null; image_url: string | null; image_local_path: string | null; level: number | null; weight: number | null; slots: number | null; skill: string | null; description?: string | null; servers: string[]; tags: string[]; metadata: Record; }; categories: string[]; serverOptions: string[]; onSubmit: (payload: ItemFormPayload) => Promise; submitting?: boolean; } type AnalysisResult = { table: string; columns: Record; } | null; function parseCsv(input: string): string[] { return input .split(",") .map((value) => value.trim()) .filter(Boolean); } function serializeCsv(list: string[]): string { if (!Array.isArray(list) || list.length === 0) return ""; return list.join(", "); } function parseNumberInput(value: string): number | null { if (!value) return null; const parsed = Number(value); return Number.isFinite(parsed) ? parsed : null; } export function ItemCreationModal({ open, onOpenChange, mode, item, categories, serverOptions, onSubmit, submitting, }: ItemCreationModalProps) { const isEdit = mode === "edit"; const [name, setName] = useState(""); const [category, setCategory] = useState(""); const [rarity, setRarity] = useState(""); const [imageUrl, setImageUrl] = useState(""); const [imageLocalPath, setImageLocalPath] = useState(""); const [description, setDescription] = useState(""); const [level, setLevel] = useState(""); const [weight, setWeight] = useState(""); const [slots, setSlots] = useState(""); const [skill, setSkill] = useState(""); const [serversCsv, setServersCsv] = useState(""); const [tagsCsv, setTagsCsv] = useState(""); const [analysis, setAnalysis] = useState(null); const [analysisLoading, setAnalysisLoading] = useState(false); const [analysisError, setAnalysisError] = useState(null); const [submitStatus, setSubmitStatus] = useState(null); const allowAiTools = !isEdit; const [categoryTemplates, setCategoryTemplates] = useState([]); const [categoryLoading, setCategoryLoading] = useState(false); const [categoryError, setCategoryError] = useState(null); const [dynamicValues, setDynamicValues] = useState>({}); const [showNewCategoryModal, setShowNewCategoryModal] = useState(false); const [showNewFieldModal, setShowNewFieldModal] = useState(false); const [newCategoryName, setNewCategoryName] = useState(""); const [newCategoryFields, setNewCategoryFields] = useState>([]); const [newCategoryError, setNewCategoryError] = useState(null); const [newFieldName, setNewFieldName] = useState(""); const [newFieldType, setNewFieldType] = useState("TEXT"); const [newFieldNotNull, setNewFieldNotNull] = useState(false); const [newFieldError, setNewFieldError] = useState(null); const fetchCategories = useCallback(async () => { setCategoryLoading(true); setCategoryError(null); try { const response = await fetch("/api/admin/categories", { credentials: "include" }); if (!response.ok) { throw new Error("Falha ao carregar categorias"); } const data = await response.json(); setCategoryTemplates(Array.isArray(data?.categories) ? data.categories : []); } catch (error) { console.error(error); setCategoryError(error instanceof Error ? error.message : "Erro ao carregar categorias"); } finally { setCategoryLoading(false); } }, []); const dynamicInitializedRef = useRef(false); const lastCategoryRef = useRef(null); useEffect(() => { if (!open) return; fetchCategories(); }, [open, fetchCategories]); useEffect(() => { if (!open) { setSubmitStatus(null); setAnalysis(null); setAnalysisError(null); dynamicInitializedRef.current = false; lastCategoryRef.current = null; return; } if (isEdit && item) { setName(item.item_name); setCategory(item.category.toLowerCase()); setRarity(item.rarity ?? ""); setImageUrl(item.image_url ?? ""); setImageLocalPath(item.image_local_path ?? ""); setDescription(item.description ?? ""); setLevel(item.level !== null && item.level !== undefined ? String(item.level) : ""); setWeight(item.weight !== null && item.weight !== undefined ? String(item.weight) : ""); setSlots(item.slots !== null && item.slots !== undefined ? String(item.slots) : ""); setSkill(item.skill ?? ""); setServersCsv(serializeCsv(item.servers)); setTagsCsv(serializeCsv(item.tags)); setSubmitStatus(null); } else { const defaultCategory = categoryTemplates[0]?.name ?? categories[0] ?? ""; setName(""); setCategory(defaultCategory.toLowerCase()); setRarity(""); setImageUrl(""); setImageLocalPath(""); setDescription(""); setLevel(""); setWeight(""); setSlots(""); setSkill(""); setServersCsv(""); setTagsCsv(""); setSubmitStatus(null); } }, [open, isEdit, item, categories, categoryTemplates]); const getTemplateForCategory = useCallback( (categoryName: string | null | undefined) => categoryTemplates.find((template) => template.name === (categoryName ?? "").toLowerCase()), [categoryTemplates] ); const buildDynamicInitialValues = useCallback( (template: CategoryTemplate | undefined, metadata?: Record) => { if (!template) return {} as Record; const values: Record = {}; template.columns .filter((column) => !BASE_COLUMN_NAMES.includes(column.name) && column.editable !== false) .forEach((column) => { const sourceValue = metadata ? metadata[column.name] : undefined; if (sourceValue === undefined || sourceValue === null) { values[column.name] = column.type === "BOOLEAN" ? false : ""; return; } if (column.type === "BOOLEAN") { values[column.name] = Boolean(Number(sourceValue)); } else if (column.type === "INTEGER" || column.type === "REAL") { values[column.name] = String(sourceValue); } else { values[column.name] = String(sourceValue); } }); return values; }, [] ); const handleAnalyze = async () => { if (!allowAiTools) return; if (!name.trim() || !description.trim()) { setAnalysisError("Informe nome e descrição para gerar o JSON."); return; } setAnalysisError(null); setAnalysis(null); setAnalysisLoading(true); try { const res = await fetch("/api/admin/items/analyze", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: name.trim(), description: description.trim(), scope: serverOptions.length > 0 ? "server" : "global", server: serverOptions[0] ?? null, }), }); if (!res.ok) { throw new Error("Falha ao comunicar com o serviço de IA."); } const data = await res.json(); setAnalysis(data.structure ?? null); } catch (error) { console.error(error); setAnalysisError( error instanceof Error ? error.message : "Erro desconhecido ao gerar JSON." ); } finally { setAnalysisLoading(false); } }; const handleSubmit = async () => { if (!name.trim()) { setSubmitStatus("Informe o nome do item."); return; } if (!category.trim()) { setSubmitStatus("Informe a categoria do item."); return; } setSubmitStatus(null); try { const metadataPayload: Record = {}; if (allowAiTools && analysis) { metadataPayload.aiStructure = analysis; } for (const [key, value] of Object.entries(dynamicValues)) { metadataPayload[key] = value; } await onSubmit({ id: item?.item_id, name: name.trim(), category: category.trim().toLowerCase(), rarity: rarity.trim() || null, imageUrl: imageUrl.trim() || null, imageLocalPath: imageLocalPath.trim() || null, description: description.trim() || null, level: parseNumberInput(level), weight: parseNumberInput(weight), slots: parseNumberInput(slots), skill: skill.trim() || null, servers: parseCsv(serversCsv), tags: parseCsv(tagsCsv), metadata: metadataPayload, fields: dynamicValues, }); setSubmitStatus(isEdit ? "Alterações salvas com sucesso." : "Item criado com sucesso."); if (!isEdit) { setName(""); setCategory(categories[0] ?? ""); setRarity(""); setImageUrl(""); setImageLocalPath(""); setDescription(""); setLevel(""); setWeight(""); setSlots(""); setSkill(""); setServersCsv(""); setTagsCsv(""); setAnalysis(null); } } catch (error) { console.error(error); setSubmitStatus( error instanceof Error ? error.message : "Erro ao salvar. Tente novamente." ); } }; const handleDynamicValueChange = useCallback((column: CategoryColumn, value: string | boolean | null) => { setDynamicValues((prev) => ({ ...prev, [column.name]: value, })); }, []); const handleCategoryChange = useCallback((value: string) => { dynamicInitializedRef.current = false; lastCategoryRef.current = null; setCategory(value.toLowerCase()); }, []); const handleAddCategoryField = useCallback(() => { setNewCategoryFields((prev) => [ ...prev, { id: generateTempId(), name: "", type: "TEXT", notNull: false }, ]); }, []); const handleUpdateCategoryField = useCallback((id: string, key: "name" | "type" | "notNull", value: string | boolean) => { setNewCategoryFields((prev) => prev.map((field) => field.id === id ? { ...field, [key]: key === "notNull" ? Boolean(value) : value, } : field ) ); }, []); const handleRemoveCategoryField = useCallback((id: string) => { setNewCategoryFields((prev) => prev.filter((field) => field.id !== id)); }, []); const handleCreateCategory = useCallback(async () => { if (!newCategoryName.trim()) { setNewCategoryError("Informe o nome da categoria"); return; } try { setNewCategoryError(null); const payload = { name: newCategoryName.trim().toLowerCase(), columns: newCategoryFields .filter((field) => field.name.trim()) .map((field) => ({ name: field.name.trim().toLowerCase(), type: field.type, notNull: field.notNull, })), }; const res = await fetch("/api/admin/categories", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(payload), }); if (!res.ok) { const error = await res.json().catch(() => null); throw new Error(error?.error || "Falha ao criar categoria"); } await fetchCategories(); handleCategoryChange(payload.name); setShowNewCategoryModal(false); setNewCategoryFields([]); } catch (error) { console.error(error); setNewCategoryError(error instanceof Error ? error.message : "Erro ao criar categoria"); } }, [newCategoryName, newCategoryFields, fetchCategories, handleCategoryChange]); const handleAddFieldToCategory = useCallback(async () => { if (!category) { setNewFieldError("Selecione uma categoria"); return; } if (!newFieldName.trim()) { setNewFieldError("Informe o nome do campo"); return; } try { setNewFieldError(null); const payload = { name: newFieldName.trim().toLowerCase(), type: newFieldType, notNull: newFieldNotNull, }; const res = await fetch(`/api/admin/categories/${category}/fields`, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(payload), }); if (!res.ok) { const error = await res.json().catch(() => null); throw new Error(error?.error || "Falha ao adicionar campo"); } await fetchCategories(); dynamicInitializedRef.current = false; lastCategoryRef.current = null; setShowNewFieldModal(false); } catch (error) { console.error(error); setNewFieldError(error instanceof Error ? error.message : "Erro ao adicionar campo"); } }, [category, newFieldName, newFieldType, newFieldNotNull, fetchCategories]); const serverSuggestions = useMemo(() => { if (serverOptions.length === 0) return ""; return serverOptions.join(", "); }, [serverOptions]); const activeTemplate = useMemo(() => getTemplateForCategory(category), [category, getTemplateForCategory]); const dynamicColumns = useMemo( () => activeTemplate?.columns.filter( (column) => !BASE_COLUMN_NAMES.includes(column.name) && column.editable !== false, ) ?? [], [activeTemplate], ); useEffect(() => { if (!open) return; const effectiveCategory = (category || item?.category || categoryTemplates[0]?.name || "").toLowerCase(); if (!effectiveCategory) return; const template = getTemplateForCategory(effectiveCategory); if (!template) return; if (!category) { setCategory(template.name); } const metadataSource = isEdit ? (item?.metadata as Record | undefined) : undefined; if (!dynamicInitializedRef.current || lastCategoryRef.current !== template.name) { setDynamicValues(buildDynamicInitialValues(template, metadataSource)); dynamicInitializedRef.current = true; lastCategoryRef.current = template.name; } }, [open, category, categoryTemplates, getTemplateForCategory, buildDynamicInitialValues, isEdit, item]); return ( <>
{isEdit ? "Editar item" : "Novo item"} Preencha os dados do item unificado. Utilize a IA para gerar um JSON de referência, se desejar.
setName(event.target.value)} />
{categoryError && (

{categoryError}

)} {isEdit && (

A categoria existente do item não pode ser alterada neste fluxo.

)}
setRarity(event.target.value)} />
setImageUrl(event.target.value)} />
setImageLocalPath(event.target.value)} />
setLevel(event.target.value)} />
setWeight(event.target.value)} />
setSlots(event.target.value)} />
setSkill(event.target.value)} />
{dynamicColumns.length > 0 && (

Defina valores adicionais conforme o modelo da categoria selecionada.

{dynamicColumns.map((column) => { const value = dynamicValues[column.name]; if (column.type === "BOOLEAN") { return (
handleDynamicValueChange(column, checked === true) } />
); } const inputType = column.type === "TEXT" ? "text" : "number"; const step = column.type === "REAL" ? "0.01" : "1"; return (
handleDynamicValueChange(column, event.target.value) } />
); })}
)}
setServersCsv(event.target.value)} /> {serverSuggestions && (

Sugestões cadastradas: {serverSuggestions}

)}
setTagsCsv(event.target.value)} />