"use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Checkbox } from "@/components/ui/checkbox"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; 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"]; const categoriesCache: { data: CategoryTemplate[] | null; pending: Promise | null; } = { data: null, pending: null, }; 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; server?: string | null; } interface ItemReference { 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; } 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; } interface HeaderSlotProps { title: string; description: string; } interface FooterSlotProps { isEdit: boolean; submitting?: boolean; submitLabel: string; onCancel?: () => void; onSubmitClick: () => void; } type PrefillPayload = Partial<{ name: string; category: string; rarity: string | null; imageUrl: string | null; imageLocalPath: string | null; level: number | string | null; weight: number | string | null; slots: number | string | null; skill: string | null; servers: string[] | string; tags: string[] | string; description: string | null; metadata: Record; }>; export interface AdminItemFormProps { mode: "create" | "edit"; item?: ItemReference; categories: string[]; serverOptions: string[]; submitting?: boolean; onSubmit: (payload: ItemFormPayload) => Promise; onCancel?: () => void; onSuccess?: () => void; isActive?: boolean; allowAiTools?: boolean; prefillData?: PrefillPayload | null; className?: string; contentClassName?: string; headerSlot?: (props: HeaderSlotProps) => React.ReactNode; footerSlot?: (props: FooterSlotProps) => React.ReactNode; } export function AdminItemForm({ mode, item, categories, serverOptions, submitting, onSubmit, onCancel, onSuccess, isActive = true, allowAiTools: allowAiToolsProp, prefillData, className, contentClassName, headerSlot, footerSlot, }: AdminItemFormProps) { const isEdit = mode === "edit"; const allowAiTools = allowAiToolsProp ?? !isEdit; 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 [originServer, setOriginServer] = useState(""); const [analysis, setAnalysis] = useState(null); const [analysisLoading, setAnalysisLoading] = useState(false); const [analysisError, setAnalysisError] = useState(null); const [tokenUsage, setTokenUsage] = useState<{ promptTokens: number; outputTokens: number; totalTokens: number; costUsd: number; } | null>(null); const [accumulatedCostUsd, setAccumulatedCostUsd] = useState(() => { if (typeof window === "undefined") return 0; const stored = window.localStorage.getItem("aiCostAccumulatedUsd"); return stored ? Number(stored) || 0 : 0; }); const [submitStatus, setSubmitStatus] = useState(null); const [categoryTemplates, setCategoryTemplates] = useState< CategoryTemplate[] >([]); const [categoryLoading, setCategoryLoading] = useState(false); const [categoryError, setCategoryError] = useState(null); const [dynamicValues, setDynamicValues] = useState< Record >({}); const [prefilledMetadata, setPrefilledMetadata] = useState< Record >({}); const [showNewCategoryModal, setShowNewCategoryModal] = useState(false); const [showNewFieldModal, setShowNewFieldModal] = useState(false); const [newCategoryName, setNewCategoryName] = useState(""); const [newCategoryFields, setNewCategoryFields] = useState< Array<{ id: string; name: string; type: CategoryColumn["type"]; notNull: boolean; }> >([]); 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 dynamicInitializedRef = useRef(false); const lastCategoryRef = useRef(null); const [pendingPrefill, setPendingPrefill] = useState( null, ); const [unsavedFields, setUnsavedFields] = useState>(new Set()); const [_showPrefillModal, setShowPrefillModal] = useState(false); type DiffEntry = { field: string; current: string | null; next: string | null; }; const [_prefillDiff, setPrefillDiff] = useState<{ changed: DiffEntry[]; added: DiffEntry[]; unchanged: DiffEntry[]; }>({ changed: [], added: [], unchanged: [], }); const [showExitConfirm, setShowExitConfirm] = useState(false); const [aiSuggestedName, setAiSuggestedName] = useState(false); const [confirmNome, setConfirmNome] = useState(false); const markUnsaved = useCallback((key: string) => { setUnsavedFields((prev) => { const next = new Set(prev); next.add(key); return next; }); }, []); const isUnsaved = useCallback( (key: string) => unsavedFields.has(key), [unsavedFields], ); const resetFormState = useCallback( (templateList: CategoryTemplate[]) => { 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)); } else { const defaultCategory = templateList[0]?.name ?? categories[0] ?? ""; setName(""); setCategory(defaultCategory.toLowerCase()); setRarity(""); setImageUrl(""); setImageLocalPath(""); setDescription(""); setLevel(""); setWeight(""); setSlots(""); setSkill(""); setServersCsv(""); setTagsCsv(""); } setSubmitStatus(null); setAnalysis(null); setAnalysisError(null); setCategoryError(null); setNewCategoryError(null); setNewFieldError(null); setNewCategoryFields([]); dynamicInitializedRef.current = false; lastCategoryRef.current = null; setPrefilledMetadata({}); }, [isEdit, item, categories], ); const fetchCategories = useCallback(async () => { setCategoryError(null); if (categoriesCache.data) { setCategoryTemplates(categoriesCache.data); resetFormState(categoriesCache.data); return; } if (categoriesCache.pending) { setCategoryLoading(true); try { const cached = await categoriesCache.pending; if (cached) { setCategoryTemplates(cached); resetFormState(cached); } } catch (error) { console.error(error); setCategoryError( error instanceof Error ? error.message : "Erro ao carregar categorias", ); } finally { setCategoryLoading(false); } return; } setCategoryLoading(true); const pendingPromise = (async () => { 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(); const templates = Array.isArray(data?.categories) ? data.categories : []; categoriesCache.data = templates; setCategoryTemplates(templates); resetFormState(templates); return templates; } catch (error) { console.error(error); setCategoryError( error instanceof Error ? error.message : "Erro ao carregar categorias", ); throw error; } finally { categoriesCache.pending = null; setCategoryLoading(false); } })(); categoriesCache.pending = pendingPromise; await pendingPromise; }, [resetFormState]); useEffect(() => { if (!isActive) { resetFormState(categoryTemplates); return; } fetchCategories(); }, [isActive, fetchCategories, categoryTemplates, resetFormState]); useEffect(() => { if (!isActive) { resetFormState(categoryTemplates); return; } if (categoryTemplates.length === 0) { resetFormState(categoryTemplates); } }, [isActive, categoryTemplates, resetFormState]); 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; }, [], ); useEffect(() => { if (!isActive) 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; } }, [ isActive, category, item, categoryTemplates, getTemplateForCategory, buildDynamicInitialValues, isEdit, ]); 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: originServer || (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); if (data?.usage && typeof data?.costUsd === "number") { setTokenUsage({ promptTokens: Number(data.usage.promptTokens || 0), outputTokens: Number(data.usage.outputTokens || 0), totalTokens: Number(data.usage.totalTokens || 0), costUsd: Number(data.costUsd || 0), }); setAccumulatedCostUsd((prev) => { const next = Number(prev) + Number(data.costUsd || 0); try { if (typeof window !== "undefined") { window.localStorage.setItem("aiCostAccumulatedUsd", String(next)); } } catch {} return next; }); } } catch (error) { console.error(error); setAnalysisError( error instanceof Error ? error.message : "Erro desconhecido ao gerar JSON.", ); } finally { setAnalysisLoading(false); } }; 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()); markUnsaved("category"); }, [markUnsaved], ); 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"); } categoriesCache.data = null; 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"); } categoriesCache.data = null; 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], ); const submitLabel = isEdit ? "Salvar alterações" : "Criar item"; const normalizeValue = (value: unknown): string | null => { if (value === null || value === undefined) return null; if (Array.isArray(value)) return value.join(", "); return String(value); }; const getCurrentSnapshot = useCallback(() => { return { name, category, rarity, imageUrl, imageLocalPath, level, weight, slots, skill, servers: parseCsv(serversCsv), tags: parseCsv(tagsCsv), description, metadata: { ...prefilledMetadata, ...dynamicValues }, server: originServer || null, }; }, [ name, category, rarity, imageUrl, imageLocalPath, level, weight, slots, skill, serversCsv, tagsCsv, description, prefilledMetadata, dynamicValues, originServer, ]); const computePrefillDiff = useCallback( (prefill: PrefillPayload) => { const current = getCurrentSnapshot(); const changed: DiffEntry[] = []; const added: DiffEntry[] = []; const unchanged: DiffEntry[] = []; const topKeys: Array = [ "name", "category", "rarity", "imageUrl", "imageLocalPath", "level", "weight", "slots", "skill", "servers", "tags", "description", ]; for (const key of topKeys) { if (prefill[key] === undefined) continue; const next = normalizeValue( key === "servers" || key === "tags" ? Array.isArray(prefill[key]) ? (prefill[key] as string[]) : String(prefill[key] ?? "") .split(",") .map((v) => v.trim()) .filter(Boolean) : prefill[key], ); const currentVal = normalizeValue((current as any)[key]); const field = key === "servers" ? "servers" : key === "tags" ? "tags" : (key as string); if (currentVal !== next) { if (currentVal === null || currentVal === "") { added.push({ field, current: currentVal, next }); } else { changed.push({ field, current: currentVal, next }); } } else { unchanged.push({ field, current: currentVal, next }); } } const meta = prefill.metadata && typeof prefill.metadata === "object" ? prefill.metadata : {}; for (const [k, v] of Object.entries(meta as Record)) { if (k === "category") continue; const currentVal = normalizeValue(current.metadata[k]); const next = normalizeValue(v); const field = `meta.${k}`; if (currentVal !== next) { if (currentVal === null || currentVal === "") { added.push({ field, current: currentVal, next }); } else { changed.push({ field, current: currentVal, next }); } } else { unchanged.push({ field, current: currentVal, next }); } } return { changed, added, unchanged }; }, [getCurrentSnapshot, normalizeValue], ); const markUnsavedFromDiff = useCallback( (diff: { changed: DiffEntry[]; added: DiffEntry[] }) => { const next = new Set(); for (const d of [...diff.changed, ...diff.added]) { next.add(d.field); } setUnsavedFields(next); }, [], ); const _renderUnsavedTag = (field: string) => unsavedFields.has(field) ? ( Não salvo ) : null; const handlePrimarySubmit = useCallback(async () => { if (!name.trim()) { setSubmitStatus("Informe o nome do item."); return; } if (aiSuggestedName && !confirmNome) { setSubmitStatus("Confirme o nome sugerido pela IA."); return; } if (!category.trim()) { setSubmitStatus("Informe a categoria do item."); return; } if (!originServer || !originServer.trim()) { setSubmitStatus("Selecione o servidor de origem."); return; } setSubmitStatus(null); try { const metadataPayload: Record = { ...prefilledMetadata }; if (aiSuggestedName) { metadataPayload.confirmar_nome = confirmNome; } 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, server: originServer, }); setSubmitStatus( isEdit ? "Alterações salvas com sucesso." : "Item criado com sucesso.", ); onSuccess?.(); if (!isEdit) { resetFormState(categoryTemplates); } } catch (error) { console.error(error); setSubmitStatus( error instanceof Error ? error.message : "Erro ao salvar. Tente novamente.", ); } }, [ name, category, rarity, imageUrl, imageLocalPath, description, level, weight, slots, skill, serversCsv, tagsCsv, dynamicValues, allowAiTools, analysis, onSubmit, item, isEdit, onSuccess, categoryTemplates, resetFormState, prefilledMetadata, aiSuggestedName, confirmNome, originServer, ]); const title = isEdit ? "Editar item" : "Novo item"; const descriptionText = allowAiTools ? "Preencha os dados do item unificado. Utilize a IA para gerar um JSON de referência, se desejar." : "Preencha os dados do item unificado. Ajuste as sugestões geradas pela IA acima para concluir o cadastro."; const handleFormSubmit = (event: React.FormEvent) => { event.preventDefault(); void handlePrimarySubmit(); }; const handleCancelClick = useCallback(() => { if (unsavedFields.size > 0) { setShowExitConfirm(true); return; } onCancel?.(); }, [unsavedFields, onCancel]); const formClassName = className ?? "flex h-full flex-col"; const bodyClassName = contentClassName ?? "flex-1 min-h-0 overflow-y-auto px-6 py-4"; const [prefillLoading, setPrefillLoading] = useState(false); const [prefillAnimating, setPrefillAnimating] = useState(false); const [prefillStatus, setPrefillStatus] = useState(null); const formattedAnalysis = useMemo(() => { if (!analysis) return null; try { const cloned: any = JSON.parse(JSON.stringify(analysis)); const cols = cloned?.columns; if (cols && typeof cols.description === "string") { const s = cols.description as string; if (s.length > 140) { // Insere quebras de linha após vírgulas para melhorar legibilidade cols.description = s.replace(/,\s+/g, ",\n"); } } return cloned; } catch { return analysis; } }, [analysis]); const applyPrefill = useCallback( (prefill: PrefillPayload) => { if (prefill.name !== undefined) setName(prefill.name ?? ""); if (prefill.description !== undefined) setDescription(prefill.description ?? ""); if (prefill.rarity !== undefined) setRarity(prefill.rarity ?? ""); if (prefill.imageUrl !== undefined) setImageUrl(prefill.imageUrl ?? ""); if (prefill.imageLocalPath !== undefined) setImageLocalPath(prefill.imageLocalPath ?? ""); if (prefill.level !== undefined) setLevel(prefill.level ? String(prefill.level) : ""); if (prefill.weight !== undefined) setWeight(prefill.weight ? String(prefill.weight) : ""); if (prefill.slots !== undefined) setSlots(prefill.slots ? String(prefill.slots) : ""); if (prefill.skill !== undefined) setSkill(prefill.skill ?? ""); if (prefill.servers !== undefined) { const serversValue = Array.isArray(prefill.servers) ? prefill.servers.join(", ") : (prefill.servers ?? ""); setServersCsv(serversValue); } if (prefill.tags !== undefined) { const tagsValue = Array.isArray(prefill.tags) ? prefill.tags.join(", ") : (prefill.tags ?? ""); setTagsCsv(tagsValue); } let targetCategory = category; const metadataCategory = typeof prefill.metadata?.category === "string" ? prefill.metadata.category.toLowerCase() : null; if (prefill.category) { targetCategory = prefill.category.toLowerCase(); setCategory(targetCategory); dynamicInitializedRef.current = false; lastCategoryRef.current = null; } else if (metadataCategory) { targetCategory = metadataCategory; setCategory(targetCategory); dynamicInitializedRef.current = false; lastCategoryRef.current = null; } if (prefill.metadata && typeof prefill.metadata === "object") { setPrefilledMetadata(prefill.metadata); const template = getTemplateForCategory(targetCategory); if (template) { setDynamicValues( buildDynamicInitialValues(template, prefill.metadata), ); } const origem = typeof prefill.metadata?.origem_dados === "string" ? (prefill.metadata as any).origem_dados : null; const hasAutoName = prefill.name !== undefined && String(prefill.name ?? "").trim().length > 0; setAiSuggestedName(Boolean(hasAutoName && origem === "automatico")); setConfirmNome(false); } }, [buildDynamicInitialValues, category, getTemplateForCategory], ); useEffect(() => { if (prefillData) { setPendingPrefill(prefillData); } }, [prefillData]); useEffect(() => { if (!pendingPrefill) return; if (categoryTemplates.length === 0) return; const diff = computePrefillDiff(pendingPrefill); setPrefillDiff(diff); setShowPrefillModal(true); }, [pendingPrefill, categoryTemplates, computePrefillDiff]); const _handleConfirmPrefill = useCallback(() => { if (!pendingPrefill) return; applyPrefill(pendingPrefill); const diff = computePrefillDiff(pendingPrefill); markUnsavedFromDiff(diff); setPendingPrefill(null); setShowPrefillModal(false); }, [pendingPrefill, applyPrefill, computePrefillDiff, markUnsavedFromDiff]); const hasUnsaved = unsavedFields.size > 0; useEffect(() => { const handler = (e: BeforeUnloadEvent) => { if (hasUnsaved) { e.preventDefault(); e.returnValue = ""; } }; window.addEventListener("beforeunload", handler); return () => window.removeEventListener("beforeunload", handler); }, [hasUnsaved]); return ( <>
{headerSlot?.({ title, description: descriptionText }) ?? (

{title}

{descriptionText}

)}
{ setName(event.target.value); setAiSuggestedName(false); setConfirmNome(false); markUnsaved("name"); }} /> {aiSuggestedName && (
{ setConfirmNome(Boolean(checked)); markUnsaved("confirmar_nome"); }} />
)}
{categoryError && (

{categoryError}

)} {isEdit && (

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

)}
{ setRarity(event.target.value); markUnsaved("rarity"); }} />
{ setImageUrl(event.target.value); markUnsaved("imageUrl"); }} />
{ setImageLocalPath(event.target.value); markUnsaved("imageLocalPath"); }} />
{ setLevel(event.target.value); markUnsaved("level"); }} />
{ setWeight(event.target.value); markUnsaved("weight"); }} />
{ setSlots(event.target.value); markUnsaved("slots"); }} />
{ setSkill(event.target.value); markUnsaved("skill"); }} />
{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, ); markUnsaved(`fields.${column.name}`); }} />
); })}
)}
{!originServer && (

Campo obrigatório. Selecione o servidor de origem.

)}
{ setServersCsv(event.target.value); markUnsaved("servers"); }} /> {serverSuggestions && (

Sugestões cadastradas: {serverSuggestions}

)}
{ setTagsCsv(event.target.value); markUnsaved("tags"); }} />