diff --git a/src/pages/Competition/Schedule/PeopleList.tsx b/src/components/PeopleList/PeopleList.tsx similarity index 100% rename from src/pages/Competition/Schedule/PeopleList.tsx rename to src/components/PeopleList/PeopleList.tsx diff --git a/src/components/PeopleList/index.ts b/src/components/PeopleList/index.ts new file mode 100644 index 0000000..487eb57 --- /dev/null +++ b/src/components/PeopleList/index.ts @@ -0,0 +1 @@ +export * from './PeopleList'; diff --git a/src/components/index.ts b/src/components/index.ts index 87e3260..b333df8 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -15,3 +15,4 @@ export * from './LastFetchedAt'; export * from './LinkButton'; export * from './Notebox'; export * from './PinCompetitionButton'; +export * from './PeopleList'; diff --git a/src/containers/Schedule/Activity.tsx b/src/containers/Schedule/Activity.tsx new file mode 100644 index 0000000..34b6633 --- /dev/null +++ b/src/containers/Schedule/Activity.tsx @@ -0,0 +1,69 @@ +import { Activity, activityCodeToName } from '@wca/helpers'; +import { useMemo } from 'react'; +import { useParams } from 'react-router-dom'; +import { Container } from '@/components/Container'; +import { getAllActivities } from '@/lib/activities'; +import { parseActivityCodeFlexible } from '@/lib/activityCodes'; +import { useWCIF } from '@/providers/WCIFProvider'; +import { EventActivity } from './EventActivity'; +import { OtherActivity } from './OtherActivity'; + +export function CompetitionActivity() { + const { wcif } = useWCIF(); + const { activityId } = useParams(); + + const activity = useMemo( + () => + wcif && getAllActivities(wcif).find((a) => activityId && a.id === parseInt(activityId, 10)), + [wcif, activityId], + ); + + const everyoneInActivity = useMemo( + () => + wcif + ? wcif.persons + .map((person) => ({ + ...person, + assignments: person.assignments?.filter( + (a) => activityId && a.activityId === parseInt(activityId, 10), // TODO this is a hack because types aren't fixed yet for @wca/helpers + ), + })) + .filter(({ assignments }) => assignments && assignments.length > 0) + : [], + [wcif, activityId], + ); + + if (!activity) { + return ( + +

Activity not found

+
+ ); + } + + const { eventId } = parseActivityCodeFlexible(activity.activityCode); + + const isEventGroup = !eventId?.startsWith('other'); + const GroupComponent = isEventGroup ? EventActivity : OtherActivity; + + return ( + + {wcif?.id && activity && everyoneInActivity && ( + + )} + + ); +} + +export const niceActivityName = (activty: Activity) => { + if (activty.activityCode.startsWith('other')) { + return activty.name; + } else { + try { + return activityCodeToName(activty.activityCode); + } catch (e) { + console.error(e); + return activty.name; + } + } +}; diff --git a/src/pages/Competition/Schedule/EventActivity.tsx b/src/containers/Schedule/EventActivity.tsx similarity index 99% rename from src/pages/Competition/Schedule/EventActivity.tsx rename to src/containers/Schedule/EventActivity.tsx index 0e49fa1..520f167 100644 --- a/src/pages/Competition/Schedule/EventActivity.tsx +++ b/src/containers/Schedule/EventActivity.tsx @@ -8,12 +8,12 @@ import { import { useCallback, useEffect, useMemo } from 'react'; import { Link } from 'react-router-dom'; import { CutoffTimeLimitPanel } from '@/components/CutoffTimeLimitPanel'; +import { PeopleList } from '@/components/PeopleList'; import { getRooms } from '@/lib/activities'; import { isRankedBySingle } from '@/lib/events'; import { renderResultByEventId } from '@/lib/results'; import { formatDateTimeRange } from '@/lib/time'; import { useWCIF } from '@/providers/WCIFProvider'; -import { PeopleList } from './PeopleList'; const isAssignment = (assignment) => (a) => a.assignments.some(({ assignmentCode }) => assignmentCode === assignment); diff --git a/src/pages/Competition/Schedule/OtherActivity.tsx b/src/containers/Schedule/OtherActivity.tsx similarity index 97% rename from src/pages/Competition/Schedule/OtherActivity.tsx rename to src/containers/Schedule/OtherActivity.tsx index 4e84c12..c2c43ce 100644 --- a/src/pages/Competition/Schedule/OtherActivity.tsx +++ b/src/containers/Schedule/OtherActivity.tsx @@ -1,10 +1,10 @@ import { Activity, AssignmentCode, Person } from '@wca/helpers'; import { useEffect, useMemo } from 'react'; import { Link } from 'react-router-dom'; +import { PeopleList } from '@/components/PeopleList'; import { getRooms } from '@/lib/activities'; import { formatDateTimeRange } from '@/lib/time'; import { useWCIF } from '@/providers/WCIFProvider'; -import { PeopleList } from './PeopleList'; interface OtherGroupProps { competitionId: string; diff --git a/src/containers/Schedule/Room.tsx b/src/containers/Schedule/Room.tsx new file mode 100644 index 0000000..51336df --- /dev/null +++ b/src/containers/Schedule/Room.tsx @@ -0,0 +1,132 @@ +import { useCallback, useEffect, useMemo } from 'react'; +import { Link, useParams } from 'react-router-dom'; +import { ActivityRow, Container } from '@/components'; +import { getAllChildActivities } from '@/lib/activities'; +import { formatToParts } from '@/lib/time'; +import { byDate } from '@/lib/utils'; +import { useWCIF } from '@/providers/WCIFProvider'; + +export function CompetitionRoom() { + const { wcif, setTitle } = useWCIF(); + const { roomId } = useParams(); + + useEffect(() => { + setTitle('Schedule'); + }, [setTitle]); + + const venue = wcif?.schedule?.venues?.find((venue) => + venue.rooms.some((room) => room.id.toString() === roomId), + ); + const room = venue?.rooms?.find((room) => room.id.toString() === roomId); + + const timeZone = venue?.timezone ?? wcif?.schedule.venues?.[0]?.timezone ?? ''; + + const activities = useMemo( + () => + room?.activities + .flatMap((activity) => + activity?.childActivities?.length ? getAllChildActivities(activity) : activity, + ) + .sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime()) || [], + [room?.activities], + ); + + const scheduleDays = activities + .map((activity) => { + const venue = + wcif?.schedule.venues?.find((v) => + v.rooms.some((r) => + r.activities.some( + (a) => a.id === activity.id || a.childActivities?.some((ca) => ca.id === activity.id), + ), + ), + ) || wcif?.schedule.venues?.[0]; + + const dateTime = new Date(activity.startTime); + + return { + approxDateTime: dateTime.getTime(), + date: dateTime.toLocaleDateString([], { + weekday: 'long', + year: 'numeric', + month: 'numeric', + day: 'numeric', + timeZone: venue?.timezone, + }), + dateParts: formatToParts(dateTime), + }; + }) + .filter((v, i, arr) => arr.findIndex(({ date }) => date === v.date) === i) + .sort((a, b) => a.approxDateTime - b.approxDateTime); + + const activitiesWithParsedDate = activities + .map((activity) => { + const venue = + wcif?.schedule.venues?.find((v) => + v.rooms.some((r) => + r.activities.some( + (a) => a.id === activity.id || a.childActivities?.some((ca) => ca.id === activity.id), + ), + ), + ) || wcif?.schedule.venues?.[0]; + + const dateTime = new Date(activity.startTime); + + return { + ...activity, + date: dateTime.toLocaleDateString([], { + weekday: 'long', + year: 'numeric', + month: 'numeric', + day: 'numeric', + timeZone: venue?.timezone, + }), + }; + }) + .sort((a, b) => byDate(a, b)); + + const getActivitiesByDate = useCallback( + (date) => { + return activitiesWithParsedDate.filter((a) => a.date === date); + }, + [activitiesWithParsedDate], + ); + + return ( + +
+
+

{room?.name}

+ {venue?.name} +
+ + {scheduleDays.map((day) => ( +
+

{day.date}

+
+ {getActivitiesByDate(day.date).map((activity) => { + return ( + + ); + })} +
+
+ ))} +
+
+ + Back to list of Rooms + +
+
+
+ ); +} diff --git a/src/containers/Schedule/Rooms.tsx b/src/containers/Schedule/Rooms.tsx new file mode 100644 index 0000000..f59d619 --- /dev/null +++ b/src/containers/Schedule/Rooms.tsx @@ -0,0 +1,40 @@ +import { useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { Container } from '@/components/Container'; +import { useWCIF } from '@/providers/WCIFProvider'; + +export function CompetitionRooms() { + const { wcif, setTitle } = useWCIF(); + + useEffect(() => { + setTitle('Rooms'); + }, [setTitle]); + + return ( + +
+ {wcif?.schedule?.venues?.map((venue) => ( +
+

+ {venue.name} ({venue.timezone}) +

+
+
+ {venue.rooms.map((room) => ( + +
+

{room.name}

+
+ + ))} +
+
+ ))} +
+
+ ); +} diff --git a/src/containers/Schedule/index.ts b/src/containers/Schedule/index.ts index 66881c6..6b6a374 100644 --- a/src/containers/Schedule/index.ts +++ b/src/containers/Schedule/index.ts @@ -1 +1,6 @@ export * from './Schedule'; +export * from './Activity'; +export * from './Room'; +export * from './Rooms'; +export * from './EventActivity'; +export * from './OtherActivity'; diff --git a/src/pages/Competition/Schedule/Activity.tsx b/src/pages/Competition/Schedule/Activity.tsx index 34b6633..91a9f8f 100644 --- a/src/pages/Competition/Schedule/Activity.tsx +++ b/src/pages/Competition/Schedule/Activity.tsx @@ -1,69 +1 @@ -import { Activity, activityCodeToName } from '@wca/helpers'; -import { useMemo } from 'react'; -import { useParams } from 'react-router-dom'; -import { Container } from '@/components/Container'; -import { getAllActivities } from '@/lib/activities'; -import { parseActivityCodeFlexible } from '@/lib/activityCodes'; -import { useWCIF } from '@/providers/WCIFProvider'; -import { EventActivity } from './EventActivity'; -import { OtherActivity } from './OtherActivity'; - -export function CompetitionActivity() { - const { wcif } = useWCIF(); - const { activityId } = useParams(); - - const activity = useMemo( - () => - wcif && getAllActivities(wcif).find((a) => activityId && a.id === parseInt(activityId, 10)), - [wcif, activityId], - ); - - const everyoneInActivity = useMemo( - () => - wcif - ? wcif.persons - .map((person) => ({ - ...person, - assignments: person.assignments?.filter( - (a) => activityId && a.activityId === parseInt(activityId, 10), // TODO this is a hack because types aren't fixed yet for @wca/helpers - ), - })) - .filter(({ assignments }) => assignments && assignments.length > 0) - : [], - [wcif, activityId], - ); - - if (!activity) { - return ( - -

Activity not found

-
- ); - } - - const { eventId } = parseActivityCodeFlexible(activity.activityCode); - - const isEventGroup = !eventId?.startsWith('other'); - const GroupComponent = isEventGroup ? EventActivity : OtherActivity; - - return ( - - {wcif?.id && activity && everyoneInActivity && ( - - )} - - ); -} - -export const niceActivityName = (activty: Activity) => { - if (activty.activityCode.startsWith('other')) { - return activty.name; - } else { - try { - return activityCodeToName(activty.activityCode); - } catch (e) { - console.error(e); - return activty.name; - } - } -}; +export { CompetitionActivity } from '@/containers/Schedule'; diff --git a/src/pages/Competition/Schedule/Room.tsx b/src/pages/Competition/Schedule/Room.tsx index 51336df..460231f 100644 --- a/src/pages/Competition/Schedule/Room.tsx +++ b/src/pages/Competition/Schedule/Room.tsx @@ -1,132 +1 @@ -import { useCallback, useEffect, useMemo } from 'react'; -import { Link, useParams } from 'react-router-dom'; -import { ActivityRow, Container } from '@/components'; -import { getAllChildActivities } from '@/lib/activities'; -import { formatToParts } from '@/lib/time'; -import { byDate } from '@/lib/utils'; -import { useWCIF } from '@/providers/WCIFProvider'; - -export function CompetitionRoom() { - const { wcif, setTitle } = useWCIF(); - const { roomId } = useParams(); - - useEffect(() => { - setTitle('Schedule'); - }, [setTitle]); - - const venue = wcif?.schedule?.venues?.find((venue) => - venue.rooms.some((room) => room.id.toString() === roomId), - ); - const room = venue?.rooms?.find((room) => room.id.toString() === roomId); - - const timeZone = venue?.timezone ?? wcif?.schedule.venues?.[0]?.timezone ?? ''; - - const activities = useMemo( - () => - room?.activities - .flatMap((activity) => - activity?.childActivities?.length ? getAllChildActivities(activity) : activity, - ) - .sort((a, b) => new Date(a.startTime).getTime() - new Date(b.startTime).getTime()) || [], - [room?.activities], - ); - - const scheduleDays = activities - .map((activity) => { - const venue = - wcif?.schedule.venues?.find((v) => - v.rooms.some((r) => - r.activities.some( - (a) => a.id === activity.id || a.childActivities?.some((ca) => ca.id === activity.id), - ), - ), - ) || wcif?.schedule.venues?.[0]; - - const dateTime = new Date(activity.startTime); - - return { - approxDateTime: dateTime.getTime(), - date: dateTime.toLocaleDateString([], { - weekday: 'long', - year: 'numeric', - month: 'numeric', - day: 'numeric', - timeZone: venue?.timezone, - }), - dateParts: formatToParts(dateTime), - }; - }) - .filter((v, i, arr) => arr.findIndex(({ date }) => date === v.date) === i) - .sort((a, b) => a.approxDateTime - b.approxDateTime); - - const activitiesWithParsedDate = activities - .map((activity) => { - const venue = - wcif?.schedule.venues?.find((v) => - v.rooms.some((r) => - r.activities.some( - (a) => a.id === activity.id || a.childActivities?.some((ca) => ca.id === activity.id), - ), - ), - ) || wcif?.schedule.venues?.[0]; - - const dateTime = new Date(activity.startTime); - - return { - ...activity, - date: dateTime.toLocaleDateString([], { - weekday: 'long', - year: 'numeric', - month: 'numeric', - day: 'numeric', - timeZone: venue?.timezone, - }), - }; - }) - .sort((a, b) => byDate(a, b)); - - const getActivitiesByDate = useCallback( - (date) => { - return activitiesWithParsedDate.filter((a) => a.date === date); - }, - [activitiesWithParsedDate], - ); - - return ( - -
-
-

{room?.name}

- {venue?.name} -
- - {scheduleDays.map((day) => ( -
-

{day.date}

-
- {getActivitiesByDate(day.date).map((activity) => { - return ( - - ); - })} -
-
- ))} -
-
- - Back to list of Rooms - -
-
-
- ); -} +export { CompetitionRoom } from '@/containers/Schedule'; diff --git a/src/pages/Competition/Schedule/Rooms.tsx b/src/pages/Competition/Schedule/Rooms.tsx index f59d619..70c6e79 100644 --- a/src/pages/Competition/Schedule/Rooms.tsx +++ b/src/pages/Competition/Schedule/Rooms.tsx @@ -1,40 +1 @@ -import { useEffect } from 'react'; -import { Link } from 'react-router-dom'; -import { Container } from '@/components/Container'; -import { useWCIF } from '@/providers/WCIFProvider'; - -export function CompetitionRooms() { - const { wcif, setTitle } = useWCIF(); - - useEffect(() => { - setTitle('Rooms'); - }, [setTitle]); - - return ( - -
- {wcif?.schedule?.venues?.map((venue) => ( -
-

- {venue.name} ({venue.timezone}) -

-
-
- {venue.rooms.map((room) => ( - -
-

{room.name}

-
- - ))} -
-
- ))} -
-
- ); -} +export { CompetitionRooms } from '@/containers/Schedule';