From 8723000cdd499c414f273ad1a6f4a379d96ca75c Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Sun, 12 Apr 2026 14:18:04 -0700 Subject: [PATCH] feat: add batch-add-results server intent with duplicate filtering Co-Authored-By: Claude Sonnet 4.6 --- .../__tests__/batch-results-helpers.test.ts | 29 +++++++++++++ ...rts-seasons.$id.events.$eventId.helpers.ts | 10 +++++ ...orts-seasons.$id.events.$eventId.server.ts | 43 +++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 app/routes/__tests__/batch-results-helpers.test.ts create mode 100644 app/routes/admin.sports-seasons.$id.events.$eventId.helpers.ts diff --git a/app/routes/__tests__/batch-results-helpers.test.ts b/app/routes/__tests__/batch-results-helpers.test.ts new file mode 100644 index 0000000..a40172e --- /dev/null +++ b/app/routes/__tests__/batch-results-helpers.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from "vitest"; +import { filterNewResults } from "../admin.sports-seasons.$id.events.$eventId.helpers"; + +describe("filterNewResults", () => { + it("returns all rows when no existing results", () => { + const incoming = [ + { participantId: "p1", placement: 1 }, + { participantId: "p2", placement: 2 }, + ]; + expect(filterNewResults(incoming, new Set())).toEqual(incoming); + }); + + it("skips participantIds already in existingIds", () => { + const incoming = [ + { participantId: "p1", placement: 1 }, + { participantId: "p2", placement: 2 }, + ]; + const existing = new Set(["p1"]); + expect(filterNewResults(incoming, existing)).toEqual([ + { participantId: "p2", placement: 2 }, + ]); + }); + + it("returns empty array when all are duplicates", () => { + const incoming = [{ participantId: "p1", placement: 1 }]; + const existing = new Set(["p1"]); + expect(filterNewResults(incoming, existing)).toEqual([]); + }); +}); diff --git a/app/routes/admin.sports-seasons.$id.events.$eventId.helpers.ts b/app/routes/admin.sports-seasons.$id.events.$eventId.helpers.ts new file mode 100644 index 0000000..1f449ca --- /dev/null +++ b/app/routes/admin.sports-seasons.$id.events.$eventId.helpers.ts @@ -0,0 +1,10 @@ +/** + * Filter incoming batch results to only those whose participantId + * is not already in the set of existing result participantIds. + */ +export function filterNewResults( + incoming: { participantId: string; placement: number }[], + existingParticipantIds: Set +): { participantId: string; placement: number }[] { + return incoming.filter((r) => !existingParticipantIds.has(r.participantId)); +} diff --git a/app/routes/admin.sports-seasons.$id.events.$eventId.server.ts b/app/routes/admin.sports-seasons.$id.events.$eventId.server.ts index 8dfc594..ca55891 100644 --- a/app/routes/admin.sports-seasons.$id.events.$eventId.server.ts +++ b/app/routes/admin.sports-seasons.$id.events.$eventId.server.ts @@ -10,11 +10,13 @@ import { import { getEventResults, createEventResult, + createEventResultsBulk, updateEventResult, deleteEventResult, type CreateEventResultData, type UpdateEventResultData, } from "~/models/event-result"; +import { filterNewResults } from "./admin.sports-seasons.$id.events.$eventId.helpers"; import { findParticipantResultsBySportsSeasonId } from "~/models/participant-result"; import { upsertParticipantSeasonResult, @@ -323,5 +325,46 @@ export async function action({ request, params }: Route.ActionArgs) { } } + if (intent === "batch-add-results") { + const resultsJson = formData.get("results"); + if (typeof resultsJson !== "string" || !resultsJson) { + return { error: "Results data is required" }; + } + + let incoming: { participantId: string; placement: number }[]; + try { + incoming = JSON.parse(resultsJson); + } catch { + return { error: "Invalid results format" }; + } + + if (!Array.isArray(incoming) || incoming.length === 0) { + return { error: "No results provided" }; + } + + try { + const existingResults = await getEventResults(params.eventId); + const existingIds = new Set(existingResults.map((r) => r.participantId)); + const newResults = filterNewResults(incoming, existingIds); + + if (newResults.length > 0) { + await createEventResultsBulk( + newResults.map((r) => ({ + scoringEventId: params.eventId, + participantId: r.participantId, + placement: r.placement, + })) + ); + } + + return { + success: `${newResults.length} result${newResults.length === 1 ? "" : "s"} saved${incoming.length - newResults.length > 0 ? ` (${incoming.length - newResults.length} duplicate${incoming.length - newResults.length === 1 ? "" : "s"} skipped)` : ""}.`, + }; + } catch (error) { + logger.error("Error saving batch results:", error); + return { error: "Failed to save results" }; + } + } + return { error: "Invalid action" }; }