fix : not null artifact and weapon in team calc
CI / Build immutable images and deploy (push) Successful in 2m6s

This commit is contained in:
2026-09-17 17:36:34 +07:00 Unverified
parent 13b6c646fe
commit d5f067627b
7 changed files with 62 additions and 20 deletions
+17 -13
View File
@@ -563,7 +563,7 @@ function CatalogSlotPicker({
aria-label={selected ? `${label}: ${selected.name}` : label} aria-label={selected ? `${label}: ${selected.name}` : label}
aria-invalid={invalid} aria-invalid={invalid}
className={cn( 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), rarityBackground && selected && rarityGradientClass(selected.rarity),
className, className,
)} )}
@@ -614,11 +614,13 @@ function ArtifactSlotPicker({
values, values,
onChange, onChange,
label, label,
invalid = false,
}: { }: {
items: CatalogItem[]; items: CatalogItem[];
values: string[]; values: string[];
onChange: (values: string[]) => void; onChange: (values: string[]) => void;
label: string; label: string;
invalid?: boolean;
}) { }) {
const firstKey = values[0]; const firstKey = values[0];
const secondKey = values[1]; const secondKey = values[1];
@@ -656,7 +658,8 @@ function ArtifactSlotPicker({
aria-label={selected.length aria-label={selected.length
? `${label}: ${selected.map((item) => item.name).join(", ")}` ? `${label}: ${selected.map((item) => item.name).join(", ")}`
: label} : 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 ? ( {selected.length ? (
<TeamArtifactIcon <TeamArtifactIcon
@@ -3550,22 +3553,13 @@ function TeamEditor({
value={member.characterKey} value={member.characterKey}
label={`เลือกตัวละครตำแหน่ง ${memberIndex + 1}`} label={`เลือกตัวละครตำแหน่ง ${memberIndex + 1}`}
placeholder="ค้นหาชื่อตัวละคร…" placeholder="ค้นหาชื่อตัวละคร…"
rarityBackground
invalid={memberFieldInvalid( invalid={memberFieldInvalid(
teamIndex, teamIndex,
memberIndex, memberIndex,
"characterKey", "characterKey",
)} )}
className={cn( className="rounded-lg border-0 [&>img]:object-cover [&>img]:p-0"
"rounded-lg border-0 [&>img]:object-cover [&>img]:p-0",
character &&
rarityGradientClass(
isCustomTravelerKey(
character.key,
)
? 5
: character.rarity,
),
)}
onChange={(characterKey) => onChange={(characterKey) =>
updateTeamMember( updateTeamMember(
teamIndex, teamIndex,
@@ -3589,6 +3583,11 @@ function TeamEditor({
label={`เลือกอาวุธของตัวละครตำแหน่ง ${memberIndex + 1}`} label={`เลือกอาวุธของตัวละครตำแหน่ง ${memberIndex + 1}`}
placeholder="ค้นหาอาวุธที่รองรับ…" placeholder="ค้นหาอาวุธที่รองรับ…"
rarityBackground rarityBackground
invalid={memberFieldInvalid(
teamIndex,
memberIndex,
"weaponKey",
)}
onChange={(weaponKey) => onChange={(weaponKey) =>
updateTeamMember( updateTeamMember(
teamIndex, teamIndex,
@@ -3610,6 +3609,11 @@ function TeamEditor({
(key): key is string => Boolean(key), (key): key is string => Boolean(key),
)} )}
label={`เลือก Artifact สูงสุด 2 ชุดของตัวละครตำแหน่ง ${memberIndex + 1}`} label={`เลือก Artifact สูงสุด 2 ชุดของตัวละครตำแหน่ง ${memberIndex + 1}`}
invalid={memberFieldInvalid(
teamIndex,
memberIndex,
"artifactKey",
)}
onChange={(artifactKeys) => onChange={(artifactKeys) =>
updateTeamMember( updateTeamMember(
teamIndex, teamIndex,
+1 -1
View File
@@ -7,7 +7,7 @@ function Empty({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="empty" data-slot="empty"
className={cn( className={cn(
"flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-xl border-dashed p-6 text-center text-balance", "flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-xl border-dashed p-6 text-center text-balance 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",
className className
)} )}
{...props} {...props}
+2 -2
View File
@@ -479,8 +479,8 @@ export const teamMembers = guidesSchema.table(
teamId: uuid("team_id").notNull().references(() => teamCompositions.id, { onDelete: "cascade" }), teamId: uuid("team_id").notNull().references(() => teamCompositions.id, { onDelete: "cascade" }),
position: integer("position").notNull(), position: integer("position").notNull(),
characterKey: varchar("character_key", { length: 64 }).notNull(), characterKey: varchar("character_key", { length: 64 }).notNull(),
weaponKey: varchar("weapon_key", { length: 64 }), weaponKey: varchar("weapon_key", { length: 64 }).notNull(),
artifactKey: varchar("artifact_key", { length: 64 }), artifactKey: varchar("artifact_key", { length: 64 }).notNull(),
secondArtifactKey: varchar("second_artifact_key", { length: 64 }), secondArtifactKey: varchar("second_artifact_key", { length: 64 }),
constellation: integer("constellation").default(0).notNull(), constellation: integer("constellation").default(0).notNull(),
refinement: integer("refinement").default(1).notNull(), refinement: integer("refinement").default(1).notNull(),
+4 -2
View File
@@ -184,15 +184,17 @@ describe("structured guide validation", () => {
}); });
it("requires four unique team members and non-zero damage", () => { 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 teams = [{ title: "Test", rotationSeconds: 20, combo: "E > Q", members: ["1", "2", "3", "4"].map(member) }];
const base = { expectedVersion: 1, groups: [{ note: null, teams }] }; const base = { expectedVersion: 1, groups: [{ note: null, teams }] };
expect(teamSectionSchema.safeParse(base).success).toBe(true); 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: ["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", () => { 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) }] }] }; 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); expect(teamSectionSchema.safeParse(input).success).toBe(true);
}); });
+2 -2
View File
@@ -156,8 +156,8 @@ export const teamSectionSchema = z.strictObject({
combo: z.string().trim().min(1).max(2_000), combo: z.string().trim().min(1).max(2_000),
members: z.array(z.strictObject({ members: z.array(z.strictObject({
characterKey: catalogKey, characterKey: catalogKey,
weaponKey: catalogKey.nullable(), weaponKey: catalogKey,
artifactKey: catalogKey.nullable(), artifactKey: catalogKey,
secondArtifactKey: catalogKey.nullable(), secondArtifactKey: catalogKey.nullable(),
constellation: z.number().int().min(0).max(6), constellation: z.number().int().min(0).max(6),
refinement: z.number().int().min(1).max(5), refinement: z.number().int().min(1).max(5),
+1
View File
@@ -7,6 +7,7 @@ interface ValidationIssue {
const fieldMessages: Record<string, string> = { const fieldMessages: Record<string, string> = {
weaponKey: "กรุณาเลือกอาวุธ", weaponKey: "กรุณาเลือกอาวุธ",
artifactKey: "กรุณาเลือก Artifact",
firstArtifactKey: "กรุณาเลือก Artifact ชุดแรก", firstArtifactKey: "กรุณาเลือก Artifact ชุดแรก",
characterKey: "กรุณาเลือกตัวละคร", characterKey: "กรุณาเลือกตัวละคร",
overallPercent: "กรุณากรอก Overall % ระหว่าง -9,999 ถึง 9,999", overallPercent: "กรุณากรอก Overall % ระหว่าง -9,999 ถึง 9,999",
+35
View File
@@ -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 aria-invalid={hasError}>
{/* empty-state content */}
</Empty>
```
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.