diff --git a/src/containers/CompetitionResults/CompetitionResults.test.tsx b/src/containers/CompetitionResults/CompetitionResults.test.tsx
index 2c7d1d0..38d35fb 100644
--- a/src/containers/CompetitionResults/CompetitionResults.test.tsx
+++ b/src/containers/CompetitionResults/CompetitionResults.test.tsx
@@ -43,8 +43,8 @@ jest.mock('react-i18next', () => ({
if (key === 'competition.results.average') return 'Avg';
if (key === 'competition.results.best') return 'Best';
if (key === 'competition.results.attempts') return 'Attempts';
- if (key === 'competition.results.liveResultsDelayNote') {
- return 'Data is pulled from WCA Live and may be delayed.';
+ if (key === 'competition.results.resultsSourceNote') {
+ return 'Results are merged from WCIF and WCA Live when available. WCA Live data may be delayed.';
}
if (key === 'competition.results.allResults') return 'All results';
if (key === 'competition.results.unknownCompetitor') {
@@ -273,7 +273,9 @@ describe('CompetitionResultsContainer', () => {
expect(screen.queryByRole('link', { name: 'Round 2' })).not.toBeInTheDocument();
expect(screen.queryByRole('table', { name: 'Results' })).not.toBeInTheDocument();
expect(
- screen.getByText('Data is pulled from WCA Live and may be delayed.'),
+ screen.getByText(
+ 'Results are merged from WCIF and WCA Live when available. WCA Live data may be delayed.',
+ ),
).toBeInTheDocument();
});
@@ -316,7 +318,9 @@ describe('CompetitionResultsContainer', () => {
expect(screen.getAllByText('12.00')).toHaveLength(2);
expect(screen.queryByLabelText('Attempts')).not.toBeInTheDocument();
expect(
- screen.getByText('Data is pulled from WCA Live and may be delayed.'),
+ screen.getByText(
+ 'Results are merged from WCIF and WCA Live when available. WCA Live data may be delayed.',
+ ),
).toBeInTheDocument();
expect(
screen
@@ -511,7 +515,9 @@ describe('CompetitionResultsContainer', () => {
within(screen.getByRole('row', { name: /2 Nick Silvestri/ })).getAllByText('15.00'),
).toHaveLength(2);
expect(
- screen.getByText('Data is pulled from WCA Live and may be delayed.'),
+ screen.getByText(
+ 'Results are merged from WCIF and WCA Live when available. WCA Live data may be delayed.',
+ ),
).toBeInTheDocument();
});
diff --git a/src/containers/CompetitionResults/CompetitionResults.tsx b/src/containers/CompetitionResults/CompetitionResults.tsx
index bd91ec8..27c8dcb 100644
--- a/src/containers/CompetitionResults/CompetitionResults.tsx
+++ b/src/containers/CompetitionResults/CompetitionResults.tsx
@@ -274,7 +274,7 @@ export function CompetitionResultsContainer({
roundNumber: selectedRound.roundNumber,
})}
-
+
-
+
diff --git a/src/containers/CompetitionResults/resultSources.ts b/src/containers/CompetitionResults/resultSources.ts
index 6e008dd..ac0510a 100644
--- a/src/containers/CompetitionResults/resultSources.ts
+++ b/src/containers/CompetitionResults/resultSources.ts
@@ -5,6 +5,16 @@ import { normalizeResultRecordTag } from './ResultRecordBadge';
const roundTypeOrder = ['0', '1', 'c', '2', 'e', '3', 'b', 'd', 'f'];
+const normalizeName = (value: string) => value.trim().toLowerCase();
+
+export const findUniquePersonByName = (persons: Person[], name: string) => {
+ const matchingPersons = persons.filter(
+ (person) => normalizeName(person.name) === normalizeName(name),
+ );
+
+ return matchingPersons.length === 1 ? matchingPersons[0] : undefined;
+};
+
const compareRoundTypeIds = (a: string, b: string) => {
const aIndex = roundTypeOrder.indexOf(a);
const bIndex = roundTypeOrder.indexOf(b);
@@ -24,12 +34,13 @@ const compareRoundTypeIds = (a: string, b: string) => {
return a.localeCompare(b);
};
-export const findPersonForApiResult = (persons: Person[], result: WcaCompetitionResult) =>
- persons.find(
- (person) =>
- (result.wca_id && person.wcaId === result.wca_id) ||
- person.name.toLocaleLowerCase() === result.name.toLocaleLowerCase(),
- );
+export const findPersonForApiResult = (persons: Person[], result: WcaCompetitionResult) => {
+ if (result.wca_id) {
+ return persons.find((person) => person.wcaId === result.wca_id);
+ }
+
+ return findUniquePersonByName(persons, result.name);
+};
export const getWcaApiRoundTypeMap = (results: WcaCompetitionResult[]) => {
const roundTypeIdsByEventId = new Map();
diff --git a/src/containers/CompetitionResults/resultsProvider.test.ts b/src/containers/CompetitionResults/resultsProvider.test.ts
index 6cfc85f..e7f465e 100644
--- a/src/containers/CompetitionResults/resultsProvider.test.ts
+++ b/src/containers/CompetitionResults/resultsProvider.test.ts
@@ -1,6 +1,7 @@
import { Competition, Person } from '@wca/helpers';
import { WcaLiveCompetitorResult, WcaLiveRound } from '@/hooks/queries/useWcaLive';
import { WcaCompetitionResult } from '@/lib/api';
+import { findPersonForApiResult } from './resultSources';
import { getPersonalResultsFromSources, getRoundResultsFromSources } from './resultsProvider';
jest.mock('@/lib/events', () => ({
@@ -186,6 +187,8 @@ const liveRoundResult = (
person: {
id: '2',
registrantId: 2,
+ wcaUserId: 1002,
+ wcaId: '2010BOBS01',
name: 'Bob Solver',
},
...overrides,
@@ -222,6 +225,39 @@ const livePersonalResult = (
...overrides,
});
+describe('findPersonForApiResult', () => {
+ const duplicateNamePerson: Person = {
+ ...persons[0],
+ registrantId: 4,
+ wcaUserId: 1004,
+ wcaId: '2010ALIC02',
+ };
+
+ it('matches an exact WCA ID before considering duplicate names', () => {
+ expect(
+ findPersonForApiResult(
+ [...persons, duplicateNamePerson],
+ apiResult({ name: 'Alice Solver', wca_id: '2010ALIC02' }),
+ ),
+ ).toBe(duplicateNamePerson);
+ });
+
+ it('does not fall back to a name when a WCA ID is present but unmatched', () => {
+ expect(
+ findPersonForApiResult(persons, apiResult({ name: 'Alice Solver', wca_id: '2099MISS01' })),
+ ).toBeUndefined();
+ });
+
+ it('does not use an ambiguous name when the API result has no WCA ID', () => {
+ expect(
+ findPersonForApiResult(
+ [...persons, duplicateNamePerson],
+ apiResult({ name: 'Alice Solver', wca_id: null }),
+ ),
+ ).toBeUndefined();
+ });
+});
+
describe('resultsProvider', () => {
describe('getRoundResultsFromSources', () => {
it('returns no round results without WCIF or a selected round', () => {
@@ -301,6 +337,8 @@ describe('resultsProvider', () => {
average: 2200,
round_type_id: '1',
attempts: [2100, 2200, 2300],
+ best_index: 2,
+ worst_index: 0,
regional_single_record: 'NR',
regional_average_record: 'WR',
}),
@@ -326,6 +364,12 @@ describe('resultsProvider', () => {
singleRecordTag: 'NR',
averageRecordTag: 'WR',
});
+ expect(results.find((result) => result.personId === 2)).not.toHaveProperty(
+ 'bestAttemptIndex',
+ );
+ expect(results.find((result) => result.personId === 2)).not.toHaveProperty(
+ 'worstAttemptIndex',
+ );
expect(results.find((result) => result.personId === 1)).toMatchObject({
ranking: 1,
best: 1200,
@@ -341,6 +385,98 @@ describe('resultsProvider', () => {
});
});
+ it('matches live round results by WCA user id when registrant id is missing', () => {
+ const results = getRoundResultsFromSources({
+ wcif,
+ selectedRound: selectedRound(0),
+ wcaApiResults: [],
+ wcaLiveRound: liveRound([
+ liveRoundResult({
+ id: 'live-alice',
+ ranking: 4,
+ best: 900,
+ average: 1000,
+ attempts: [{ result: 900 }, { result: 1000 }, { result: 1100 }],
+ person: {
+ id: 'missing-registrant-id',
+ registrantId: null,
+ wcaUserId: 1001,
+ wcaId: '2010ALIC01',
+ name: 'Alice Published Name',
+ },
+ }),
+ ]),
+ });
+
+ expect(results).toHaveLength(3);
+ expect(results.filter((result) => result.personId === 1)).toHaveLength(1);
+ expect(results.find((result) => result.personId === 1)).toMatchObject({
+ personName: 'Alice Published Name',
+ ranking: 4,
+ best: 900,
+ average: 1000,
+ attempts: [{ result: 900 }, { result: 1000 }, { result: 1100 }],
+ });
+ });
+
+ it('leaves a live result unlinked when its name is ambiguous and identifiers are missing', () => {
+ const duplicateNamePerson: Person = {
+ ...persons[0],
+ registrantId: 4,
+ wcaUserId: 1004,
+ wcaId: '2010ALIC02',
+ };
+ const duplicateNameWcif = {
+ ...wcif,
+ persons: [...persons, duplicateNamePerson],
+ } as Competition;
+ const results = getRoundResultsFromSources({
+ wcif: duplicateNameWcif,
+ selectedRound: selectedRound(0),
+ wcaApiResults: [],
+ wcaLiveRound: liveRound([
+ liveRoundResult({
+ id: 'live-ambiguous-alice',
+ person: {
+ id: 'missing-identifiers',
+ registrantId: null,
+ name: 'Alice Solver',
+ },
+ }),
+ ]),
+ });
+
+ expect(results.find((result) => result.id === 'live-ambiguous-alice')).toMatchObject({
+ personId: null,
+ personName: 'Alice Solver',
+ });
+ });
+
+ it('does not override an unmatched live identity with a name match', () => {
+ const results = getRoundResultsFromSources({
+ wcif,
+ selectedRound: selectedRound(0),
+ wcaApiResults: [],
+ wcaLiveRound: liveRound([
+ liveRoundResult({
+ id: 'live-conflicting-alice',
+ person: {
+ id: 'conflicting-identifiers',
+ registrantId: null,
+ wcaUserId: 9999,
+ wcaId: '2099MISS01',
+ name: 'Alice Solver',
+ },
+ }),
+ ]),
+ });
+
+ expect(results.find((result) => result.id === 'live-conflicting-alice')).toMatchObject({
+ personId: null,
+ personName: 'Alice Solver',
+ });
+ });
+
it('matches WCA API results by WCA ID before using the displayed name', () => {
const results = getRoundResultsFromSources({
wcif,
@@ -479,6 +615,23 @@ describe('resultsProvider', () => {
]);
});
+ it('does not include a same-name API result with a conflicting WCA ID', () => {
+ const results = getPersonalResultsFromSources({
+ wcif,
+ person: persons[0],
+ wcaApiResults: [
+ apiResult({
+ id: 9201,
+ name: 'Alice Solver',
+ wca_id: '2010BOBS01',
+ }),
+ ],
+ wcaLiveResults: [],
+ });
+
+ expect(results[0].rounds.map((round) => round.roundId)).toEqual(['333-r1']);
+ });
+
it('sorts live personal events and rounds before merging them', () => {
const secondRound = livePersonalResult({
id: 'live-personal-333-r2',
diff --git a/src/containers/CompetitionResults/resultsProvider.ts b/src/containers/CompetitionResults/resultsProvider.ts
index b475ff9..562e6d7 100644
--- a/src/containers/CompetitionResults/resultsProvider.ts
+++ b/src/containers/CompetitionResults/resultsProvider.ts
@@ -8,7 +8,12 @@ import { PersonalRoundResult } from '../CompetitionPersonalResults/CompetitionPe
import { CompetitionRoundResult } from './CompetitionResultsTable';
import { normalizeResultRecordTag } from './ResultRecordBadge';
import { getStoredRoundResults } from './advancement';
-import { getApiRoundResults, getWcaApiResultsByRoundId } from './resultSources';
+import {
+ findPersonForApiResult,
+ findUniquePersonByName,
+ getApiRoundResults,
+ getWcaApiResultsByRoundId,
+} from './resultSources';
import { getRoundRosterResults } from './roundRoster';
type SelectedRound = NonNullable>;
@@ -44,22 +49,66 @@ const mergeRoundResultSources = (
return [...resultsByKey.values()];
};
-const getLiveRoundResults = (wcaLiveRound: WcaLiveRound | null | undefined) =>
+const findPersonForLiveRoundResult = (
+ wcif: Competition,
+ result: WcaLiveRound['results'][number],
+): Person | undefined => {
+ if (result.person.registrantId != null) {
+ const matchedByRegistrantId = wcif.persons.find(
+ (person) => person.registrantId === result.person.registrantId,
+ );
+
+ if (matchedByRegistrantId) {
+ return matchedByRegistrantId;
+ }
+ }
+
+ if (result.person.wcaUserId != null) {
+ const matchedByWcaUserId = wcif.persons.find(
+ (person) => person.wcaUserId === result.person.wcaUserId,
+ );
+
+ if (matchedByWcaUserId) {
+ return matchedByWcaUserId;
+ }
+ }
+
+ if (result.person.wcaId) {
+ const matchedByWcaId = wcif.persons.find((person) => person.wcaId === result.person.wcaId);
+
+ if (matchedByWcaId) {
+ return matchedByWcaId;
+ }
+ }
+
+ const hasStrongIdentifier =
+ result.person.registrantId != null ||
+ result.person.wcaUserId != null ||
+ Boolean(result.person.wcaId);
+
+ return hasStrongIdentifier ? undefined : findUniquePersonByName(wcif.persons, result.person.name);
+};
+
+const getLiveRoundResults = (wcif: Competition, wcaLiveRound: WcaLiveRound | null | undefined) =>
wcaLiveRound?.results
.filter((result) => result.attempts.length > 0)
- .map((result) => ({
- id: result.id,
- personId: result.person.registrantId,
- personName: result.person.name,
- ranking: result.ranking,
- advancing: result.advancing,
- advancingQuestionable: result.advancingQuestionable,
- attempts: result.attempts,
- best: result.best,
- average: result.average,
- singleRecordTag: normalizeResultRecordTag(result.singleRecordTag),
- averageRecordTag: normalizeResultRecordTag(result.averageRecordTag),
- })) ?? [];
+ .map((result) => {
+ const matchedPerson = findPersonForLiveRoundResult(wcif, result);
+
+ return {
+ id: result.id,
+ personId: matchedPerson?.registrantId ?? result.person.registrantId,
+ personName: result.person.name,
+ ranking: result.ranking,
+ advancing: result.advancing,
+ advancingQuestionable: result.advancingQuestionable,
+ attempts: result.attempts,
+ best: result.best,
+ average: result.average,
+ singleRecordTag: normalizeResultRecordTag(result.singleRecordTag),
+ averageRecordTag: normalizeResultRecordTag(result.averageRecordTag),
+ };
+ }) ?? [];
export const getRoundResultsFromSources = ({
wcif,
@@ -76,7 +125,7 @@ export const getRoundResultsFromSources = ({
return [];
}
- const liveRoundResults = getLiveRoundResults(wcaLiveRound);
+ const liveRoundResults = getLiveRoundResults(wcif, wcaLiveRound);
const storedRoundResults = getStoredRoundResults(
selectedRound.round,
getAdvancementConditionForRound(selectedRound.event.rounds, selectedRound.round),
@@ -147,8 +196,12 @@ const getLivePersonalResults = (
return {
eventId,
- eventName: firstResult.round.competitionEvent.event.name,
- eventRank: firstResult.round.competitionEvent.event.rank,
+ eventName: wcifEvent
+ ? getEventName(eventId, wcifEvent)
+ : firstResult.round.competitionEvent.event.name,
+ eventRank: wcifEvent
+ ? wcif.events.indexOf(wcifEvent)
+ : firstResult.round.competitionEvent.event.rank,
rounds: eventResults
.sort((a, b) => a.round.number - b.round.number)
.map((result) => ({
@@ -175,9 +228,7 @@ const getApiPersonalResults = (
apiResults: WcaCompetitionResult[],
): EventResults[] => {
const personResults = apiResults.filter(
- (result) =>
- (person.wcaId && result.wca_id === person.wcaId) ||
- result.name.toLocaleLowerCase() === person.name.toLocaleLowerCase(),
+ (result) => findPersonForApiResult(wcif.persons, result)?.registrantId === person.registrantId,
);
const resultsByRoundId = getWcaApiResultsByRoundId(wcif, personResults);
@@ -223,11 +274,18 @@ const mergeEventResultSources = (...sources: EventResults[][]): EventResults[] =
);
sourceEvent.rounds.forEach((roundResult) => {
- roundsById.set(roundResult.roundId, roundResult);
+ const existingRoundResult = roundsById.get(roundResult.roundId);
+
+ roundsById.set(roundResult.roundId, {
+ ...existingRoundResult,
+ ...roundResult,
+ });
});
eventsById.set(sourceEvent.eventId, {
+ ...existingEvent,
...sourceEvent,
+ eventName: existingEvent?.eventName ?? sourceEvent.eventName,
eventRank: existingEvent?.eventRank ?? sourceEvent.eventRank,
rounds: [...roundsById.values()].sort((a, b) => a.roundNumber - b.roundNumber),
});
diff --git a/src/containers/PersonalSchedule/utils.test.ts b/src/containers/PersonalSchedule/utils.test.ts
new file mode 100644
index 0000000..50cbbbc
--- /dev/null
+++ b/src/containers/PersonalSchedule/utils.test.ts
@@ -0,0 +1,163 @@
+import { Competition, Person } from '@wca/helpers';
+import { parseActivityCodeFlexible } from '@/lib/activityCodes';
+import { formatBriefActivityName, getAllAssignments, getGroupedAssignmentsByDate } from './utils';
+
+jest.mock('@/i18n', () => ({
+ __esModule: true,
+ default: {
+ t: (key: string) => key,
+ },
+ t: (key: string) => key,
+}));
+
+const wcif = {
+ id: 'WC2025',
+ schedule: {
+ venues: [
+ {
+ id: 1,
+ name: 'Venue',
+ timezone: 'America/Los_Angeles',
+ rooms: [],
+ },
+ ],
+ },
+ events: [],
+} as unknown as Competition;
+
+const worldsAssignmentsPerson = {
+ registrantId: 215,
+ assignments: [],
+ registration: {
+ eventIds: [],
+ },
+ extensions: [
+ {
+ id: 'com.competitiongroups.worldsassignments',
+ data: {
+ assignments: [
+ {
+ staff: 'Stage Stream - Main',
+ startTime: '2025-07-04T01:00:00Z',
+ endTime: '2025-07-04T02:00:00Z',
+ },
+ ],
+ },
+ },
+ ],
+} as unknown as Person;
+
+const fmcWcif = {
+ ...wcif,
+ schedule: {
+ ...wcif.schedule,
+ venues: [
+ {
+ ...wcif.schedule.venues[0],
+ rooms: [
+ {
+ id: 1,
+ name: 'FMC Room',
+ color: '#ffffff',
+ extensions: [],
+ activities: [
+ {
+ id: 100,
+ name: 'Fewest Moves Attempt 1',
+ activityCode: '333fm-r1-a1',
+ startTime: '2025-07-03T18:00:00Z',
+ endTime: '2025-07-03T19:00:00Z',
+ extensions: [],
+ childActivities: [
+ {
+ id: 101,
+ name: 'Fewest Moves Attempt 1 Group 1',
+ activityCode: '333fm-r1-g1-a1',
+ startTime: '2025-07-03T18:00:00Z',
+ endTime: '2025-07-03T19:00:00Z',
+ extensions: [],
+ childActivities: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+} as unknown as Competition;
+
+const fmcPerson = {
+ registrantId: 216,
+ assignments: [
+ {
+ activityId: 101,
+ assignmentCode: 'competitor',
+ stationNumber: null,
+ },
+ ],
+ registration: {
+ eventIds: ['333fm'],
+ },
+ extensions: [],
+} as unknown as Person;
+
+describe('PersonalSchedule utils', () => {
+ it('creates parse-safe activities for worlds assignments with free-form staff names', () => {
+ const [assignment] = getAllAssignments(wcif, worldsAssignmentsPerson);
+
+ expect(assignment.activity).toBeDefined();
+ const activity = assignment.activity!;
+
+ expect(activity.activityCode).toBe('other-misc');
+ expect(() => parseActivityCodeFlexible(activity.activityCode)).not.toThrow();
+ expect(formatBriefActivityName(activity)).toBe('Stage Stream - Main');
+ });
+
+ it('includes days that only have worlds assignments', () => {
+ const [scheduleDay] = getGroupedAssignmentsByDate(wcif, worldsAssignmentsPerson);
+
+ expect(scheduleDay.date).toBe('Thursday, 7/3/2025');
+ expect(scheduleDay.assignments).toHaveLength(1);
+ expect(scheduleDay.assignments[0].assignment.assignmentCode).toBe('Stage Stream - Main');
+ });
+
+ it('keeps standard other activities in the other namespace', () => {
+ expect(parseActivityCodeFlexible('other-lunch-g2')).toMatchObject({
+ eventId: 'other-lunch',
+ roundNumber: 1,
+ groupNumber: 2,
+ attemptNumber: null,
+ });
+ expect(parseActivityCodeFlexible('other-misc')).toMatchObject({
+ eventId: 'other-misc',
+ });
+ });
+
+ it('only includes the FMC attempt activity assigned to the competitor', () => {
+ const fmcAssignments = getAllAssignments(fmcWcif, fmcPerson).filter(({ activity }) =>
+ activity?.activityCode.startsWith('333fm'),
+ );
+
+ expect(fmcAssignments).toHaveLength(1);
+ expect(fmcAssignments[0].activityId).toBe(101);
+ });
+
+ it('does not create FMC assignments without a matching activity assignment', () => {
+ const personWithoutFmcAssignment = {
+ ...fmcPerson,
+ assignments: [],
+ } as Person;
+
+ expect(getAllAssignments(fmcWcif, personWithoutFmcAssignment)).toHaveLength(0);
+ });
+
+ it('includes a day that only has an assigned FMC attempt', () => {
+ const [scheduleDay] = getGroupedAssignmentsByDate(fmcWcif, fmcPerson);
+
+ expect(scheduleDay.date).toBe('Thursday, 7/3/2025');
+ expect(scheduleDay.assignments).toHaveLength(1);
+ expect(scheduleDay.assignments[0].assignment.activityId).toBe(101);
+ });
+});
diff --git a/src/containers/PersonalSchedule/utils.ts b/src/containers/PersonalSchedule/utils.ts
index 99cf80d..43ad8ff 100644
--- a/src/containers/PersonalSchedule/utils.ts
+++ b/src/containers/PersonalSchedule/utils.ts
@@ -1,12 +1,27 @@
import { Activity, Assignment, Competition, Person } from '@wca/helpers';
import { getWorldAssignmentsExtension } from '@/extensions/com.competitiongroups.worldsassignments';
import i18n from '@/i18n';
-import { getAllActivities, getRooms } from '@/lib/activities';
+import { getAllActivities } from '@/lib/activities';
import { isUnofficialParsedActivityCode, parseActivityCodeFlexible } from '@/lib/activityCodes';
import { eventById, isOfficialEventId } from '@/lib/events';
import { formatNumericDate, getNumericDateFormatter } from '@/lib/time';
import { byDate } from '@/lib/utils';
+type ActivityWithLocation = Activity & {
+ room?: { id: number };
+ parent?: { room?: { id: number } };
+};
+
+const getActivityTimeZone = (
+ venues: Competition['schedule']['venues'],
+ activity: ActivityWithLocation,
+) => {
+ const roomId = activity.room?.id ?? activity.parent?.room?.id;
+ const venue = venues.find((candidate) => candidate.rooms.some((room) => room.id === roomId));
+
+ return venue?.timezone ?? venues[0]?.timezone;
+};
+
export const getNormalAssignments = (wcif: Competition, person: Person) => {
const allActivities = getAllActivities(wcif);
@@ -53,7 +68,7 @@ const getExtraAssignments = (person: Person) => {
activityId: -1,
stationNumber: null,
activity: {
- activityCode: 'other-' + assignment.staff,
+ activityCode: 'other-misc',
startTime: assignment.startTime,
endTime: assignment.endTime,
childActivities: [],
@@ -152,16 +167,6 @@ export const getGroupedAssignmentsByDate = (wcif: Competition, person: Person) =
const scheduledDays = allAssignments
.map((a) => {
- // Commenting out for NAC26, extra assignments are valid even if the day does not have any other assignments
- // if (a.type === 'extra') {
- // return {
- // approxDateTime: 0,
- // date: '',
- // dateParts: [],
- // assignments: [],
- // };
- // }
-
if (!a.activity) {
return {
approxDateTime: 0,
@@ -171,18 +176,14 @@ export const getGroupedAssignmentsByDate = (wcif: Competition, person: Person) =
};
}
- const roomId = a.activity && 'room' in a.activity && a.activity.room?.id;
- const parent = a.activity && 'parent' in a.activity && a.activity.parent;
-
- const venue = venues.find((v) => v.rooms.some((r) => r.id === roomId || parent));
-
const dateTime = new Date(a.activity?.startTime);
- const date = formatNumericDate(dateTime, venue?.timezone);
+ const timeZone = getActivityTimeZone(venues, a.activity);
+ const date = formatNumericDate(dateTime, timeZone);
return {
approxDateTime: dateTime.getTime(),
date: date,
- dateParts: getNumericDateFormatter(venue?.timezone).formatToParts(dateTime),
+ dateParts: getNumericDateFormatter(timeZone).formatToParts(dateTime),
};
})
.filter((v, i, arr) => arr.findIndex(({ date }) => date === v.date) === i);
@@ -200,7 +201,6 @@ export const getGroupedAssignmentsByDate = (wcif: Competition, person: Person) =
export const getAssignmentsWithParsedDate = (wcif: Competition, person: Person) => {
const allAssignments = getAllAssignments(wcif, person);
const venues = wcif.schedule.venues;
- const rooms = getRooms(wcif);
return allAssignments
.map((assignment) => {
@@ -214,7 +214,10 @@ export const getAssignmentsWithParsedDate = (wcif: Competition, person: Person)
return {
assignment,
activity,
- date: formatNumericDate(new Date(activity.startTime), venues[0].timezone),
+ date: formatNumericDate(
+ new Date(activity.startTime),
+ getActivityTimeZone(venues, activity),
+ ),
};
}
@@ -226,16 +229,12 @@ export const getAssignmentsWithParsedDate = (wcif: Competition, person: Person)
};
}
- const roomId = (activity.room || activity.parent?.room)?.id;
-
- const venue = activity?.room?.id ? rooms.find((r) => r.id === roomId)?.venue : venues[0];
-
const dateTime = new Date(activity.startTime);
return {
assignment,
activity,
- date: formatNumericDate(dateTime, venue?.timezone),
+ date: formatNumericDate(dateTime, getActivityTimeZone(venues, activity)),
};
}
})
diff --git a/src/hooks/queries/useWcaLive.ts b/src/hooks/queries/useWcaLive.ts
index 66e9013..d274189 100644
--- a/src/hooks/queries/useWcaLive.ts
+++ b/src/hooks/queries/useWcaLive.ts
@@ -121,6 +121,8 @@ export interface WcaLiveRoundResult {
person: {
id: string;
registrantId: number | null;
+ wcaUserId?: number | null;
+ wcaId?: string | null;
name: string;
};
}
@@ -219,6 +221,8 @@ const WCA_LIVE_ROUND_RESULTS_QUERY = `
person {
id
registrantId
+ wcaUserId
+ wcaId
name
}
}
diff --git a/src/i18n/en/translation.yaml b/src/i18n/en/translation.yaml
index 33b4f57..aae1be7 100644
--- a/src/i18n/en/translation.yaml
+++ b/src/i18n/en/translation.yaml
@@ -210,7 +210,7 @@ competition:
unknownCompetitor: 'Competitor #{{personId}}'
allResults: All results
viewLiveResults: View live results
- liveResultsDelayNote: Data is pulled from WCA Live and may be delayed.
+ resultsSourceNote: Results are merged from WCIF and WCA Live when available. WCA Live data may be delayed.
personalResults:
eventResults: Event results
round: