feat : update
This commit is contained in:
@@ -24,9 +24,6 @@ import {
|
||||
Field, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSet,
|
||||
} from "@/components/ui/field";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious,
|
||||
} from "@/components/ui/pagination";
|
||||
import {
|
||||
Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
@@ -42,8 +39,10 @@ import {
|
||||
teamSectionSchema,
|
||||
weaponSectionSchema,
|
||||
} from "@/lib/guides/schemas";
|
||||
import { supportsWeaponType } from "@/lib/catalog/lunaris";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type CatalogItem = { key: string; name: string; imageUrl: string };
|
||||
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 = { weaponKey: string; refinement: number; overallPercent: string | null; personalPercent: string | null; weapon: CatalogItem };
|
||||
type ArtifactValue = { id: string; name: string; rows: Array<{ firstArtifactKey: string; secondArtifactKey: string | null; percent: string }> };
|
||||
@@ -69,11 +68,9 @@ export interface GuideSectionEditorProps {
|
||||
};
|
||||
}
|
||||
|
||||
const SECTION_ORDER = ["weapon", "artifact", "constellations", "team", "extra"] as const;
|
||||
const SECTION_LABELS = { weapon: "Weapons", artifact: "Artifacts & Stats", constellations: "Constellations", team: "Team Calc", extra: "Extra" };
|
||||
const DEFAULT_TARGET_STATS = ["ATK", "EM", "ER", "CRIT Rate", "CRIT DMG"] as const;
|
||||
|
||||
function CatalogPicker({ items, value, onChange, placeholder }: { items: CatalogItem[]; value: string; onChange: (value: string) => void; placeholder: string }) {
|
||||
function CatalogPicker({ items, value, onChange, placeholder, weaponBackground = false }: { items: CatalogItem[]; value: string; onChange: (value: string) => void; placeholder: string; weaponBackground?: boolean }) {
|
||||
const selected = items.find((item) => item.key === value) ?? null;
|
||||
return (
|
||||
<Combobox items={items} value={selected} onValueChange={(next) => onChange(next?.key ?? "")} itemToStringLabel={(item) => item.name} itemToStringValue={(item) => item.key}>
|
||||
@@ -81,7 +78,7 @@ function CatalogPicker({ items, value, onChange, placeholder }: { items: Catalog
|
||||
<ComboboxContent>
|
||||
<ComboboxEmpty>ไม่พบรายการ</ComboboxEmpty>
|
||||
<ComboboxList>
|
||||
{items.map((item) => <ComboboxItem key={item.key} value={item}><Image src={item.imageUrl} alt="" width={32} height={32} className="rounded-md" />{item.name}</ComboboxItem>)}
|
||||
{items.map((item) => <ComboboxItem key={item.key} value={item}><div className={cn("relative size-8 shrink-0 overflow-hidden rounded-md", weaponBackground && item.rarity === 5 && "bg-[linear-gradient(90deg,rgba(138,90,0,1)_0%,rgba(15,8,0,1)_100%)]", weaponBackground && item.rarity === 4 && "bg-gradient-to-r from-[#a300b8] to-black")}><Image src={item.imageUrl} alt="" fill className="object-contain" /></div>{item.name}</ComboboxItem>)}
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
@@ -113,7 +110,6 @@ function moveItem<T>(items: T[], from: number, to: number): T[] {
|
||||
}
|
||||
|
||||
export function GuideSectionEditor(props: GuideSectionEditorProps) {
|
||||
const index = SECTION_ORDER.indexOf(props.section);
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
{props.section === "weapon" ? <WeaponEditor {...props} /> : null}
|
||||
@@ -121,14 +117,15 @@ export function GuideSectionEditor(props: GuideSectionEditorProps) {
|
||||
{props.section === "constellations" ? <ConstellationEditor {...props} /> : null}
|
||||
{props.section === "team" ? <TeamEditor {...props} /> : null}
|
||||
{props.section === "extra" ? <ExtraEditor {...props} /> : null}
|
||||
<Pagination><PaginationContent className="flex-wrap"><PaginationItem>{index > 0 ? <PaginationPrevious href={`/admin/${props.guide.characterKey}/${SECTION_ORDER[index - 1]}`} text={SECTION_LABELS[SECTION_ORDER[index - 1]]} /> : <PaginationLink href={`/admin/${props.guide.characterKey}`} size="default">Overview</PaginationLink>}</PaginationItem><PaginationItem><PaginationLink href={`/admin/${props.guide.characterKey}/${props.section}`} size="default" isActive>{SECTION_LABELS[props.section]}</PaginationLink></PaginationItem>{index < SECTION_ORDER.length - 1 ? <PaginationItem><PaginationNext href={`/admin/${props.guide.characterKey}/${SECTION_ORDER[index + 1]}`} text={SECTION_LABELS[SECTION_ORDER[index + 1]]} /></PaginationItem> : null}</PaginationContent></Pagination>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type EditorBase = GuideSectionEditorProps;
|
||||
|
||||
function WeaponEditor({ guide, weapons, initial }: EditorBase) {
|
||||
function WeaponEditor({ guide, weapons, characters, initial }: EditorBase) {
|
||||
const characterWeaponType = characters.find((character) => character.key === guide.characterKey)?.weaponType;
|
||||
const compatibleWeapons = weapons.filter((weapon) => supportsWeaponType(characterWeaponType, weapon.weaponType));
|
||||
const [rows, setRows] = useState(initial.weapons.map((row) => ({ weaponKey: row.weaponKey, refinement: row.refinement, overallPercent: row.overallPercent ?? "", personalPercent: row.personalPercent ?? "" })));
|
||||
const value = { rows: rows.map((row) => ({ ...row, overallPercent: row.overallPercent === "" ? null : Number(row.overallPercent), personalPercent: row.personalPercent === "" ? null : Number(row.personalPercent) })) };
|
||||
const autosave = useAutosave({ value, valid: weaponSectionSchema.safeParse({ ...value, expectedVersion: guide.version }).success, initialVersion: guide.version, persist: (next, expectedVersion) => saveWeaponSection(guide.id, { ...next, expectedVersion }) });
|
||||
@@ -136,7 +133,7 @@ function WeaponEditor({ guide, weapons, initial }: EditorBase) {
|
||||
<Card>
|
||||
<CardHeader><CardTitle>Weapon tier list</CardTitle><CardDescription>เรียงอาวุธจากดีที่สุดลงมา และกรอกเปอร์เซ็นต์อย่างน้อยหนึ่งช่อง ระบบจะบันทึกอัตโนมัติ</CardDescription></CardHeader>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
{rows.map((row, rowIndex) => <Card key={rowIndex} size="sm"><CardHeader><CardTitle>อันดับ {rowIndex + 1}</CardTitle><CardAction><RowActions index={rowIndex} length={rows.length} move={(from, to) => setRows(moveItem(rows, from, to))} remove={() => setRows(rows.filter((_, index) => index !== rowIndex))} /></CardAction></CardHeader><CardContent><FieldGroup><Field><FieldLabel>อาวุธ</FieldLabel><CatalogPicker items={weapons} value={row.weaponKey} placeholder="ค้นหาชื่ออาวุธ…" onChange={(weaponKey) => setRows(rows.map((item, index) => index === rowIndex ? { ...item, weaponKey } : item))} /></Field><Field><FieldLabel>Refinement</FieldLabel><ToggleGroup value={[`R${row.refinement}`]} onValueChange={(selected) => { const next = selected[0]; if (next) setRows(rows.map((item, index) => index === rowIndex ? { ...item, refinement: Number(next.slice(1)) } : item)); }}>{[1, 2, 3, 4, 5].map((refinement) => <ToggleGroupItem key={refinement} value={`R${refinement}`}>R{refinement}</ToggleGroupItem>)}</ToggleGroup></Field><div className="grid gap-4 sm:grid-cols-2"><Field><FieldLabel>Overall %</FieldLabel><Input type="number" min="0" step="0.01" value={row.overallPercent} onChange={(event) => setRows(rows.map((item, index) => index === rowIndex ? { ...item, overallPercent: event.target.value } : item))} /></Field><Field><FieldLabel>Personal %</FieldLabel><Input type="number" min="0" step="0.01" value={row.personalPercent} onChange={(event) => setRows(rows.map((item, index) => index === rowIndex ? { ...item, personalPercent: event.target.value } : item))} /></Field></div></FieldGroup></CardContent></Card>)}
|
||||
{rows.map((row, rowIndex) => { const selectedWeapon = compatibleWeapons.find((weapon) => weapon.key === row.weaponKey); return <Card key={rowIndex} size="sm"><CardHeader><CardTitle className="flex items-center gap-3">{selectedWeapon ? <div className={cn("relative size-10 shrink-0 overflow-hidden rounded-lg", selectedWeapon.rarity === 5 && "bg-[linear-gradient(90deg,rgba(138,90,0,1)_0%,rgba(15,8,0,1)_100%)]", selectedWeapon.rarity === 4 && "bg-gradient-to-r from-[#a300b8] to-black")}><Image src={selectedWeapon.imageUrl} alt="" fill className="object-contain" /></div> : null}<span>อันดับ {rowIndex + 1}</span></CardTitle><CardAction><RowActions index={rowIndex} length={rows.length} move={(from, to) => setRows(moveItem(rows, from, to))} remove={() => setRows(rows.filter((_, index) => index !== rowIndex))} /></CardAction></CardHeader><CardContent><FieldGroup><Field><FieldLabel>อาวุธ</FieldLabel><CatalogPicker items={compatibleWeapons} value={row.weaponKey} placeholder="ค้นหาชื่ออาวุธ…" weaponBackground onChange={(weaponKey) => setRows(rows.map((item, index) => index === rowIndex ? { ...item, weaponKey } : item))} /></Field><Field><FieldLabel>Refinement</FieldLabel><ToggleGroup value={[`R${row.refinement}`]} onValueChange={(selected) => { const next = selected[0]; if (next) setRows(rows.map((item, index) => index === rowIndex ? { ...item, refinement: Number(next.slice(1)) } : item)); }}>{[1, 2, 3, 4, 5].map((refinement) => <ToggleGroupItem key={refinement} value={`R${refinement}`}>R{refinement}</ToggleGroupItem>)}</ToggleGroup></Field><div className="grid gap-4 sm:grid-cols-2"><Field><FieldLabel>Overall %</FieldLabel><Input type="number" min="0" step="0.01" value={row.overallPercent} onChange={(event) => setRows(rows.map((item, index) => index === rowIndex ? { ...item, overallPercent: event.target.value } : item))} /></Field><Field><FieldLabel>Personal %</FieldLabel><Input type="number" min="0" step="0.01" value={row.personalPercent} onChange={(event) => setRows(rows.map((item, index) => index === rowIndex ? { ...item, personalPercent: event.target.value } : item))} /></Field></div></FieldGroup></CardContent></Card>; })}
|
||||
</CardContent>
|
||||
<CardFooter className="flex-wrap justify-between gap-3"><Button type="button" variant="outline" onClick={() => setRows([...rows, { weaponKey: "", refinement: 1, overallPercent: "", personalPercent: "" }])}><PlusIcon data-icon="inline-start" />เพิ่มอาวุธ</Button><AutosaveStatus state={autosave} /></CardFooter>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user