feat: add guide section header notes
This commit is contained in:
@@ -130,6 +130,7 @@ export interface GuideSectionEditorProps {
|
||||
catalogConstellations: CatalogConstellation[];
|
||||
catalogTalents: CatalogTalent[];
|
||||
initial: {
|
||||
sectionNote: string | null;
|
||||
weapons: WeaponValue[];
|
||||
artifactGroups: ArtifactValue[];
|
||||
artifactProfile: ArtifactProfile;
|
||||
@@ -325,8 +326,9 @@ function WeaponEditor({ guide, weapons, characters, initial }: EditorBase) {
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
|
||||
);
|
||||
const [note, setNote] = useState(initial.sectionNote ?? "");
|
||||
const [rows, setRows] = useState(initial.weapons.map((row) => ({ clientId: row.id, weaponKey: row.weaponKey, refinement: row.refinement, overallPercent: row.overallPercent ?? "", personalPercent: row.personalPercent ?? "", note: row.note ?? "" })));
|
||||
const value = { rows: rows.map((row) => ({ weaponKey: row.weaponKey, refinement: row.refinement, overallPercent: row.overallPercent === "" ? null : Number(row.overallPercent), personalPercent: row.personalPercent === "" ? null : Number(row.personalPercent), note: row.note.trim() || null })) };
|
||||
const value = { note: note.trim() || null, rows: rows.map((row) => ({ weaponKey: row.weaponKey, refinement: row.refinement, overallPercent: row.overallPercent === "" ? null : Number(row.overallPercent), personalPercent: row.personalPercent === "" ? null : Number(row.personalPercent), note: row.note.trim() || null })) };
|
||||
const validation = weaponSectionSchema.safeParse({ ...value, expectedVersion: guide.version });
|
||||
const autosave = useAutosave({ value, valid: validation.success, initialVersion: guide.version, persist: (next, expectedVersion) => saveWeaponSection(guide.id, { ...next, expectedVersion }) });
|
||||
const handleWeaponDragEnd = ({ active, over }: DragEndEvent) => {
|
||||
@@ -341,6 +343,7 @@ function WeaponEditor({ guide, weapons, characters, initial }: EditorBase) {
|
||||
<Card>
|
||||
<CardHeader><CardTitle>Weapon tier list</CardTitle><CardDescription>ลากเพื่อเรียงอาวุธจากดีที่สุดลงมา และกรอกเปอร์เซ็นต์อย่างน้อยหนึ่งช่อง ระบบจะบันทึกอัตโนมัติ</CardDescription></CardHeader>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<Field><FieldLabel htmlFor="weapon-section-note">Header note (optional)</FieldLabel><Textarea id="weapon-section-note" value={note} onChange={(event) => setNote(event.target.value)} /></Field>
|
||||
<DndContext sensors={weaponSensors} collisionDetection={closestCenter} onDragEnd={handleWeaponDragEnd}>
|
||||
<SortableContext items={rows.map((row) => row.clientId)} strategy={verticalListSortingStrategy}>
|
||||
<div className="flex flex-col gap-4">
|
||||
@@ -536,6 +539,7 @@ function ArtifactEditor({ guide, artifacts, initial, catalogTalents }: EditorBas
|
||||
}
|
||||
|
||||
function ConstellationEditor({ guide, initial, catalogConstellations }: EditorBase) {
|
||||
const [note, setNote] = useState(initial.sectionNote ?? "");
|
||||
const [rows, setRows] = useState(() => {
|
||||
const source = catalogConstellations.length ? catalogConstellations : Array.from({ length: 6 }, (_, index) => ({ level: index + 1, name: `C${index + 1}`, description: initial.constellations.find((row) => row.level === index + 1)?.detail ?? "", iconUrl: null }));
|
||||
return source.map((constellation) => {
|
||||
@@ -543,14 +547,17 @@ function ConstellationEditor({ guide, initial, catalogConstellations }: EditorBa
|
||||
return { ...constellation, cumulativePercent: saved?.cumulativePercent ?? "", isGreat: saved?.isGreat ?? false };
|
||||
});
|
||||
});
|
||||
const value = { rows: rows.filter((row) => row.description.trim() && row.cumulativePercent !== "").map((row) => ({ level: row.level, detail: row.description, cumulativePercent: Number(row.cumulativePercent), isGreat: row.isGreat })) };
|
||||
const value = { note: note.trim() || null, rows: rows.filter((row) => row.description.trim() && row.cumulativePercent !== "").map((row) => ({ level: row.level, detail: row.description, cumulativePercent: Number(row.cumulativePercent), isGreat: row.isGreat })) };
|
||||
const validation = constellationSectionSchema.safeParse({ ...value, expectedVersion: guide.version });
|
||||
const autosave = useAutosave({ value, valid: validation.success, initialVersion: guide.version, persist: (next, expectedVersion) => saveConstellationSection(guide.id, { ...next, expectedVersion }) });
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader><CardTitle>Constellations</CardTitle><CardDescription>ชื่อ รายละเอียด และไอคอนมาจาก Lunaris อัตโนมัติ กรอกเฉพาะเปอร์เซ็นต์เทียบกับ C0 = 100%</CardDescription></CardHeader>
|
||||
<CardContent className="grid gap-4 lg:grid-cols-2">
|
||||
{rows.map((row, index) => <Card key={row.level} size="sm"><CardHeader>{row.iconUrl ? <Image src={row.iconUrl} alt="" width={48} height={48} className="rounded-lg bg-muted" /> : null}<CardTitle>C{row.level} · {row.name}</CardTitle><CardDescription>{row.description}</CardDescription></CardHeader><CardContent className="flex items-end gap-3"><Field className="min-w-0 flex-1"><FieldLabel>Cumulative %</FieldLabel><Input type="number" min="0" step="0.01" value={row.cumulativePercent} onChange={(event) => setRows(rows.map((item, itemIndex) => itemIndex === index ? { ...item, cumulativePercent: event.target.value } : item))} /></Field><Toggle variant="outline" pressed={row.isGreat} onPressedChange={(isGreat) => setRows(rows.map((item, itemIndex) => itemIndex === index ? { ...item, isGreat } : item))} aria-label={`Mark C${row.level} percentage as great`} className={cn(SELECTED_TOGGLE_CLASS, "data-[state=on]:text-warning")}><SparklesIcon data-icon="inline-start" />Great %</Toggle></CardContent></Card>)}
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<Field><FieldLabel htmlFor="constellation-section-note">Header note (optional)</FieldLabel><Textarea id="constellation-section-note" value={note} onChange={(event) => setNote(event.target.value)} /></Field>
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
{rows.map((row, index) => <Card key={row.level} size="sm"><CardHeader>{row.iconUrl ? <Image src={row.iconUrl} alt="" width={48} height={48} className="rounded-lg bg-muted" /> : null}<CardTitle>C{row.level} · {row.name}</CardTitle><CardDescription>{row.description}</CardDescription></CardHeader><CardContent className="flex items-end gap-3"><Field className="min-w-0 flex-1"><FieldLabel>Cumulative %</FieldLabel><Input type="number" min="0" step="0.01" value={row.cumulativePercent} onChange={(event) => setRows(rows.map((item, itemIndex) => itemIndex === index ? { ...item, cumulativePercent: event.target.value } : item))} /></Field><Toggle variant="outline" pressed={row.isGreat} onPressedChange={(isGreat) => setRows(rows.map((item, itemIndex) => itemIndex === index ? { ...item, isGreat } : item))} aria-label={`Mark C${row.level} percentage as great`} className={cn(SELECTED_TOGGLE_CLASS, "data-[state=on]:text-warning")}><SparklesIcon data-icon="inline-start" />Great %</Toggle></CardContent></Card>)}
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="justify-end"><AutosaveStatus state={autosave} issues={validation.success ? [] : validation.error.issues.map((issue) => issue.path.length ? `${issue.path.join(".")}: ${issue.message}` : issue.message)} /></CardFooter>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user