"use client"; import { Folder, FolderTree, Home, Loader2, Plus, RefreshCw, Wand2, } from "lucide-react"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; import { organizeImportFurni } from "@/actions/catalog"; import { IconPicker } from "@/components/admin/catalog/icon-picker"; import { CatalogIcon, LAYOUT_COLORS } from "@/components/admin/catalog-tree"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { groupSuggestedCategories } from "@/features/catalog/domain/organize"; import { CATALOG_LAYOUTS, type CatalogLayout } from "@/lib/catalog-layouts"; import { getLocalIconUrl } from "@/lib/furni/classname"; import { cn } from "@/lib/utils"; interface OrganizeItem { id: number; itemId: number; itemName: string; publicName: string; type: string; spriteId: number; interactionType: string; catalogItemId: number | null; sourcePageId: number | null; sourcePageCaption: string | null; alreadyPlaced: boolean; } interface RootPage { id: number; caption: string; } interface OrganizeGroupOverride { caption?: string; icon?: number; layout?: CatalogLayout; } interface OrganizeImportsDialogProps { open: boolean; onOpenChange: (open: boolean) => void; } export function OrganizeImportsDialog({ open, onOpenChange, }: OrganizeImportsDialogProps) { const t = useTranslations("pages.admin.catalog.workspace.organizeImports"); const router = useRouter(); const [loading, setLoading] = useState(false); const [loadError, setLoadError] = useState(false); const [items, setItems] = useState([]); const [rootPages, setRootPages] = useState([]); const [importRootPageId, setImportRootPageId] = useState(null); const [importRootCaption, setImportRootCaption] = useState( null, ); const [parentId, setParentId] = useState(-1); const [approved, setApproved] = useState>({}); const [overrides, setOverrides] = useState< Record >({}); const [isCreating, setIsCreating] = useState(false); const [freeMode, setFreeMode] = useState(true); const [credits, setCredits] = useState(0); const [points, setPoints] = useState(0); const [pointsType, setPointsType] = useState(0); const load = useCallback(async () => { setLoading(true); setLoadError(false); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 45000); try { const [data, tree] = await Promise.all([ fetch("/api/admin/import/organize", { signal: controller.signal }).then( (r) => r.json(), ), fetch("/api/admin/catalog/tree?parentId=-1", { signal: controller.signal, }).then((r) => r.json()), ]); setItems(Array.isArray(data?.items) ? data.items : []); setImportRootPageId(data?.importRootPageId ?? null); setImportRootCaption(data?.importRootCaption ?? null); const pages = Array.isArray(tree?.pages) ? tree.pages : []; setRootPages( pages.map((p: { id: number; caption: string }) => ({ id: Number(p.id), caption: String(p.caption ?? p.id), })), ); } catch (error) { console.error("[OrganizeImports] load failed:", error); setLoadError(true); setItems([]); } finally { clearTimeout(timeout); setLoading(false); } }, []); useEffect(() => { if (!open) return; setItems([]); setApproved({}); setOverrides({}); setParentId(-1); setFreeMode(true); setCredits(0); setPoints(0); setPointsType(0); void load(); }, [open, load]); const groups = useMemo(() => groupSuggestedCategories(items), [items]); const allApproved = groups.length > 0 && groups.every((g) => approved[g.name]); const totalSelected = useMemo( () => groups.reduce( (sum, g) => sum + (approved[g.name] ? g.items.length : 0), 0, ), [groups, approved], ); const parentOptions = useMemo(() => { const options: RootPage[] = []; for (const p of rootPages) { if (importRootPageId != null && p.id === importRootPageId) continue; options.push(p); } return options; }, [rootPages, importRootPageId]); const toggleAll = () => { const next = !allApproved; setApproved(Object.fromEntries(groups.map((g) => [g.name, next]))); }; const updateGroup = (name: string, patch: OrganizeGroupOverride) => { setOverrides((prev) => ({ ...prev, [name]: { ...prev[name], ...patch } })); }; const handleCreate = async () => { if (isCreating) return; const payload = groups .filter((g) => approved[g.name]) .map((g) => { const o = overrides[g.name] ?? {}; return { caption: (o.caption ?? g.name).trim(), pageLayout: o.layout ?? g.layout?.layout ?? "default_3x3", iconImage: o.icon ?? g.icon, items: g.items.map((item) => ({ itemId: item.id, catalogItemId: item.catalogItemId, credits: freeMode ? 0 : credits, points: freeMode ? 0 : points, pointsType: freeMode ? 0 : pointsType, })), }; }); if (payload.length === 0 || payload.some((g) => !g.caption)) { toast.error("Every approved page needs a name"); return; } setIsCreating(true); try { const result = await organizeImportFurni({ parentId, groups: payload }); if (!result.ok) throw new Error(result.error ?? "Failed"); const created = result.data?.created ?? []; toast.success( `Organized ${created.length} import page${created.length === 1 ? "" : "s"}`, ); onOpenChange(false); router.refresh(); } catch (error) { console.error("[OrganizeImports] create failed:", error); toast.error("Failed to organize imports"); } finally { setIsCreating(false); } }; return ( {t("title")} {totalSelected > 0 && !loading && ( {t("itemsBadge", { count: totalSelected })} )}
{/* Price (new offers only) */} {!loading && items.length > 0 && (
{t("price")} {!freeMode && ( <> {t("pointsType")} )}

{t("newPriceHint")}

)} {/* Parent */} {!loading && items.length > 0 && (
{t("parent")}
)} {/* Loading / empty / groups */} {loading ? (
{t("loading")}
) : items.length === 0 ? (
{loadError ? t("loadError") : t("empty")}
) : ( <>

{t("desc")}

{groups.map((group) => { const o = overrides[group.name] ?? {}; const caption = o.caption ?? group.name; const icon = o.icon ?? group.icon; const layout = o.layout ?? group.layout?.layout ?? "default_3x3"; const isApproved = approved[group.name] !== false; const moved = group.items.filter((i) => i.alreadyPlaced).length; const added = group.items.length - moved; return ( ); })} )}
); } function FurniThumb({ item, size = 24, }: { item: { itemName: string }; size?: number; }) { const primary = getLocalIconUrl(item.itemName); return (
{/* eslint-disable-next-line @next/next/no-img-element */} {item.itemName} { const img = e.target as HTMLImageElement; img.style.opacity = "0"; }} loading="lazy" />
); }