fix: address final review issues (sportsSeasonId authority, participant validation, CRLF, unused prop)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
057f935c88
commit
57cc66ca36
5 changed files with 22 additions and 11 deletions
|
|
@ -44,14 +44,12 @@ type Stage = "idle" | "preview" | "saving";
|
||||||
|
|
||||||
interface BatchResultEntryProps {
|
interface BatchResultEntryProps {
|
||||||
participants: Participant[];
|
participants: Participant[];
|
||||||
eventId: string;
|
|
||||||
sportsSeasonId: string;
|
sportsSeasonId: string;
|
||||||
existingResultParticipantIds: Set<string>;
|
existingResultParticipantIds: Set<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BatchResultEntry({
|
export function BatchResultEntry({
|
||||||
participants,
|
participants,
|
||||||
eventId: _eventId,
|
|
||||||
sportsSeasonId,
|
sportsSeasonId,
|
||||||
existingResultParticipantIds,
|
existingResultParticipantIds,
|
||||||
}: BatchResultEntryProps) {
|
}: BatchResultEntryProps) {
|
||||||
|
|
@ -165,7 +163,7 @@ export function BatchResultEntry({
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleConfirm() {
|
function handleConfirm() {
|
||||||
if (!allResolved) return;
|
if (!allResolved || rows.length === 0) return;
|
||||||
setStage("saving");
|
setStage("saving");
|
||||||
const results = rows
|
const results = rows
|
||||||
.filter((r) => r.participantId !== null)
|
.filter((r) => r.participantId !== null)
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,14 @@ describe("parseResultsText", () => {
|
||||||
{ placement: 1, rawName: "Gerwyn Price" },
|
{ placement: 1, rawName: "Gerwyn Price" },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("handles \\r\\n line endings", () => {
|
||||||
|
const input = "1. Gerwyn Price\r\n2. Luke Littler";
|
||||||
|
expect(parseResultsText(input)).toEqual([
|
||||||
|
{ placement: 1, rawName: "Gerwyn Price" },
|
||||||
|
{ placement: 2, rawName: "Luke Littler" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("multi-line mixed formats", () => {
|
describe("multi-line mixed formats", () => {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export interface ParsedLine {
|
||||||
export function parseResultsText(text: string): ParsedLine[] {
|
export function parseResultsText(text: string): ParsedLine[] {
|
||||||
const results: ParsedLine[] = [];
|
const results: ParsedLine[] = [];
|
||||||
|
|
||||||
for (const rawLine of text.split("\n")) {
|
for (const rawLine of text.split(/\r?\n/)) {
|
||||||
const line = rawLine.trim();
|
const line = rawLine.trim();
|
||||||
if (!line) continue;
|
if (!line) continue;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -363,6 +363,14 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
return { error: "No results provided" };
|
return { error: "No results provided" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate that all participant IDs belong to this sports season
|
||||||
|
const allSeasonParticipants = await findParticipantsBySportsSeasonId(params.id);
|
||||||
|
const validParticipantIds = new Set(allSeasonParticipants.map((p) => p.id));
|
||||||
|
const invalidEntries = incoming.filter((r) => !validParticipantIds.has(r.participantId));
|
||||||
|
if (invalidEntries.length > 0) {
|
||||||
|
return { error: "Some participant IDs do not belong to this sports season" };
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const existingResults = await getEventResults(params.eventId);
|
const existingResults = await getEventResults(params.eventId);
|
||||||
const existingIds = new Set(existingResults.map((r) => r.participantId));
|
const existingIds = new Set(existingResults.map((r) => r.participantId));
|
||||||
|
|
@ -389,20 +397,15 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
|
|
||||||
if (intent === "create-participant") {
|
if (intent === "create-participant") {
|
||||||
const name = formData.get("name");
|
const name = formData.get("name");
|
||||||
const sportsSeasonId = formData.get("sportsSeasonId");
|
|
||||||
|
|
||||||
if (typeof name !== "string" || !name.trim()) {
|
if (typeof name !== "string" || !name.trim()) {
|
||||||
return { error: "Participant name is required" };
|
return { error: "Participant name is required" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof sportsSeasonId !== "string" || !sportsSeasonId.trim()) {
|
|
||||||
return { error: "Sports season ID is required" };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const participant = await createParticipant({
|
const participant = await createParticipant({
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
sportsSeasonId,
|
sportsSeasonId: params.id,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
success: `Participant "${participant.name}" created.`,
|
success: `Participant "${participant.name}" created.`,
|
||||||
|
|
@ -410,6 +413,9 @@ export async function action({ request, params }: Route.ActionArgs) {
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error creating participant:", error);
|
logger.error("Error creating participant:", error);
|
||||||
|
if (error instanceof Error && error.message.includes("participants_sports_season_name_unique")) {
|
||||||
|
return { error: `A participant named "${name.trim()}" already exists in this sports season.` };
|
||||||
|
}
|
||||||
return { error: "Failed to create participant" };
|
return { error: "Failed to create participant" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -417,7 +417,6 @@ export default function EventResults({
|
||||||
{event.isQualifyingEvent && event.eventType !== "final_standings" && event.eventType !== "playoff_game" && !event.isComplete && (
|
{event.isQualifyingEvent && event.eventType !== "final_standings" && event.eventType !== "playoff_game" && !event.isComplete && (
|
||||||
<BatchResultEntry
|
<BatchResultEntry
|
||||||
participants={participants}
|
participants={participants}
|
||||||
eventId={event.id}
|
|
||||||
sportsSeasonId={sportsSeason.id}
|
sportsSeasonId={sportsSeason.id}
|
||||||
existingResultParticipantIds={
|
existingResultParticipantIds={
|
||||||
new Set(results.map((r: { participant: { id: string } }) => r.participant.id))
|
new Set(results.map((r: { participant: { id: string } }) => r.participant.id))
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue