Skip to content
26 changes: 25 additions & 1 deletion client/src/components/admin/AdminRound/AdminRoundContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,23 @@ function setStoredBatchResults(roundId, results) {
}
}

/**
* Combine results and batchResults to produce a temporary results-like array
* that has all batchResults changes applied. This allows us to display a
* warning if a result is being entered which matches a batched result exactly.
*/
function combineResultsAndBatchResults(results, batchResults) {
const combinedResults = [...results];
batchResults.forEach((batchedResult) => {
const i = combinedResults.findIndex((res) => res.id === batchedResult.id);
combinedResults[i] = {
...combinedResults[i],
attempts: batchedResult.attempts,
};
});
return combinedResults;
}

function AdminRoundContent({ round, competitionId, officialWorldRecords }) {
const confirm = useConfirm();
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
Expand All @@ -91,6 +108,9 @@ function AdminRoundContent({ round, competitionId, officialWorldRecords }) {
const [isBatchMode, setIsBatchMode] = useState(
() => batchResults.length > 0 || getStoreIsBatchMode(),
);
const [combinedResults, setCombinedResults] = useState(() =>
combineResultsAndBatchResults(round.results, batchResults),
);
const formContainerRef = useRef(null);

const [enterResults, { loading }] = useMutation(ENTER_RESULTS, {
Expand Down Expand Up @@ -184,7 +204,10 @@ function AdminRoundContent({ round, competitionId, officialWorldRecords }) {

useEffect(() => {
setStoredBatchResults(round.id, batchResults);
}, [round.id, batchResults]);
setCombinedResults(
combineResultsAndBatchResults(round.results, batchResults),
);
}, [round, batchResults]);

return (
<>
Expand All @@ -193,6 +216,7 @@ function AdminRoundContent({ round, competitionId, officialWorldRecords }) {
<ResultAttemptsForm
result={editedResult}
results={round.results}
combinedResults={combinedResults}
onResultChange={setEditedResult}
eventId={round.competitionEvent.event.id}
format={round.format}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
function ResultAttemptsForm({
result,
results,
combinedResults, // combined results and batchResults array
onResultChange,
eventId,
format,
Expand Down Expand Up @@ -91,15 +92,23 @@ function ResultAttemptsForm({
const attempts = trimTrailingSkipped(attemptResults).map((result) => ({
result,
}));
onSubmit(attempts);
onSubmit(attempts, result.person);
});
}

function confirmSubmission() {
// We don't want to show a duplicate warning if the user is submitting
// already-entered results for the correct competitor. This is often used
// as a quick way to refresh data without refreshing the whole page.
const resultsToCheck = combinedResults.filter(
(res) => res.id !== result.id,
);

const submissionWarning = attemptResultsWarning(
attemptResults,
eventId,
officialWorldRecords,
resultsToCheck,
);

if (submissionWarning) {
Expand Down
38 changes: 38 additions & 0 deletions client/src/lib/attempt-result.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ export function attemptResultsWarning(
attemptResults,
eventId,
officialWorldRecords = [],
results = [],
) {
const skippedGapIndex =
trimTrailingSkipped(attemptResults).indexOf(SKIPPED_VALUE);
Expand Down Expand Up @@ -482,6 +483,22 @@ export function attemptResultsWarning(
};
}
}

// Check whether this result is a duplicate of existing results in the same round.
// Excludes FMC and all-DNF results since ties are common.
if (eventId !== "333fm" && completeAttempts.length > 0) {
const matches = findAllMatchingResults(attemptResults, results);
if (matches.length > 0) {
const matchesString = matches
.map((match) => `${match.person.name} (${match.person.id})`)
.join(", ");
return {
description: `The result you're trying to submit matches all results for
the following competitor${matches.length > 1 ? "s" : ""}: ${matchesString}.
Please check that the results are accurate.`,
};
}
}
}
return null;
}
Expand Down Expand Up @@ -545,3 +562,24 @@ function checkForDnsFollowedByValidResult(attemptResults) {
index > dnsIndex && attempt !== SKIPPED_VALUE && attempt !== DNS_VALUE,
);
}

/**
* Check whether an attempt matches an existing attempt exactly.
*/
function findAllMatchingResults(attemptResults, results) {
const filteredAttemptResults = trimTrailingSkipped(attemptResults);

const matches = results.filter((result) => {
if (result.attempts.length !== filteredAttemptResults.length) {
return false;
}
for (let i = 0; i < result.attempts.length; i++) {
if (result.attempts[i].result !== filteredAttemptResults[i]) {
return false;
}
}
return true;
});

return matches;
}
55 changes: 55 additions & 0 deletions client/src/lib/tests/attempt-result.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,61 @@ describe("attemptResultsWarning", () => {
attemptResultsWarning(attemptResults, "333fm", worldRecords),
).toEqual(null);
});

it("complains about exact duplicate results", () => {
const attemptResults = [500, 600, 700, 800, 900];
const existingResults = [
{
attempts: [
{ result: 500 },
{ result: 600 },
{ result: 700 },
{ result: 800 },
{ result: 900 },
],
person: { id: 2, name: "Person 2" },
},
];
expect(
attemptResultsWarning(attemptResults, "333", [], existingResults),
).toMatchObject({
description: `The result you're trying to submit matches all results for
the following competitor: Person 2 (2).
Please check that the results are accurate.`,
});
});

it("does not check for duplicates in FMC", () => {
const attemptResults = [25, 26];
const existingResults = [
{
attempts: [{ result: 25 }, { result: 26 }],
person: { id: 2, name: "Person 2" },
},
];
expect(
attemptResultsWarning(attemptResults, "333fm", [], existingResults),
).toEqual(null);
});

it("does not warn about all-DNF duplicates", () => {
const attemptResults = [-1, -1, -1, -1, -1];
const existingResults = [
{
attempts: [
{ result: -1 },
{ result: -1 },
{ result: -1 },
{ result: -1 },
{ result: -1 },
],
person: { id: 2, name: "Person 2" },
},
];
expect(
attemptResultsWarning(attemptResults, "333", [], existingResults),
).toEqual(null);
});
});

describe("applyTimeLimit", () => {
Expand Down