diff --git a/components/admin/guide-section-editor.tsx b/components/admin/guide-section-editor.tsx index b6d4699..adbf900 100644 --- a/components/admin/guide-section-editor.tsx +++ b/components/admin/guide-section-editor.tsx @@ -2851,7 +2851,6 @@ function TeamEditor({ const selectedEverywhereTeams = eligibleEverywhereTeams.filter((team) => everywhereSelectedIds.has(team.id)); const allEverywhereTeamsSelected = eligibleEverywhereTeams.length > 0 && eligibleEverywhereTeams.every((team) => everywhereSelectedIds.has(team.id)); - const availableTeamSlots = 30 - teams.length; const value = { groups: teamGroups.map((group) => ({ name: group.name.trim() || null, note: group.note.trim() || null, iconMediaId: group.iconMediaId, teams: group.teams.map((team) => ({ title: team.title, @@ -3122,10 +3121,6 @@ function TeamEditor({ }; const appendImportedTeams = (importedTeams: Array>) => { if (!importedTeams.length) return false; - if (teams.length + importedTeams.length > 30) { - toast.error("Import ไม่ได้ เพราะ Guide จะมี Team เกิน 30 Team"); - return false; - } for (const imported of importedTeams) { const catalogIssue = findInvalidTeamCatalogReference( imported, @@ -3461,13 +3456,12 @@ function TeamEditor({ type="button" variant="ghost" size="icon-sm" - disabled={teams.length >= 30} onClick={() => setTeams((current) => { const sourceIndex = current.findIndex( (item) => item.clientId === team.clientId, ); - if (sourceIndex === -1 || current.length >= 30) + if (sourceIndex === -1) return current; const source = current[sourceIndex]; const title = source.title.trim() @@ -3858,7 +3852,6 @@ function TeamEditor({ - - diff --git a/lib/guides/mutations.ts b/lib/guides/mutations.ts index 172af0e..e18fc12 100644 --- a/lib/guides/mutations.ts +++ b/lib/guides/mutations.ts @@ -351,9 +351,33 @@ export async function saveTeams(guideId: string, input: unknown, context: GuideM await tx.delete(teamConditions).where(eq(teamConditions.guideId, guideId)); for (const [groupOrder, group] of data.groups.entries()) { const [condition] = await tx.insert(teamConditions).values({ guideId, name: group.name, note: group.note, iconMediaId: group.iconMediaId, sortOrder: groupOrder }).returning(); - for (const [sortOrder, team] of group.teams.entries()) { - const [created] = await tx.insert(teamCompositions).values({ guideId, conditionId: condition.id, title: team.title, rotationSeconds: team.rotationSeconds.toString(), combo: team.combo, sortOrder }).returning(); - await tx.insert(teamMembers).values(team.members.map((member, position) => ({ ...member, teamId: created.id, position, damage: member.damage.toString() }))); + for (let start = 0; start < group.teams.length; start += 500) { + const batch = group.teams.slice(start, start + 500); + const created = await tx.insert(teamCompositions).values( + batch.map((team, index) => ({ + guideId, + conditionId: condition.id, + title: team.title, + rotationSeconds: team.rotationSeconds.toString(), + combo: team.combo, + sortOrder: start + index, + })), + ).returning({ id: teamCompositions.id, sortOrder: teamCompositions.sortOrder }); + const teamIdByOrder = new Map(); + for (const team of created) teamIdByOrder.set(team.sortOrder, team.id); + const memberRows = batch.flatMap((team, index) => { + const teamId = teamIdByOrder.get(start + index); + if (!teamId) throw new Error("บันทึก Team ไม่สำเร็จ"); + return team.members.map((member, position) => ({ + ...member, + teamId, + position, + damage: member.damage.toString(), + })); + }); + for (let memberStart = 0; memberStart < memberRows.length; memberStart += 500) { + await tx.insert(teamMembers).values(memberRows.slice(memberStart, memberStart + 500)); + } } } await auditGuide(tx, context, "guide.team.saved", updated, "team"); diff --git a/lib/guides/schemas.test.ts b/lib/guides/schemas.test.ts index d3e5ad3..5f103b3 100644 --- a/lib/guides/schemas.test.ts +++ b/lib/guides/schemas.test.ts @@ -216,6 +216,17 @@ describe("structured guide validation", () => { 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 more than 30 teams in one condition", () => { + const members = ["1", "2", "3", "4"].map((characterKey) => ({ + characterKey, weaponKey: "weapon-1", artifactKey: "artifact-1", secondArtifactKey: null, + constellation: 0, refinement: 1, damage: 100, + })); + const teams = Array.from({ length: 31 }, (_, index) => ({ + title: `Team ${index + 1}`, rotationSeconds: 20, combo: "E > Q", members, + })); + expect(teamSectionSchema.safeParse({ expectedVersion: 1, groups: [{ note: null, teams }] }).success).toBe(true); + }); + it("accepts all-level ranges for team constellations and refinements", () => { const member = (characterKey: string) => ({ characterKey, weaponKey: "weapon-1", artifactKey: "artifact-1", secondArtifactKey: null, constellation: "0-6", refinement: "1-5", damage: 100 }); const input = { expectedVersion: 1, groups: [{ note: null, teams: [{ title: "Test", rotationSeconds: 20, combo: "E > Q", members: ["1", "2", "3", "4"].map(member) }] }] }; diff --git a/lib/guides/schemas.ts b/lib/guides/schemas.ts index 76b707d..8f1f79f 100644 --- a/lib/guides/schemas.ts +++ b/lib/guides/schemas.ts @@ -175,7 +175,7 @@ export const teamSectionSchema = z.strictObject({ }).refine((members) => members.some((member) => member.damage > 0), { message: "ความเสียหายรวมต้องมากกว่า 0", }), - })).max(30), + })), })).max(20), });