diff --git a/app/controllers/api/v0/competitions_controller.rb b/app/controllers/api/v0/competitions_controller.rb index d37b345614..4fa21ad8da 100644 --- a/app/controllers/api/v0/competitions_controller.rb +++ b/app/controllers/api/v0/competitions_controller.rb @@ -25,7 +25,7 @@ def competition_index competitions = competitions_scope.search(params[:q], params: params) - serial_methods = ["short_display_name", "city", "country_iso2", "event_ids", "date_range", "latitude_degrees", "longitude_degrees"] + serial_methods = ["short_display_name", "city", "country_iso2", "event_ids", "date_range", "latitude_degrees", "longitude_degrees", "cached_registration_status"] serial_includes = {} serial_includes["delegates"] = { only: ["id", "name"], methods: [], include: ["avatar"] } if admin_mode diff --git a/app/models/competition.rb b/app/models/competition.rb index 358ad74b69..1e95a247ea 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -1008,6 +1008,12 @@ def registration_status end end + def cached_registration_status + Rails.cache.fetch(["comp-registration-status", self.id], expires_in: 5.minutes) do + self.registration_status + end + end + def any_registrations? self.registrations.any? end diff --git a/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js b/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js index 9145b98a90..b2f8c8c85d 100644 --- a/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js +++ b/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js @@ -16,6 +16,7 @@ import UtcDatePicker from '../wca/UtcDatePicker'; function CompetitionsFilters({ filterState, dispatchFilter, + displayMode, shouldShowAdminDetails, canViewAdminDetails, }) { @@ -30,17 +31,17 @@ function CompetitionsFilters({ /> - - + + - + {shouldShowAdminDetails && ( - + )} @@ -49,12 +50,20 @@ function CompetitionsFilters({ + + + + {canViewAdminDetails && shouldShowAdminDetails && ( - - - - - + + + )} ); @@ -428,8 +437,6 @@ function CustomDateSelector({ filterState, dispatchFilter }) { export function CompDisplayCheckboxes({ shouldIncludeCancelled, dispatchFilter, - shouldShowRegStatus, - setShouldShowRegStatus, shouldShowAdminDetails, canViewAdminDetails, displayMode, @@ -449,32 +456,19 @@ export function CompDisplayCheckboxes({ { - displayMode === 'list' && ( - <> -
- setShouldShowRegStatus(!shouldShowRegStatus)} - /> -
- {canViewAdminDetails && ( -
- dispatchFilter( - { shouldShowAdminDetails: !shouldShowAdminDetails }, - )} - /> -
- )} - + displayMode === 'list' && canViewAdminDetails && ( +
+ dispatchFilter( + { shouldShowAdminDetails: !shouldShowAdminDetails }, + )} + /> +
) } diff --git a/app/webpacker/components/CompetitionsOverview/CompetitionsView.js b/app/webpacker/components/CompetitionsOverview/CompetitionsView.js index 859df26b0c..45d33caaf0 100644 --- a/app/webpacker/components/CompetitionsOverview/CompetitionsView.js +++ b/app/webpacker/components/CompetitionsOverview/CompetitionsView.js @@ -1,11 +1,10 @@ import React, { useEffect, useMemo, useReducer, useState, } from 'react'; -import { keepPreviousData, useInfiniteQuery, useQuery } from '@tanstack/react-query'; +import { useInfiniteQuery } from '@tanstack/react-query'; import { Button, Container, - Form, Header, Icon, Segment, @@ -16,7 +15,7 @@ import I18n from '../../lib/i18n'; import { apiV0Urls, WCA_API_PAGINATION } from '../../lib/requests/routes.js.erb'; import { fetchJsonOrError } from '../../lib/requests/fetchWithAuthenticityToken'; -import CompetitionsFilters, { CompDisplayCheckboxes, ToggleListOrMapDisplay } from './CompetitionsFilters'; +import CompetitionsFilters, { ToggleListOrMapDisplay } from './CompetitionsFilters'; import ListView from './ListView'; import MapView from './MapView'; import { @@ -44,7 +43,7 @@ function CompetitionsView({ canViewAdminDetails = false }) { ); const debouncedFilterState = useDebounce(filterState, DEBOUNCE_MS); const [displayMode, setDisplayMode] = useState(() => getDisplayMode(searchParams)); - const [shouldShowRegStatus, setShouldShowRegStatus] = useState(false); + const competitionQueryKey = useMemo( () => calculateQueryKey(debouncedFilterState, canViewAdminDetails), [debouncedFilterState, canViewAdminDetails], @@ -84,35 +83,7 @@ function CompetitionsView({ canViewAdminDetails = false }) { }, }); - const baseCompetitions = rawCompetitionData?.pages.flatMap((page) => page.data); - const compIds = baseCompetitions?.map((comp) => comp.id) || []; - - const { - data: compRegistrationData, - isFetching: regDataIsPending, - } = useQuery({ - queryFn: () => fetchJsonOrError(apiV0Urls.competitions.registrationData, { - headers: { - 'Content-Type': 'application/json', - }, - method: 'POST', - body: JSON.stringify({ ids: compIds }), - }), - queryKey: ['registration-info', ...compIds], - enabled: shouldShowRegStatus && compIds.length > 0, - // This is where the magic happens: Using `keepPreviousData` makes it so that - // all previously loaded indicators are held in-cache while the fetcher for the next - // batch is running in the background. (Adding comment here because it's not in the docs) - placeholderData: keepPreviousData, - select: (data) => data.data, - }); - - const competitions = useMemo(() => (shouldShowRegStatus ? ( - baseCompetitions?.map((comp) => { - const regData = compRegistrationData?.find((reg) => reg.id === comp.id); - return regData ? { ...comp, ...regData } : comp; - }) - ) : baseCompetitions), [baseCompetitions, compRegistrationData, shouldShowRegStatus]); + const competitions = rawCompetitionData?.pages.flatMap((page) => page.data); const [showFilters, setShowFilters] = useState(true); @@ -153,29 +124,15 @@ function CompetitionsView({ canViewAdminDetails = false }) { dispatchFilter={dispatchFilter} shouldShowAdminDetails={shouldShowAdminDetails} canViewAdminDetails={canViewAdminDetails} + displayMode={displayMode} /> -
- - - - - - -
+ { @@ -184,10 +141,8 @@ function CompetitionsView({ canViewAdminDetails = false }) { diff --git a/app/webpacker/components/CompetitionsOverview/ListView.js b/app/webpacker/components/CompetitionsOverview/ListView.js index 4bbd66e5a5..0eff01641f 100644 --- a/app/webpacker/components/CompetitionsOverview/ListView.js +++ b/app/webpacker/components/CompetitionsOverview/ListView.js @@ -11,10 +11,8 @@ import { isInProgress, isProbablyOver } from '../../lib/utils/competition-table' function ListView({ competitions, filterState, - shouldShowRegStatus, shouldShowAdminDetails, isLoading, - regStatusLoading, fetchMoreCompetitions, hasMoreCompsToLoad, }) { @@ -39,10 +37,8 @@ function ListView({ @@ -68,9 +64,7 @@ function ListView({ @@ -78,9 +72,7 @@ function ListView({ @@ -99,11 +91,9 @@ function ListView({ @@ -163,11 +149,9 @@ function ListView({ ) : ( @@ -59,8 +55,6 @@ function ListViewSection({ competitions={competitions} isLoading={isLoading} hasMoreCompsToLoad={hasMoreCompsToLoad} - shouldShowRegStatus={shouldShowRegStatus} - regStatusLoading={regStatusLoading} isSortedByAnnouncement={isSortedByAnnouncement} /> )} @@ -73,8 +67,6 @@ function ResponsiveCompetitionsTables({ competitions, isLoading, hasMoreCompsToLoad, - shouldShowRegStatus, - regStatusLoading, isSortedByAnnouncement, }) { const noCompetitions = !competitions || competitions.length === 0; @@ -91,8 +83,6 @@ function ResponsiveCompetitionsTables({ @@ -100,8 +90,6 @@ function ResponsiveCompetitionsTables({ @@ -109,8 +97,6 @@ function ResponsiveCompetitionsTables({ @@ -120,8 +106,6 @@ function ResponsiveCompetitionsTables({ export function CompetitionsTable({ competitions, - shouldShowRegStatus, - regStatusLoading, isSortedByAnnouncement = false, }) { return ( @@ -148,9 +132,7 @@ export function CompetitionsTable({ @@ -177,8 +159,6 @@ export function CompetitionsTable({ export function CompetitionsTabletTable({ competitions, - shouldShowRegStatus, - regStatusLoading, isSortedByAnnouncement = false, }) { return ( @@ -204,9 +184,7 @@ export function CompetitionsTabletTable({ @@ -233,8 +211,6 @@ export function CompetitionsTabletTable({ export function CompetitionsMobileTable({ competitions, - shouldShowRegStatus, - regStatusLoading, isSortedByAnnouncement = false, }) { return ( @@ -253,9 +229,7 @@ export function CompetitionsMobileTable({ @@ -288,9 +262,7 @@ function AdminCompetitionsTable({ competitions, isLoading, hasMoreCompsToLoad, - shouldShowRegStatus, selectedDelegate, - regStatusLoading, isSortedByAnnouncement, }) { const noCompetitions = !competitions || competitions.length === 0; @@ -333,9 +305,7 @@ function AdminCompetitionsTable({ @@ -437,15 +407,8 @@ function ConditionalYearHeader({ } } -function RegistrationStatus({ comp, isLoading }) { - // It is important that we check both conditions, because the query hook - // uses a `keepPreviousData` trick that holds existing data in-memory while - // also executing the query for the next batch of rows in the background. - if (isLoading && !comp.registration_status) { - return (); - } - - if (comp.registration_status === 'not_yet_opened') { +function RegistrationStatus({ comp }) { + if (comp.cached_registration_status === 'not_yet_opened') { return ( } @@ -455,7 +418,7 @@ function RegistrationStatus({ comp, isLoading }) { /> ); } - if (comp.registration_status === 'past') { + if (comp.cached_registration_status === 'past') { return ( } @@ -465,7 +428,7 @@ function RegistrationStatus({ comp, isLoading }) { /> ); } - if (comp.registration_status === 'full') { + if (comp.cached_registration_status === 'full') { return ( } @@ -475,7 +438,7 @@ function RegistrationStatus({ comp, isLoading }) { /> ); } - if (comp.registration_status === 'open') { + if (comp.cached_registration_status === 'open') { return ( } @@ -493,9 +456,7 @@ function RegistrationStatus({ comp, isLoading }) { function StatusIcon({ comp, - shouldShowRegStatus, isSortedByAnnouncement, - regStatusLoading, }) { let tooltipInfo = ''; let iconClass = ''; @@ -511,14 +472,11 @@ function StatusIcon({ } else if (isInProgress(comp)) { tooltipInfo = I18n.t('competitions.index.tooltips.hourglass.in_progress'); iconClass = 'hourglass half'; - } else if (shouldShowRegStatus) { - return ; } else if (isSortedByAnnouncement) { tooltipInfo = I18n.t('competitions.index.tooltips.hourglass.announced_on', { announcement_date: comp.announced_at }); iconClass = 'hourglass start'; } else { - tooltipInfo = I18n.t('competitions.index.tooltips.hourglass.starts_in', { days: I18n.t('common.days', { count: dayDifferenceFromToday(comp.start_date) }) }); - iconClass = 'hourglass start'; + return ; } return (