105 lines
5.0 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|