From 0e68bd2037deeb549a30b316943d9f0f2b258d4c Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Wed, 15 Oct 2025 21:21:33 -0700 Subject: [PATCH] feat: add bulk participant creation and expected value field --- app/models/participant.ts | 1 + app/models/sports-season.ts | 6 + .../admin.sports-seasons.$id.participants.tsx | 119 +- app/routes/admin.sports-seasons.tsx | 15 +- app/utils/sports-data-sync.server.ts | 3 + database/schema.ts | 1 + drizzle/0014_lowly_gateway.sql | 1 + drizzle/meta/0014_snapshot.json | 1045 +++++++++++++++++ drizzle/meta/_journal.json | 7 + 9 files changed, 1171 insertions(+), 27 deletions(-) create mode 100644 drizzle/0014_lowly_gateway.sql create mode 100644 drizzle/meta/0014_snapshot.json diff --git a/app/models/participant.ts b/app/models/participant.ts index d008dfc..4970c95 100644 --- a/app/models/participant.ts +++ b/app/models/participant.ts @@ -96,6 +96,7 @@ export async function copyParticipantsFromSeason( name: p.name, shortName: p.shortName, externalId: p.externalId, + expectedValue: p.expectedValue, })) ); } diff --git a/app/models/sports-season.ts b/app/models/sports-season.ts index edbc55e..e0dbe77 100644 --- a/app/models/sports-season.ts +++ b/app/models/sports-season.ts @@ -80,6 +80,11 @@ export async function findAllSportsSeasons(): Promise { orderBy: (sportsSeasons, { desc, asc }) => [desc(sportsSeasons.year), asc(sportsSeasons.name)], with: { sport: true, + participants: { + columns: { + id: true, + }, + }, }, }); } @@ -128,6 +133,7 @@ export async function copyParticipantsFromPreviousSeason( name: p.name, shortName: p.shortName, externalId: p.externalId, + expectedValue: p.expectedValue, })) ); } diff --git a/app/routes/admin.sports-seasons.$id.participants.tsx b/app/routes/admin.sports-seasons.$id.participants.tsx index 944aa17..e5caeaa 100644 --- a/app/routes/admin.sports-seasons.$id.participants.tsx +++ b/app/routes/admin.sports-seasons.$id.participants.tsx @@ -9,6 +9,7 @@ import { import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; import { Label } from "~/components/ui/label"; +import { Textarea } from "~/components/ui/textarea"; import { Card, CardContent, @@ -54,9 +55,43 @@ export async function action({ request, params }: Route.ActionArgs) { return { success: true }; } - // Add participant + // Bulk add participants + if (intent === "bulk") { + const bulkNames = formData.get("bulkNames"); + + if (typeof bulkNames !== "string" || !bulkNames.trim()) { + return { error: "Please enter at least one participant name" }; + } + + const names = bulkNames + .split("\n") + .map(name => name.trim()) + .filter(name => name.length > 0); + + if (names.length === 0) { + return { error: "Please enter at least one participant name" }; + } + + try { + for (const name of names) { + await createParticipant({ + sportsSeasonId: params.id, + name, + shortName: null, + externalId: null, + expectedValue: 0, + }); + } + + return { success: true, count: names.length }; + } catch (error) { + console.error("Error creating participants:", error); + return { error: "Failed to add participants. Please try again." }; + } + } + + // Add single participant const name = formData.get("name"); - const shortName = formData.get("shortName"); if (typeof name !== "string" || !name.trim()) { return { error: "Participant name is required" }; @@ -66,8 +101,9 @@ export async function action({ request, params }: Route.ActionArgs) { await createParticipant({ sportsSeasonId: params.id, name: name.trim(), - shortName: typeof shortName === "string" && shortName.trim() ? shortName.trim() : null, + shortName: null, externalId: null, + expectedValue: 0, }); return { success: true }; @@ -79,6 +115,9 @@ export async function action({ request, params }: Route.ActionArgs) { export default function ManageParticipants({ loaderData, actionData }: Route.ComponentProps) { const { sportsSeason, participants } = loaderData; + + // Use success state to reset forms by changing the key + const formKey = actionData?.success ? Date.now() : 'static'; return (
@@ -96,7 +135,7 @@ export default function ManageParticipants({ loaderData, actionData }: Route.Com

-
+
Add Participant @@ -105,11 +144,9 @@ export default function ManageParticipants({ loaderData, actionData }: Route.Com -
+
- +
-
- - -
- - {actionData?.error && ( + {actionData?.error && !actionData?.count && (
{actionData.error}
)} - {actionData?.success && ( + {actionData?.success && !actionData?.count && (
Participant added successfully!
@@ -150,6 +177,53 @@ export default function ManageParticipants({ loaderData, actionData }: Route.Com + + Bulk Add Participants + + Add multiple participants at once (one per line) + + + + + +
+ +