"use client"; import { useTranslation } from "@/components/i18n/locale-provider"; import { searchItems } from "@/lib/search"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { PlusIcon, RefreshCwIcon, RotateCcwIcon, SearchIcon, XCircleIcon, Trash2Icon, } from "lucide-react"; import { useEffect, useState, useTransition } from "react"; import { toast } from "sonner"; import { deleteStructuredGuide, cancelCatalogSync, getCatalogSyncProgress, moveGuideToTrash, restoreStructuredGuide, startCatalogSync, } from "@/app/admin/actions"; import { DeleteConfirmationDialog } from "@/components/admin/delete-confirmation-dialog"; import { GuideVisibilityControl } from "@/components/admin/guide-visibility-control"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyTitle, } from "@/components/ui/empty"; import { InputGroup, InputGroupAddon, InputGroupInput, } from "@/components/ui/input-group"; import { Spinner } from "@/components/ui/spinner"; import { useAdminPresence } from "@/hooks/use-presence"; import type { CatalogSyncJob } from "@/lib/catalog/sync"; import { cn } from "@/lib/utils"; type GuideCard = { id: string; characterKey: string; name: string; overview: string; coverUrl: string; version: number; updatedAt: Date; trashedAt: Date | null; isPublic: boolean; }; export function StructuredDashboard({ guides, trash, catalogVersion, }: { guides: GuideCard[]; trash: GuideCard[]; catalogVersion: string | null; }) { const { t, locale } = useTranslation(); const router = useRouter(); const [pending, startTransition] = useTransition(); const [query, setQuery] = useState(""); const [syncJob, setSyncJob] = useState(null); const presence = useAdminPresence(null); const catalogReady = Boolean(catalogVersion); const syncJobId = syncJob?.id; const syncJobStatus = syncJob?.status; const search = searchItems(guides, query, (guide) => `${guide.name} ${guide.characterKey} ${guide.overview}`, (guide) => [guide.name]); const filteredGuides = search.items; useEffect(() => { if (!syncJobId || !syncJobStatus || ["completed", "unchanged", "cancelled", "error"].includes(syncJobStatus)) return; let stopped = false; const poll = async () => { const latest = await getCatalogSyncProgress(syncJobId); if (!stopped && latest) setSyncJob(latest); }; void poll(); const timer = window.setInterval(() => void poll(), 1000); return () => { stopped = true; window.clearInterval(timer); }; }, [syncJobId, syncJobStatus]); function startSync() { startTransition(async () => { const result = await startCatalogSync(); if (result.status === "error") toast.error(t(result.message ?? "กรุณาตรวจสอบข้อมูลให้ถูกต้อง")); else setSyncJob(result.job); }); } function cancelSync() { if (!syncJob) return; startTransition(async () => { const result = await cancelCatalogSync(syncJob.id); if (result) setSyncJob(result); }); } function run( action: () => Promise<{ status: string; message?: string }>, success: string, ) { startTransition(async () => { const result = await action(); if (result.status === "error") toast.error(t(result.message ?? "กรุณาตรวจสอบข้อมูลให้ถูกต้อง")); else { toast.success(success); router.refresh(); } }); } return (
{t("Lunaris ที่ Sync:")}{" "}{" "} {catalogVersion ? `v${catalogVersion}` : t("ยังไม่ Sync")}
{syncJob && !["completed", "unchanged", "cancelled", "error"].includes(syncJob.status) ? ( ) : null}
{syncJob ? (
{t(syncJob.message)} {syncJob.total ? `${syncJob.completed}/${syncJob.total}` : t("กำลังเริ่ม…")}
{syncJob.status === "error" ?

{t(syncJob.error ?? "Sync ไม่สำเร็จ")}

: null} ) : null} {guides.length ? ( setQuery(event.target.value)} placeholder={t("ค้นหาตัวละคร…")} aria-label={t("ค้นหาตัวละคร")} /> ) : null} {filteredGuides.length ? (
{filteredGuides.map((guide) => ( {t("แก้ไข Guide")}{" "}{guide.name}
{`Guide
{guide.name} {guide.overview}
{`v${guide.version}`} {(presence?.editorsByGuide[guide.id] ?? []).map((editor) => ( {editor.name} {editor.sections.length ? ` · ${editor.sections.join(", ")}` : ""} ))}

{t("แก้ไขล่าสุด")}{" "}{" "} {new Date(guide.updatedAt).toLocaleString(locale === "th" ? "th-TH" : "en-GB")}

} title={t("ย้าย {name} ไปถังขยะ?", { name: guide.name })} description={t("Guide จะหายจากหน้าเว็บไซต์ทันที และสามารถกู้คืนได้ภายหลัง")} confirmLabel={t("ย้ายไปถังขยะ")} onConfirm={() => run(() => moveGuideToTrash(guide.id), t("ลบสำเร็จ!")) } > {t("ย้าย")}{" "}{guide.name} {t("ไปถังขยะ")}
))}
) : guides.length ? ( {t("ไม่พบตัวละคร")} {t("ลองค้นหาด้วยชื่อตัวละครอื่น")} ) : ( {t("ยังไม่มี Guide")} {t("Sync Catalog แล้วเลือกตัวละครเพื่อสร้าง Guide แรก")} )} {trash.length ? (

{t("ถังขยะ")}

{t("Guide ที่ซ่อนไว้จากเว็บไซต์")}

{trash.map((guide) => ( {guide.name} {guide.overview}
} title={t("ลบ {name} ถาวร?", { name: guide.name })} description={t("ข้อมูล Weapons, Artifacts, Constellations, Team และ Extra ทั้งหมดจะกู้คืนไม่ได้")} confirmLabel={t("ยืนยันลบถาวร")} onConfirm={() => run( () => deleteStructuredGuide(guide.id), t("ลบสำเร็จ!"), ) } > {t("ลบถาวร")}
))}
) : null}
); }