From c63b879ddbaeb1322fb4c86693c0984d763b1b2c Mon Sep 17 00:00:00 2001 From: gunshiz Date: Wed, 2 Sep 2026 20:59:41 +0700 Subject: [PATCH] feat(admin) : streamline team editing and delete safety --- app/api/teams/[teamId]/image/route.tsx | 2 +- components/admin/block-canvas.tsx | 33 ++--- .../admin/delete-confirmation-dialog.tsx | 53 +++++++ components/admin/guide-section-editor.tsx | 137 ++++++++++++++---- components/admin/structured-dashboard.tsx | 6 +- lib/guides/schemas.test.ts | 6 + lib/guides/schemas.ts | 2 +- 7 files changed, 178 insertions(+), 61 deletions(-) create mode 100644 components/admin/delete-confirmation-dialog.tsx diff --git a/app/api/teams/[teamId]/image/route.tsx b/app/api/teams/[teamId]/image/route.tsx index 3c4ac32..f00466f 100644 --- a/app/api/teams/[teamId]/image/route.tsx +++ b/app/api/teams/[teamId]/image/route.tsx @@ -315,7 +315,7 @@ export async function GET( style={{ display: "flex", color: "#f7f7f7", - fontSize: 34, + fontSize: 36, }} > {amount(damage)} diff --git a/components/admin/block-canvas.tsx b/components/admin/block-canvas.tsx index ff3aeee..efdf4f7 100644 --- a/components/admin/block-canvas.tsx +++ b/components/admin/block-canvas.tsx @@ -11,16 +11,7 @@ import { } from "lucide-react"; import { useState } from "react"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from "@/components/ui/alert-dialog"; +import { DeleteConfirmationDialog } from "@/components/admin/delete-confirmation-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; @@ -239,20 +230,14 @@ function EditableBlock({ - - - - ลบบล็อกนี้หรือไม่ - - บล็อก “{blockLabels[block.type] ?? block.type}” จะถูกนำออกจากหน้า คุณยังย้อนกลับได้ก่อนประวัติการแก้ไขถูกรีเซ็ต - - - - ยกเลิก - ลบบล็อก - - - + ); } diff --git a/components/admin/delete-confirmation-dialog.tsx b/components/admin/delete-confirmation-dialog.tsx new file mode 100644 index 0000000..7addc12 --- /dev/null +++ b/components/admin/delete-confirmation-dialog.tsx @@ -0,0 +1,53 @@ +"use client"; + +import type { ComponentProps, ReactNode } from "react"; + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog"; + +interface DeleteConfirmationDialogProps { + title: ReactNode; + description: ReactNode; + onConfirm: () => void | Promise; + confirmLabel?: ReactNode; + trigger?: ComponentProps["render"]; + children?: ReactNode; + open?: ComponentProps["open"]; + onOpenChange?: ComponentProps["onOpenChange"]; +} + +export function DeleteConfirmationDialog({ + title, + description, + onConfirm, + confirmLabel = "ยืนยันลบ", + trigger, + children, + open, + onOpenChange, +}: DeleteConfirmationDialogProps) { + return ( + + {trigger ? {children} : null} + + + {title} + {description} + + + ยกเลิก + {confirmLabel} + + + + ); +} diff --git a/components/admin/guide-section-editor.tsx b/components/admin/guide-section-editor.tsx index 33a2e16..87e3e17 100644 --- a/components/admin/guide-section-editor.tsx +++ b/components/admin/guide-section-editor.tsx @@ -3,6 +3,7 @@ import Image from "next/image"; import { ChartNoAxesCombinedIcon, + CopyIcon, GemIcon, GripVerticalIcon, HeartPulseIcon, @@ -44,19 +45,9 @@ import { saveWeaponSection, } from "@/app/admin/actions"; import { AutosaveStatus } from "@/components/admin/autosave-status"; +import { DeleteConfirmationDialog } from "@/components/admin/delete-confirmation-dialog"; import { MediaUploader } from "@/components/admin/media-uploader"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; import { @@ -366,7 +357,7 @@ function WeaponEditor({ guide, weapons, characters, initial }: EditorBase) { row.clientId)} strategy={verticalListSortingStrategy}>
- {rows.map((row, rowIndex) => { const selectedWeapon = compatibleWeapons.find((weapon) => weapon.key === row.weaponKey); return {(dragHandle) => {selectedWeapon ?
: null}อันดับ {rowIndex + 1}
{dragHandle}
อาวุธ setRows(rows.map((item) => item.clientId === row.clientId ? { ...item, weaponKey } : item))} />Refinement { const next = selected[0]; if (next) setRows(rows.map((item) => item.clientId === row.clientId ? { ...item, refinement: Number(next.slice(1)) } : item)); }}>{[1, 2, 3, 4, 5].map((refinement) => R{refinement})}
Overall % setRows(rows.map((item) => item.clientId === row.clientId ? { ...item, overallPercent: event.target.value } : item))} />Personal % setRows(rows.map((item) => item.clientId === row.clientId ? { ...item, personalPercent: event.target.value } : item))} />
Note (optional)