From d5f067627b7dc83eba97fd51a92595a609775b72 Mon Sep 17 00:00:00 2001 From: gunshiz Date: Thu, 17 Sep 2026 17:36:34 +0700 Subject: [PATCH] fix : not null artifact and weapon in team calc --- components/admin/guide-section-editor.tsx | 30 ++++++++++--------- components/ui/empty.tsx | 2 +- db/schema.ts | 4 +-- lib/guides/schemas.test.ts | 6 ++-- lib/guides/schemas.ts | 4 +-- lib/guides/validation-messages.ts | 1 + shadcn.md | 35 +++++++++++++++++++++++ 7 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 shadcn.md diff --git a/components/admin/guide-section-editor.tsx b/components/admin/guide-section-editor.tsx index f931414..e19c4c9 100644 --- a/components/admin/guide-section-editor.tsx +++ b/components/admin/guide-section-editor.tsx @@ -563,7 +563,7 @@ function CatalogSlotPicker({ aria-label={selected ? `${label}: ${selected.name}` : label} aria-invalid={invalid} className={cn( - "relative flex aspect-square w-full items-center justify-center overflow-hidden rounded-md border bg-muted p-0 outline-none focus-visible:ring-2 focus-visible:ring-ring [&>svg]:hidden", + "relative flex aspect-square w-full items-center justify-center overflow-hidden rounded-md border bg-muted p-0 outline-none focus-visible:ring-2 focus-visible:ring-ring aria-invalid:border aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&>svg]:hidden", rarityBackground && selected && rarityGradientClass(selected.rarity), className, )} @@ -614,11 +614,13 @@ function ArtifactSlotPicker({ values, onChange, label, + invalid = false, }: { items: CatalogItem[]; values: string[]; onChange: (values: string[]) => void; label: string; + invalid?: boolean; }) { const firstKey = values[0]; const secondKey = values[1]; @@ -656,7 +658,8 @@ function ArtifactSlotPicker({ aria-label={selected.length ? `${label}: ${selected.map((item) => item.name).join(", ")}` : label} - className="relative aspect-square w-full overflow-hidden rounded-md border bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring [&>svg]:hidden" + aria-invalid={invalid} + className="relative aspect-square w-full overflow-hidden rounded-md border bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&>svg]:hidden" > {selected.length ? ( img]:object-cover [&>img]:p-0", - character && - rarityGradientClass( - isCustomTravelerKey( - character.key, - ) - ? 5 - : character.rarity, - ), - )} + className="rounded-lg border-0 [&>img]:object-cover [&>img]:p-0" onChange={(characterKey) => updateTeamMember( teamIndex, @@ -3589,6 +3583,11 @@ function TeamEditor({ label={`เลือกอาวุธของตัวละครตำแหน่ง ${memberIndex + 1}`} placeholder="ค้นหาอาวุธที่รองรับ…" rarityBackground + invalid={memberFieldInvalid( + teamIndex, + memberIndex, + "weaponKey", + )} onChange={(weaponKey) => updateTeamMember( teamIndex, @@ -3610,6 +3609,11 @@ function TeamEditor({ (key): key is string => Boolean(key), )} label={`เลือก Artifact สูงสุด 2 ชุดของตัวละครตำแหน่ง ${memberIndex + 1}`} + invalid={memberFieldInvalid( + teamIndex, + memberIndex, + "artifactKey", + )} onChange={(artifactKeys) => updateTeamMember( teamIndex, diff --git a/components/ui/empty.tsx b/components/ui/empty.tsx index b23187b..3cf7e21 100644 --- a/components/ui/empty.tsx +++ b/components/ui/empty.tsx @@ -7,7 +7,7 @@ function Empty({ className, ...props }: React.ComponentProps<"div">) {
teamCompositions.id, { onDelete: "cascade" }), position: integer("position").notNull(), characterKey: varchar("character_key", { length: 64 }).notNull(), - weaponKey: varchar("weapon_key", { length: 64 }), - artifactKey: varchar("artifact_key", { length: 64 }), + weaponKey: varchar("weapon_key", { length: 64 }).notNull(), + artifactKey: varchar("artifact_key", { length: 64 }).notNull(), secondArtifactKey: varchar("second_artifact_key", { length: 64 }), constellation: integer("constellation").default(0).notNull(), refinement: integer("refinement").default(1).notNull(), diff --git a/lib/guides/schemas.test.ts b/lib/guides/schemas.test.ts index 1ce0eb4..91b5ce5 100644 --- a/lib/guides/schemas.test.ts +++ b/lib/guides/schemas.test.ts @@ -184,15 +184,17 @@ describe("structured guide validation", () => { }); it("requires four unique team members and non-zero damage", () => { - const member = (characterKey: string) => ({ characterKey, weaponKey: null, artifactKey: null, secondArtifactKey: null, constellation: 0, refinement: 1, damage: 100 }); + const member = (characterKey: string) => ({ characterKey, weaponKey: "weapon-1", artifactKey: "artifact-1", secondArtifactKey: null, constellation: 0, refinement: 1, damage: 100 }); const teams = [{ title: "Test", rotationSeconds: 20, combo: "E > Q", members: ["1", "2", "3", "4"].map(member) }]; const base = { expectedVersion: 1, groups: [{ note: null, teams }] }; expect(teamSectionSchema.safeParse(base).success).toBe(true); expect(teamSectionSchema.safeParse({ ...base, groups: [{ note: null, teams: [{ ...teams[0], members: ["1", "1", "3", "4"].map(member) }] }] }).success).toBe(false); + expect(teamSectionSchema.safeParse({ ...base, groups: [{ note: null, teams: [{ ...teams[0], members: [{ ...member("1"), weaponKey: null }, ...["2", "3", "4"].map(member)] }] }] }).success).toBe(false); + expect(teamSectionSchema.safeParse({ ...base, groups: [{ note: null, teams: [{ ...teams[0], members: [{ ...member("1"), artifactKey: null }, ...["2", "3", "4"].map(member)] }] }] }).success).toBe(false); }); it("accepts a second artifact set for a team member", () => { - const member = (characterKey: string) => ({ characterKey, weaponKey: null, artifactKey: "artifact-1", secondArtifactKey: characterKey === "1" ? "artifact-2" : null, constellation: 0, refinement: 1, damage: 100 }); + const member = (characterKey: string) => ({ characterKey, weaponKey: "weapon-1", artifactKey: "artifact-1", secondArtifactKey: characterKey === "1" ? "artifact-2" : null, constellation: 0, refinement: 1, damage: 100 }); const input = { expectedVersion: 1, groups: [{ note: null, teams: [{ title: "Test", rotationSeconds: 20, combo: "E > Q", members: ["1", "2", "3", "4"].map(member) }] }] }; expect(teamSectionSchema.safeParse(input).success).toBe(true); }); diff --git a/lib/guides/schemas.ts b/lib/guides/schemas.ts index 89afb1b..3f3897b 100644 --- a/lib/guides/schemas.ts +++ b/lib/guides/schemas.ts @@ -156,8 +156,8 @@ export const teamSectionSchema = z.strictObject({ combo: z.string().trim().min(1).max(2_000), members: z.array(z.strictObject({ characterKey: catalogKey, - weaponKey: catalogKey.nullable(), - artifactKey: catalogKey.nullable(), + weaponKey: catalogKey, + artifactKey: catalogKey, secondArtifactKey: catalogKey.nullable(), constellation: z.number().int().min(0).max(6), refinement: z.number().int().min(1).max(5), diff --git a/lib/guides/validation-messages.ts b/lib/guides/validation-messages.ts index 22266e3..82e9275 100644 --- a/lib/guides/validation-messages.ts +++ b/lib/guides/validation-messages.ts @@ -7,6 +7,7 @@ interface ValidationIssue { const fieldMessages: Record = { weaponKey: "กรุณาเลือกอาวุธ", + artifactKey: "กรุณาเลือก Artifact", firstArtifactKey: "กรุณาเลือก Artifact ชุดแรก", characterKey: "กรุณาเลือกตัวละคร", overallPercent: "กรุณากรอก Overall % ระหว่าง -9,999 ถึง 9,999", diff --git a/shadcn.md b/shadcn.md new file mode 100644 index 0000000..5488648 --- /dev/null +++ b/shadcn.md @@ -0,0 +1,35 @@ +# shadcn/ui customizations + +This project keeps local changes to generated shadcn/ui components. Review these customizations before updating or reinstalling components. + +## `Empty` + +File: `components/ui/empty.tsx` + +The root `Empty` component supports an invalid state through `aria-invalid`: + +```tsx + + {/* empty-state content */} + +``` + +The customization adds a destructive border and ring with these classes: + +```text +aria-invalid:border +aria-invalid:border-destructive +aria-invalid:ring-3 +aria-invalid:ring-destructive/20 +dark:aria-invalid:border-destructive/50 +dark:aria-invalid:ring-destructive/40 +``` + +Before updating the component, inspect the upstream diff: + +```bash +bunx --bun shadcn@latest add empty --dry-run +bunx --bun shadcn@latest add empty --diff empty.tsx +``` + +Manually merge upstream changes and preserve the invalid-state classes. Do not use `--overwrite` unless removing local customizations is intentional.