Files
buzz-sheet/components/admin/template-gallery.tsx
T

105 lines
5.0 KiB
TypeScript

"use client";
import { LayoutTemplateIcon } from "lucide-react";
import { useRouter } from "next/navigation";
import { useState, useTransition } from "react";
import { toast } from "sonner";
import { applyEditorTemplate } from "@/app/admin/actions";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Field, FieldDescription, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/spinner";
import { Textarea } from "@/components/ui/textarea";
import type { TemplateBlueprint } from "@/lib/templates";
export function TemplateGallery({ templates }: { templates: readonly TemplateBlueprint[] }) {
const router = useRouter();
const [selected, setSelected] = useState<TemplateBlueprint | null>(null);
const [pending, startTransition] = useTransition();
function submit(formData: FormData) {
if (!selected) return;
startTransition(async () => {
const result = await applyEditorTemplate({
templateSlug: selected.slug,
characterSlug: String(formData.get("slug") ?? ""),
nameTh: String(formData.get("nameTh") ?? ""),
nameEn: String(formData.get("nameEn") ?? ""),
description: String(formData.get("description") ?? ""),
});
if (result.status === "created") {
toast.success("สร้างคู่มือจากเทมเพลตแล้ว");
router.push(result.location);
router.refresh();
} else if (result.status === "validation-error") {
toast.error(result.issues[0] ?? "ข้อมูลไม่ถูกต้อง");
} else {
toast.error("สร้างคู่มือไม่สำเร็จ โปรดตรวจสอบ slug ที่ซ้ำกัน");
}
});
}
return (
<>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
{templates.map((template) => (
<Card key={template.slug}>
<CardHeader>
<div className="mb-2 grid size-9 place-items-center rounded-lg bg-primary/15 text-primary"><LayoutTemplateIcon /></div>
<CardTitle>{template.name}</CardTitle>
<CardDescription>{template.description}</CardDescription>
</CardHeader>
<CardContent className="flex flex-wrap gap-2">
<Badge variant="outline">{template.pages.length} หน้า</Badge>
<Badge variant="outline">{template.dataSources.length} แหล่งข้อมูล</Badge>
<Badge variant="secondary">{template.category}</Badge>
</CardContent>
<CardFooter>
<Button className="w-full" onClick={() => setSelected(template)}>ใช้เทมเพลต</Button>
</CardFooter>
</Card>
))}
</div>
<Dialog open={Boolean(selected)} onOpenChange={(open) => { if (!open) setSelected(null); }}>
<DialogContent>
<DialogHeader>
<DialogTitle>สร้างจาก {selected?.name}</DialogTitle>
<DialogDescription>ระบบจะสร้างตัวละคร หน้า บล็อก และแหล่งข้อมูลแยกอิสระ โดยเริ่มต้นในสถานะซ่อน</DialogDescription>
</DialogHeader>
<form action={submit} className="space-y-5">
<FieldGroup>
<Field><FieldLabel htmlFor="template-name-th">ชื่อภาษาไทย</FieldLabel><Input id="template-name-th" name="nameTh" required /></Field>
<Field><FieldLabel htmlFor="template-name-en">ชื่อภาษาอังกฤษ</FieldLabel><Input id="template-name-en" name="nameEn" /></Field>
<Field><FieldLabel htmlFor="template-slug">Slug</FieldLabel><Input id="template-slug" name="slug" required pattern="[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*" placeholder="character-name" /><FieldDescription>ใช้ใน URL และเปลี่ยนภายหลังได้โดยลิงก์เดิมยังเปลี่ยนเส้นทางให้</FieldDescription></Field>
<Field><FieldLabel htmlFor="template-description">คำอธิบาย</FieldLabel><Textarea id="template-description" name="description" /></Field>
</FieldGroup>
<DialogFooter>
<Button type="button" variant="outline" onClick={() => setSelected(null)}>ยกเลิก</Button>
<Button type="submit" disabled={pending}>{pending ? <Spinner /> : null}{pending ? "กำลังสร้าง…" : "สร้างคู่มือ"}</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</>
);
}