"use client"; import Image from "next/image"; import { ChartNoAxesCombinedIcon, GemIcon, GripVerticalIcon, HeartPulseIcon, ListOrderedIcon, PlusIcon, RefreshCcwIcon, SparklesIcon, SwordIcon, TimerIcon, Trash2Icon, UserRoundIcon, XIcon, } from "lucide-react"; import { useState, type ReactNode } from "react"; import { closestCenter, DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors, type DragEndEvent, } from "@dnd-kit/core"; import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { saveArtifactSection, saveConstellationSection, saveExtraSectionList, saveTeamSection, saveWeaponSection, } from "@/app/admin/actions"; import { AutosaveStatus } from "@/components/admin/autosave-status"; 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 { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from "@/components/ui/combobox"; import { Field, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSet, } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { Toggle } from "@/components/ui/toggle"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { useAutosave } from "@/hooks/use-autosave"; import { artifactSectionSchema, constellationSectionSchema, extraSectionSchema, mainStatOptions, substatOptions, talentLevelOptions, teamSectionSchema, weaponSectionSchema, } from "@/lib/guides/schemas"; import { normalizeTargetStat, recommendedStatIconName, targetStatDefaults, targetStatIconName, targetStatOptions, } from "@/lib/guides/stats"; import { supportsWeaponType } from "@/lib/catalog/lunaris"; import { rarityGradientClass } from "@/lib/catalog/rarity"; import { normalizeTalentPriorities, reorderTalentPriorities, type TalentPriority, type TalentRelation, } from "@/lib/guides/talents"; import { cn } from "@/lib/utils"; type CatalogItem = { key: string; name: string; imageUrl: string; rarity?: number | null; weaponType?: string | null }; type Guide = { id: string; characterKey: string; version: number; name: string }; type WeaponValue = { id: string; weaponKey: string; refinement: number; overallPercent: string | null; personalPercent: string | null; note: string | null; weapon: CatalogItem }; type ArtifactValue = { id: string; name: string; rows: Array<{ firstArtifactKey: string; secondArtifactKey: string | null; percent: string; note: string | null }> }; type ArtifactProfile = { sands: string[]; goblet: string[]; circlet: string[]; substats: string[]; targets: Array<{ stat: string; value: string }>; talents: TalentPriority[]; note: string | null } | null; type ConstellationValue = { level: number; detail: string; cumulativePercent: string; isGreat: boolean }; type CatalogConstellation = { level: number; name: string; description: string; iconUrl: string | null }; type CatalogTalent = { type: "NA" | "E" | "Q"; name: string; iconUrl: string | null }; type TeamValue = { id: string; title: string; rotationSeconds: string; combo: string; members: Array<{ characterKey: string; weaponKey: string | null; artifactKey: string | null; constellation: number; refinement: number; damage: string }> }; let clientIdCounter = 0; function createClientId(prefix: string): string { const browserCrypto = globalThis.crypto; if (typeof browserCrypto?.randomUUID === "function") return `${prefix}-${browserCrypto.randomUUID()}`; clientIdCounter += 1; return `${prefix}-${Date.now()}-${clientIdCounter}`; } export interface GuideSectionEditorProps { section: "weapon" | "artifact" | "constellations" | "team" | "extra"; guide: Guide; weapons: CatalogItem[]; artifacts: CatalogItem[]; characters: CatalogItem[]; catalogConstellations: CatalogConstellation[]; catalogTalents: CatalogTalent[]; initial: { weapons: WeaponValue[]; artifactGroups: ArtifactValue[]; artifactProfile: ArtifactProfile; constellations: ConstellationValue[]; teams: TeamValue[]; extras: Array<{ title: string; rows: Array<{ mediaId: string | null; ratio: "1:1" | "16:9"; heading: string; body: string; percent: string | null; imageUrl: string | null }> }>; }; } const SELECTED_TOGGLE_CLASS = "aria-pressed:border-primary aria-pressed:ring-2 aria-pressed:ring-primary/30"; function CatalogPicker({ items, value, onChange, placeholder, rarityBackground = false, showSelectedImage = false }: { items: CatalogItem[]; value: string; onChange: (value: string) => void; placeholder: string; rarityBackground?: boolean; showSelectedImage?: boolean }) { const selected = items.find((item) => item.key === value) ?? null; return (
{showSelectedImage && selected ?
: null}
onChange(next?.key ?? "")} itemToStringLabel={(item) => item.name} itemToStringValue={(item) => item.key} autoHighlight> ไม่พบรายการ {(item) =>
{item.name}
}
); } function StatSelect({ label, options, value, max, onChange }: { label: string; options: readonly string[]; value: string[]; max: number; onChange: (value: string[]) => void }) { return ( {label} เลือกได้สูงสุด {max} ค่า ); } function StatIcons({ values }: { values: readonly string[] }) { const iconNames = values.flatMap((value) => { const targetIcon = targetStatIconName(value); if (targetIcon) return [targetIcon]; const recommendedIcon = recommendedStatIconName(value); return recommendedIcon ? [recommendedIcon] : []; }); if (!iconNames.length) return null; return ; } function StatIcon({ iconName }: { iconName: string }) { const [hasError, setHasError] = useState(false); if (hasError) { const PlaceholderIcon = iconName === "healing-bonus" ? HeartPulseIcon : SparklesIcon; return