"use client"; import { useEffect, useState } from "react"; import Link from "next/link"; import Image from "@/components/ui/resilient-image"; import { RefreshCwIcon } from "lucide-react"; import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"; import { FuzzyCombobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList } from "@/components/ui/combobox"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Empty, EmptyHeader, EmptyTitle, EmptyDescription } from "@/components/ui/empty"; import { Table, TableBody, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import type { CommentItem, CommentPage } from "@/lib/comments/types"; import { characterRarityGradientClass } from "@/lib/catalog/rarity"; import { cn } from "@/lib/utils"; import { useMarkCommentsRead } from "./admin-unread-comments"; import { CommentCard } from "./comment-card"; import { commentRequest } from "./client"; import { AdminCommentRowsSkeleton } from "./comment-skeleton"; import { useCommentEvents } from "./use-comment-events"; import { useCommentFeed } from "./use-comment-feed"; type TargetOption = { value: string; label: string; image: string | null; rarity: number | null; characterKey: string | null }; export function AdminCommentInbox({ initial, targets, initialTarget, initialComment }: { initial: CommentPage; targets: TargetOption[]; initialTarget?: string; initialComment?: string }) { const [target, setTarget] = useState(targets.find((item) => item.value === initialTarget) ?? null); const [status, setStatus] = useState("all"); const [answered, setAnswered] = useState("all"); const [sort, setSort] = useState("newest"); const [selectedId, setSelectedId] = useState(initialTarget ? initialComment : undefined); const [selectedResult, setSelectedResult] = useState<{ item: CommentItem | null; error: string }>({ item: null, error: "" }); const query = new URLSearchParams({ view: "inbox", grouped: "true", status, sort, unanswered: String(answered === "unanswered") }); if (target) query.set("target", target.value); const feed = useCommentFeed(`/api/admin/comments?${query}`, initial); const loadedSelection = feed.page.items.find((item) => item.id === selectedId && item.target === initialTarget && !item.rootId); const selectionLoaded = Boolean(loadedSelection); useEffect(() => { if (!selectedId || !initialTarget || selectionLoaded) return; const controller = new AbortController(); void Promise.resolve().then(async () => { if (controller.signal.aborted) return; setSelectedResult((current) => ({ ...current, error: "" })); try { const result = await commentRequest<{ item: CommentItem }>(`/api/comments/${selectedId}`, { signal: controller.signal }); if (!result.item || result.item.id !== selectedId || result.item.target !== initialTarget || result.item.rootId) throw new Error("ไม่พบการสนทนาที่เลือก"); if (!controller.signal.aborted) setSelectedResult({ item: result.item, error: "" }); } catch (cause) { if (!controller.signal.aborted) setSelectedResult({ item: null, error: cause instanceof Error ? cause.message : "ไม่สามารถโหลดการสนทนาได้" }); } }); return () => controller.abort(); }, [selectedId, initialTarget, selectionLoaded, feed.revision]); const selectedItem = selectedId ? loadedSelection ?? selectedResult.item : null; const selectedError = selectedId && !selectionLoaded ? selectedResult.error : ""; const selectedBusy = Boolean(selectedId && !selectedItem && !selectedError); const items = selectedItem ? [selectedItem, ...feed.page.items.filter((item) => item.id !== selectedId)] : feed.page.items; useCommentEvents({ admin: true }, () => feed.refresh({ silent: true, forceRevision: true })); const markRead = useMarkCommentsRead(); useEffect(() => { void markRead(target?.value ?? "all"); }, [markRead, target, feed.revision]); return
{"ไกด์หรือรอบ Stygian"} { if (value?.value !== target?.value) setSelectedId(undefined); setTarget(value); }} itemToStringLabel={(item) => item.label} itemToStringValue={(item) => item.value} searchNames={(item) => [item.label]} autoHighlight> {"ไม่พบไกด์ที่ตรงกัน"}{(item: TargetOption) => {item.label.slice(0, 2)}{item.label}} {"การแสดงผล"} { if (values[0] && values[0] !== status) { setSelectedId(undefined); setStatus(values[0]); } }} variant="outline" aria-label={"กรองการแสดงผลความคิดเห็น"}> {"ทั้งหมด"}{"แสดงอยู่"}{"ซ่อนอยู่"} {"การตอบกลับจากผู้ดูแล"} { if (values[0] && values[0] !== answered) { setSelectedId(undefined); setAnswered(values[0]); } }} variant="outline" aria-label={"กรองการตอบกลับจากผู้ดูแล"}> {"ทุกความคิดเห็น"}{"ยังไม่ได้ตอบ"} {"เรียงตาม"} { if (values[0] && values[0] !== sort) { setSelectedId(undefined); setSort(values[0]); } }} variant="outline" aria-label={"เรียงความคิดเห็น"}> {"ล่าสุด"}{"เก่าสุด"} {feed.error && {feed.error}} {selectedError && {selectedError}} {selectedBusy && } {items.length > 0 &&
{"ความคิดเห็น"} {"เนื้อหา"} {items.map((item) => feed.refresh({ silent: true, forceRevision: true })} revision={feed.revision} overview={item.target.startsWith("stygian:") ? {item.targetName} : item.targetImage && } />)}
} {!items.length && !feed.busy && !feed.error && !selectedBusy && !selectedError && {"ไม่พบความคิดเห็นที่ตรงกัน"}{"ความคิดเห็นจากทุกไกด์และทุกรอบ Stygian จะแสดงที่นี่"}} {feed.busy && } {feed.page.nextCursor && }
; }