feat(admin) : show Lunaris version and link guide cards
CI / Verify (push) Successful in 1m4s
CI / Build immutable images and deploy (push) Successful in 1m53s

This commit is contained in:
2026-09-03 15:39:44 +07:00 Unverified
parent dbfb9de2e8
commit 5700b55d78
3 changed files with 16 additions and 6 deletions
+6 -4
View File
@@ -17,9 +17,10 @@ import { Spinner } from "@/components/ui/spinner";
type GuideCard = { id: string; characterKey: string; name: string; overview: string; coverUrl: string; version: number; updatedAt: Date; trashedAt: Date | null };
export function StructuredDashboard({ guides, trash, catalogReady }: { guides: GuideCard[]; trash: GuideCard[]; catalogReady: boolean }) {
export function StructuredDashboard({ guides, trash, catalogVersion }: { guides: GuideCard[]; trash: GuideCard[]; catalogVersion: string | null }) {
const router = useRouter();
const [pending, startTransition] = useTransition();
const catalogReady = Boolean(catalogVersion);
function run(action: () => Promise<{ status: string; message?: string }>, success: string) {
startTransition(async () => {
@@ -32,16 +33,17 @@ export function StructuredDashboard({ guides, trash, catalogReady }: { guides: G
return (
<div className="flex flex-col gap-10">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="flex items-center gap-2"><Badge variant="outline">Lunaris {catalogReady ? "พร้อมใช้งาน" : "ยังไม่ Sync"}</Badge></div>
<div className="flex items-center gap-2"><Badge variant="outline">Lunaris {catalogVersion ? `v${catalogVersion}` : "ยังไม่ Sync"}</Badge></div>
<div className="flex gap-2"><Button variant="outline" disabled={pending} onClick={() => run(startCatalogSync, "Sync สำเร็จแล้ว")}>{pending ? <Spinner data-icon="inline-start" /> : <RefreshCwIcon data-icon="inline-start" />}Sync Catalog</Button><Button render={<Link href="/admin/create" />} nativeButton={false} disabled={!catalogReady}><PlusIcon data-icon="inline-start" />สร้าง Guide</Button></div>
</div>
{guides.length ? (
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-3">{guides.map((guide) => (
<Card key={guide.id} className="overflow-hidden py-0">
<Card key={guide.id} className="relative overflow-hidden py-0 transition-colors hover:bg-muted/50">
<Link href={`/admin/${guide.characterKey}`} prefetch className="absolute inset-0 z-10 rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-ring"><span className="sr-only">แก้ไข Guide {guide.name}</span></Link>
<div className="relative aspect-square bg-muted"><Image src={guide.coverUrl} alt={`Guide ${guide.name}`} fill className="object-cover" /></div>
<CardHeader className="pt-5"><CardTitle>{guide.name}</CardTitle><CardDescription className="line-clamp-2">{guide.overview}</CardDescription><CardAction><Badge variant="secondary">v{guide.version}</Badge></CardAction></CardHeader>
<CardContent><p className="text-xs text-muted-foreground">แก้ไขล่าสุด {new Date(guide.updatedAt).toLocaleString("th-TH")}</p></CardContent>
<CardFooter className="justify-between pb-5"><Button render={<Link href={`/admin/${guide.characterKey}`} />} nativeButton={false}>แก้ไข Guide</Button><DeleteConfirmationDialog trigger={<Button variant="ghost" size="icon" />} title={`ย้าย ${guide.name} ไปถังขยะ?`} description="Guide จะหายจากหน้าเว็บไซต์ทันที และสามารถกู้คืนได้ภายหลัง" confirmLabel="ย้ายไปถังขยะ" onConfirm={() => run(() => moveGuideToTrash(guide.id), "ย้ายไปถังขยะแล้ว")}><Trash2Icon /><span className="sr-only">ย้าย {guide.name} ไปถังขยะ</span></DeleteConfirmationDialog></CardFooter>
<CardFooter className="justify-between pb-5"><Button className="relative z-20" render={<Link href={`/admin/${guide.characterKey}`} />} nativeButton={false}>แก้ไข Guide</Button><DeleteConfirmationDialog trigger={<Button className="relative z-20" variant="ghost" size="icon" />} title={`ย้าย ${guide.name} ไปถังขยะ?`} description="Guide จะหายจากหน้าเว็บไซต์ทันที และสามารถกู้คืนได้ภายหลัง" confirmLabel="ย้ายไปถังขยะ" onConfirm={() => run(() => moveGuideToTrash(guide.id), "ย้ายไปถังขยะแล้ว")}><Trash2Icon /><span className="sr-only">ย้าย {guide.name} ไปถังขยะ</span></DeleteConfirmationDialog></CardFooter>
</Card>
))}</div>
) : <Empty className="border"><EmptyHeader><EmptyTitle>ยังไม่มี Guide</EmptyTitle><EmptyDescription>Sync Catalog แล้วเลือกตัวละครเพื่อสร้าง Guide แรก</EmptyDescription></EmptyHeader><EmptyContent><Button render={<Link href="/admin/create" />} nativeButton={false} disabled={!catalogReady}>สร้าง Guide</Button></EmptyContent></Empty>}