feat: add batch-add-results server intent with duplicate filtering
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a3c6e7ab09
commit
8723000cdd
3 changed files with 82 additions and 0 deletions
29
app/routes/__tests__/batch-results-helpers.test.ts
Normal file
29
app/routes/__tests__/batch-results-helpers.test.ts
Normal file
|
|
@ -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([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -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<string>
|
||||||
|
): { participantId: string; placement: number }[] {
|
||||||
|
return incoming.filter((r) => !existingParticipantIds.has(r.participantId));
|
||||||
|
}
|
||||||
|
|
@ -10,11 +10,13 @@ import {
|
||||||
import {
|
import {
|
||||||
getEventResults,
|
getEventResults,
|
||||||
createEventResult,
|
createEventResult,
|
||||||
|
createEventResultsBulk,
|
||||||
updateEventResult,
|
updateEventResult,
|
||||||
deleteEventResult,
|
deleteEventResult,
|
||||||
type CreateEventResultData,
|
type CreateEventResultData,
|
||||||
type UpdateEventResultData,
|
type UpdateEventResultData,
|
||||||
} from "~/models/event-result";
|
} from "~/models/event-result";
|
||||||
|
import { filterNewResults } from "./admin.sports-seasons.$id.events.$eventId.helpers";
|
||||||
import { findParticipantResultsBySportsSeasonId } from "~/models/participant-result";
|
import { findParticipantResultsBySportsSeasonId } from "~/models/participant-result";
|
||||||
import {
|
import {
|
||||||
upsertParticipantSeasonResult,
|
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" };
|
return { error: "Invalid action" };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue