From bb687c0e834fefd35bbdbea047c41133c54e9dc0 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 14 Jan 2025 15:49:19 +0100 Subject: [PATCH 01/70] WIP transform rankings page to records page --- app/controllers/results_controller.rb | 2 + app/views/results/records.html.erb | 59 +------- .../Results/Records/RecordsTable.jsx | 136 ++++++++++++++++++ .../components/Results/Records/index.jsx | 118 +++++++++++++++ .../components/Results/api/records.js | 8 ++ .../components/Results/resultsFilter.jsx | 95 ++++++++++++ app/webpacker/lib/requests/routes.js.erb | 28 +++- 7 files changed, 388 insertions(+), 58 deletions(-) create mode 100644 app/webpacker/components/Results/Records/RecordsTable.jsx create mode 100644 app/webpacker/components/Results/Records/index.jsx create mode 100644 app/webpacker/components/Results/api/records.js create mode 100644 app/webpacker/components/Results/resultsFilter.jsx diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index 48d5f85fcf8..db2a8ef0436 100644 --- a/app/controllers/results_controller.rb +++ b/app/controllers/results_controller.rb @@ -241,6 +241,8 @@ def records `rank`, type DESC, start_date, roundTypeId, personName SQL end + + @ranking_timestamp = ComputeAuxiliaryData.successful_start_date || Date.current end private def current_records_query(value, type) diff --git a/app/views/results/records.html.erb b/app/views/results/records.html.erb index c2600cb4eae..0b23e1ed6c0 100644 --- a/app/views/results/records.html.erb +++ b/app/views/results/records.html.erb @@ -1,64 +1,9 @@ <% provide(:title, t(".title")) %> -<% record_timestamp = ComputeAuxiliaryData.successful_start_date || Date.current %> -<% @rows = DbHelper.execute_cached_query(@cache_params, record_timestamp, @query) %> -

<%= yield(:title) %>

-

<%= t("results.last_updated_html", timestamp: wca_local_time(record_timestamp)) %>

+

<%= t("results.last_updated_html", timestamp: wca_local_time(@ranking_timestamp)) %>

<%= t('results.filters_fixes_underway') %>

-
- <%= render 'results_selector', show_records_options: true %> -
- - <% cache [*@cache_params, record_timestamp, I18n.locale] do %> - <% - comp_ids = @rows.map { |r| r["competitionId"] }.uniq - unless @is_slim - @competitions_by_id = Hash[Competition.where(id: comp_ids).map { |c| [c.id, c] }] - end - %> - -
-
- <% if @is_mixed %> - <% @rows.group_by { |row| row["eventId"] }.each do |event_id, rows| %> - <% event = Event.c_find(event_id) %> -

- <%= link_to rankings_path(event.id, "single"), class: "plain" do %> - <%= cubing_icon event.id %> - <%= event.name %> - <% end %> -

- <%= render 'records_mixed_table', rows: rows %> - <% end %> - <% elsif @is_slim || @is_separate %> - <% slim_rows, single_rows, average_rows = compute_slim_or_separate_records(@rows) %> - <% if @is_slim %> - <%= render 'records_slim_table', rows: slim_rows %> - <% else %> -

<%= t("results.table_elements.type_options.single") %>

- <%= render 'records_separate_table', rows: single_rows, type: "single" %> - -

<%= t("results.table_elements.type_options.average") %>

- <%= render 'records_separate_table', rows: average_rows, type: "average" %> - <% end %> - <% elsif @is_history %> - <% @rows.group_by { |row| row["eventId"] }.each do |event_id, rows| %> - <% event = Event.c_find(event_id) %> -

- <%= link_to rankings_path(event.id, "single"), class: "plain" do %> - <%= cubing_icon event.id %> - <%= event.name %> - <% end %> -

- <%= render 'records_histories_table', rows: rows %> - <% end %> - <% elsif @is_mixed_history %> - <%= render 'records_histories_table', rows: @rows %> - <% end %> -
-
- <% end %> + <%= react_component("Results/Records") %>
diff --git a/app/webpacker/components/Results/Records/RecordsTable.jsx b/app/webpacker/components/Results/Records/RecordsTable.jsx new file mode 100644 index 00000000000..e87590a673b --- /dev/null +++ b/app/webpacker/components/Results/Records/RecordsTable.jsx @@ -0,0 +1,136 @@ +import React, { useMemo } from 'react'; +import { Table } from 'semantic-ui-react'; +import _ from 'lodash'; +import I18n from '../../../lib/i18n'; +import { formatAttemptResult } from '../../../lib/wca-live/attempts'; +import CountryFlag from '../../wca/CountryFlag'; +import { continents, countries } from '../../../lib/wca-data.js.erb'; +import { personUrl } from '../../../lib/requests/routes.js.erb'; + +function CountryCell({ country }) { + return ( + + {country.iso2 && } + {' '} + {country.name} + + ); +} + +function ResultRow({ + result, competition, rank, isAverage, show, country, key, +}) { + const attempts = [result.value1, result.value2, result.value3, result.value4, result.value5] + .filter(Boolean); + const bestResult = _.max(attempts); + const worstResult = _.min(attempts); + const bestResultIndex = attempts.indexOf(bestResult); + const worstResultIndex = attempts.indexOf(worstResult); + return ( + + {show === 'by region' ? + : {rank} } + + {result.personName} + + + {formatAttemptResult(result.value, result.eventId)} + + {show !== 'by region' && } + + + {' '} + {competition.cellName} + + {isAverage && (attempts.map((a, i) => ( + + { attempts.length === 5 + && (i === bestResultIndex || i === worstResultIndex) + ? `(${formatAttemptResult(a, result.eventId)})` : formatAttemptResult(a, result.eventId)} + + )) + )} + + ); +} + +export default function RecordsTable({ + rows, competitionsById, isAverage, show, +}) { + const results = useMemo(() => { + let rowsToMap = rows; + let firstContinentIndex = 0; + let firstCountryIndex = 0; + if (show === 'by region') { + [rowsToMap, firstContinentIndex, firstCountryIndex] = rows; + } + + return rowsToMap.reduce((acc, result, index) => { + const competition = competitionsById[result.competitionId]; + const { value } = result; + + const lastItem = acc[acc.length - 1]; + const previousValue = lastItem?.result.value || 0; + const previousRank = lastItem?.rank || 0; + + const rank = value === previousValue ? previousRank : index + 1; + const tiedPrevious = rank === previousRank; + + let country = countries.real.find((c) => c.id === result.countryId); + if (index < firstContinentIndex) { + country = { name: I18n.t('results.table_elements.world') }; + } else if (index >= firstContinentIndex && index < firstCountryIndex) { + country = continents.real.find((c) => c.id === country.continentId); + } + + acc.push({ + result, + competition, + country, + rank, + tiedPrevious, + key: `${result.id}-${show}`, + }); + + return acc; + }, []); + }, [competitionsById, rows, show]); + return ( +
+ + + {show !== 'by region' ? # + : {I18n.t('results.table_elements.region')}} + {I18n.t('results.table_elements.name')} + {I18n.t('results.table_elements.result')} + {show !== 'by region' + && {I18n.t('results.table_elements.representing')}} + {I18n.t('results.table_elements.competition')} + {isAverage && ( + <> + {I18n.t('results.table_elements.solves')} + + + + + + )} + + + {results.map((r) => ( + + ))} + +
+
+ ); +} diff --git a/app/webpacker/components/Results/Records/index.jsx b/app/webpacker/components/Results/Records/index.jsx new file mode 100644 index 00000000000..e5f15d70302 --- /dev/null +++ b/app/webpacker/components/Results/Records/index.jsx @@ -0,0 +1,118 @@ +import React, { + useEffect, useMemo, useReducer, +} from 'react'; +import { Container } from 'semantic-ui-react'; +import { useQuery } from '@tanstack/react-query'; +import RecordsTable from './RecordsTable'; +import WCAQueryClientProvider from '../../../lib/providers/WCAQueryClientProvider'; +import { getRecords } from '../api/records'; +import Loading from '../../Requests/Loading'; +import { recordsUrl } from '../../../lib/requests/routes.js.erb'; +import ResultsFilter from '../resultsFilter'; + +const ActionTypes = { + SET_EVENT: 'SET_EVENT', + SET_REGION: 'SET_REGION', + SET_RANKING_TYPE: 'SET_RANKING_TYPE', + SET_GENDER: 'SET_GENDER', + SET_SHOW: 'SET_SHOW', +}; + +function parseInitialStateFromUrl(url) { + const urlPattern = /\/results\/records\/(\d+)\/(\w+)/; // Matches `/results/rankings/{event}/{rankingType}` + const match = url.match(urlPattern); + + if (!match) { + throw new Error('URL does not match the expected pattern.'); + } + + const [, event, rankingType] = match; // Extract event and rankingType from regex groups + + const urlObj = new URL(url); + const params = urlObj.searchParams; + const region = params.get('region') || 'world'; // Default to 'all' if not provided + const gender = params.get('gender') || 'All'; // Default to 'all' if not provided + const show = params.get('show') || '100 persons'; // Default to 'Persons' if not provided + + return { + event, + region, + gender, + show, + rankingType, + }; +} + +function filterReducer(state, action) { + switch (action.type) { + case ActionTypes.SET_EVENT: + return { ...state, event: action.payload }; + case ActionTypes.SET_REGION: + return { ...state, region: action.payload }; + case ActionTypes.SET_RANKING_TYPE: + return { ...state, rankingType: action.payload }; + case ActionTypes.SET_GENDER: + return { ...state, gender: action.payload }; + case ActionTypes.SET_SHOW: + return { ...state, show: action.payload }; + default: + throw new Error(`Unhandled action type: ${action.type}`); + } +} + +export default function Wrapper() { + return ( + + + + ); +} + +export function Rankings() { + // Define the initial state + const initialState = useMemo(() => ({ + event: '333', region: 'world', gender: 'All', show: '', + }), []); + + // Use the reducer + const [filterState, dispatch] = useReducer(filterReducer, initialState); + + const filterActions = useMemo( + () => ({ + setEvent: (event) => dispatch({ type: ActionTypes.SET_EVENT, payload: event }), + setRegion: (region) => dispatch({ type: ActionTypes.SET_REGION, payload: region }), + setRankingType: (rankingType) => dispatch({ type: ActionTypes.SET_RANKING_TYPE, payload: rankingType }), + setGender: (gender) => dispatch({ type: ActionTypes.SET_GENDER, payload: gender }), + setShow: (show) => dispatch({ type: ActionTypes.SET_SHOW, payload: show }), + }), + [dispatch], + ); + + const { + event, region, gender, show, + } = filterState; + + const { data, isFetching } = useQuery({ + queryKey: ['records', event, region, gender, show], + queryFn: () => getRecords(event, region, gender, show), + }); + + useEffect(() => { + window.history.replaceState(null, '', recordsUrl(event, region, gender, show)); + }, [event, region, gender, show]); + + if (isFetching) { + return ; + } + + return ( + + + + + ); +} diff --git a/app/webpacker/components/Results/api/records.js b/app/webpacker/components/Results/api/records.js new file mode 100644 index 00000000000..a4d46abb65d --- /dev/null +++ b/app/webpacker/components/Results/api/records.js @@ -0,0 +1,8 @@ +import { fetchJsonOrError } from '../../../lib/requests/fetchWithAuthenticityToken'; +import { recordsUrl } from '../../../lib/requests/routes.js.erb'; + +// eslint-disable-next-line import/prefer-default-export +export async function getRecords(eventId, region, gender, show) { + const { data } = await fetchJsonOrError(recordsUrl(eventId, region, gender, show), { headers: { Accept: 'application/json' } }); + return data; +} diff --git a/app/webpacker/components/Results/resultsFilter.jsx b/app/webpacker/components/Results/resultsFilter.jsx new file mode 100644 index 00000000000..9797aeb0e36 --- /dev/null +++ b/app/webpacker/components/Results/resultsFilter.jsx @@ -0,0 +1,95 @@ +import React, { useMemo } from 'react'; +import { + Button, ButtonGroup, Form, Segment, +} from 'semantic-ui-react'; +import { EventSelector } from '../wca/EventSelector'; +import { RegionSelector } from '../CompetitionsOverview/CompetitionsFilters'; +import { countries } from '../../lib/wca-data.js.erb'; +import I18n from '../../lib/i18n'; + +export default function ResultsFilter({ filterState, filterActions, showCategories }) { + const { + event, + region, + rankingType, + gender, + show, + } = filterState; + + const { + setEvent, + setRegion, + setRankingType, + setGender, + setShow, + } = filterActions; + + const regionIso2 = useMemo(() => { + if (region === 'world') { + return 'all'; + } + return countries.real.find((country) => country.id === region)?.iso2 || region; + }, [region]); + return ( + +
+ + setEvent(eventId)} + hideAllButton + hideClearButton + /> + { + if (r === 'all') { + setRegion('world'); + } else { + setRegion(countries.byIso2[r]?.id ?? r); + } + }} + /> + + + { rankingType && ( + + + + + + + + )} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + + + + + + + + + + + + + + + + + +
+
+ ); +} diff --git a/app/webpacker/lib/requests/routes.js.erb b/app/webpacker/lib/requests/routes.js.erb index 4dc754e43d0..a50cd12d142 100644 --- a/app/webpacker/lib/requests/routes.js.erb +++ b/app/webpacker/lib/requests/routes.js.erb @@ -254,7 +254,14 @@ export const panelUrls = { }, } -export const panelRedirectUrl = (panelPage) => `<%= CGI.unescape(Rails.application.routes.url_helpers.panel_redirect_path(panel_page: "${panelPage}")) %>`; +export const panelPageUrl = (panelPage) => `<%= CGI.unescape(Rails.application.routes.url_helpers.panel_page_path(id: "${panelPage}")) %>`; + +export const viewUrls = { + tickets: { + list: (type, status, sort) => `<%= CGI.unescape(Rails.application.routes.url_helpers.tickets_path) %>?${jsonToQueryString({ type, status, sort })}`, + show: (ticketId) => `<%= CGI.unescape(Rails.application.routes.url_helpers.ticket_path("${ticketId}")) %>`, + } +} export const actionUrls = { tickets: { @@ -291,3 +298,22 @@ export const updateRegistrationUrl = `<%= CGI.unescape(Rails.application.routes. export const bulkUpdateRegistrationUrl = `<%= CGI.unescape(Rails.application.routes.url_helpers.api_v1_registrations_bulk_update_path) %>`; export const paymentTicketUrl = (competitionId, donationIso) => `<%= CGI.unescape(Rails.application.routes.url_helpers.api_v1_registrations_payment_ticket_path(competition_id: "${competitionId}", donation_iso: "${donationIso}")) %>`; + +export const recordsUrl = (eventId, rankingType, region, gender, show) => { + const url = `<%= CGI.unescape(Rails.application.routes.url_helpers.rankings_path(event_id: "${eventId}", type: "${rankingType}")) %>` + const queryParams = new URLSearchParams(); + + if (region !== 'world') { + queryParams.append('region', region); + } + + if (gender !== 'All') { + queryParams.append('gender', gender); + } + + if (show !== '100 persons') { + queryParams.append('show', show); + } + + return `${url}?${queryParams.toString()}` +}; From a3195c1f1ff266bfa4f3d2dc12ea0cb2091a87e9 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 14 Jan 2025 12:26:31 +0100 Subject: [PATCH 02/70] port filter changes from other pr --- .../CompetitionsFilters.js | 2 +- .../Results/Records/RecordsTable.jsx | 18 ++++++++++-------- .../components/Results/resultsFilter.jsx | 12 +++++++----- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js b/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js index 7d756053fe5..09b731a4650 100644 --- a/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js +++ b/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js @@ -159,7 +159,7 @@ function TimeOrderButtonGroup({ filterState, dispatchFilter }) { return ( <> - + @@ -87,7 +87,7 @@ export default function ResultsFilter({ - +
{I18n.t('results.selector_elements.show_selector.show')}
{showCategories.map((category) => ( From d8b0cc429c32b13ef2af99e70a7d861144cc2b82 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Mon, 3 Feb 2025 19:32:34 +0900 Subject: [PATCH 39/70] Fix import after merging --- .../components/Results/Records/HistoryRecordsTables.jsx | 3 +-- .../components/Results/Records/MixedRecordsTables.jsx | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx b/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx index 77999dbf104..485debc79df 100644 --- a/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx +++ b/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx @@ -1,7 +1,6 @@ import React from 'react'; import { Header, Table } from 'semantic-ui-react'; -import { events } from '../../../lib/wca-data.js.erb'; -import { WCA_EVENT_IDS } from '../../wca/EventSelector'; +import { events, WCA_EVENT_IDS } from '../../../lib/wca-data.js.erb'; import { HistoryRow } from '../TableRows'; import { HistoryHeader } from '../TableHeaders'; import { augmentAndGroupResults } from './utils'; diff --git a/app/webpacker/components/Results/Records/MixedRecordsTables.jsx b/app/webpacker/components/Results/Records/MixedRecordsTables.jsx index 034463d0ea6..60971432a31 100644 --- a/app/webpacker/components/Results/Records/MixedRecordsTables.jsx +++ b/app/webpacker/components/Results/Records/MixedRecordsTables.jsx @@ -1,7 +1,6 @@ import React from 'react'; import { Header, Table } from 'semantic-ui-react'; -import { events } from '../../../lib/wca-data.js.erb'; -import { WCA_EVENT_IDS } from '../../wca/EventSelector'; +import { events, WCA_EVENT_IDS } from '../../../lib/wca-data.js.erb'; import { RecordRow } from '../TableRows'; import { MixedHeader } from '../TableHeaders'; import { augmentAndGroupResults } from './utils'; From 0e18de37e8d77da47cb3903efa8a8d75a11800ed Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Mon, 3 Feb 2025 19:47:12 +0900 Subject: [PATCH 40/70] Combine 'ResultsFilter' after merge --- .../components/Results/Rankings/index.jsx | 10 +- .../components/Results/ResultsFilter.jsx | 8 +- .../components/Results/resultsFilter.jsx | 95 ------------------- 3 files changed, 12 insertions(+), 101 deletions(-) delete mode 100644 app/webpacker/components/Results/resultsFilter.jsx diff --git a/app/webpacker/components/Results/Rankings/index.jsx b/app/webpacker/components/Results/Rankings/index.jsx index 0a12fe8acc8..7a5a8f803e5 100644 --- a/app/webpacker/components/Results/Rankings/index.jsx +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -5,7 +5,7 @@ import { Container } from 'semantic-ui-react'; import RankingsTable from './RankingsTable'; import WCAQueryClientProvider from '../../../lib/providers/WCAQueryClientProvider'; import { rankingsUrl } from '../../../lib/requests/routes.js.erb'; -import ResultsFilter from '../resultsFilter'; +import ResultsFilter from '../ResultsFilter'; const ActionTypes = { SET_EVENT: 'SET_EVENT', @@ -65,6 +65,8 @@ export default function Wrapper() { ); } +const SHOW_CATEGORIES = ['100 persons', '100 results', 'by region']; + export function Rankings() { const [filterState, dispatch] = useReducer( filterReducer, @@ -94,7 +96,11 @@ export function Rankings() { return ( - + ); diff --git a/app/webpacker/components/Results/ResultsFilter.jsx b/app/webpacker/components/Results/ResultsFilter.jsx index 007e4e60aaf..da13a460dfb 100644 --- a/app/webpacker/components/Results/ResultsFilter.jsx +++ b/app/webpacker/components/Results/ResultsFilter.jsx @@ -69,7 +69,7 @@ export default function ResultsFilter({ > {I18n.t('results.selector_elements.type_selector.single')} - + { event !== '333mbf' && }
)} @@ -78,7 +78,7 @@ export default function ResultsFilter({ {/* */} {/* */} {/* */} - +
{I18n.t('results.selector_elements.gender_selector.gender')}
@@ -86,11 +86,11 @@ export default function ResultsFilter({
- +
{I18n.t('results.selector_elements.show_selector.show')}
{showCategories.map((category) => ( - + ))}
diff --git a/app/webpacker/components/Results/resultsFilter.jsx b/app/webpacker/components/Results/resultsFilter.jsx deleted file mode 100644 index a8e58dda9ff..00000000000 --- a/app/webpacker/components/Results/resultsFilter.jsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { useMemo } from 'react'; -import { - Button, ButtonGroup, Form, Header, Segment, -} from 'semantic-ui-react'; -import { EventSelector } from '../wca/EventSelector'; -import { RegionSelector } from '../CompetitionsOverview/CompetitionsFilters'; -import { countries } from '../../lib/wca-data.js.erb'; -import I18n from '../../lib/i18n'; - -export default function ResultsFilter({ filterState, filterActions }) { - const { - event, - region, - rankingType, - gender, - show, - } = filterState; - - const { - setEvent, - setRegion, - setRankingType, - setGender, - setShow, - } = filterActions; - - const regionIso2 = useMemo(() => { - if (region === 'world') { - return 'all'; - } - return countries.real.find((country) => country.id === region)?.iso2 || region; - }, [region]); - return ( - -
- - setEvent(eventId)} - hideAllButton - hideClearButton - /> - - - { - if (r === 'all') { - setRegion('world'); - } else { - setRegion(countries.byIso2[r]?.id ?? r); - } - }} - /> - - - -
{I18n.t('results.selector_elements.type_selector.type')}
- - - { event !== '333mbf' && } - -
- {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - -
{I18n.t('results.selector_elements.gender_selector.gender')}
- - - - - -
- -
{I18n.t('results.selector_elements.show_selector.show')}
- - - - - -
-
-
-
- ); -} From 7ebbca19bf40fdd50f17853821da0d2ebb0564fc Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Mon, 3 Feb 2025 20:02:18 +0900 Subject: [PATCH 41/70] Styling: Remove line break from 'All' button --- app/webpacker/components/Results/ResultsFilter.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/webpacker/components/Results/ResultsFilter.jsx b/app/webpacker/components/Results/ResultsFilter.jsx index da13a460dfb..6c43b704401 100644 --- a/app/webpacker/components/Results/ResultsFilter.jsx +++ b/app/webpacker/components/Results/ResultsFilter.jsx @@ -50,6 +50,7 @@ export default function ResultsFilter({ onEventSelection={({ eventId }) => setEvent(eventId)} hideAllButton={!isRecords} hideClearButton + showBreakBeforeButtons={false} />
From 971700723a89394f03178d280b7bb9eb43504cda Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Mon, 3 Feb 2025 20:08:15 +0900 Subject: [PATCH 42/70] Show filters table while fetching data --- .../components/Results/Records/index.jsx | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/app/webpacker/components/Results/Records/index.jsx b/app/webpacker/components/Results/Records/index.jsx index 7e08cdd196e..d5fd4e7558d 100644 --- a/app/webpacker/components/Results/Records/index.jsx +++ b/app/webpacker/components/Results/Records/index.jsx @@ -83,19 +83,10 @@ export function Records() { event, region, gender, show, } = filterState; - const { data, isFetching } = useQuery({ - queryKey: ['records', event, region, gender, show], - queryFn: () => getRecords(event, region, gender, show), - }); - useEffect(() => { window.history.replaceState(null, '', recordsUrl(event, region, gender, show)); }, [event, region, gender, show]); - if (isFetching) { - return ; - } - return ( - + ); } -function RecordsTables({ competitionsById, rows, show }) { +function RecordsTables({ filterState }) { + const { + event, region, gender, show, + } = filterState; + + const { data, isFetching } = useQuery({ + queryKey: ['records', event, region, gender, show], + queryFn: () => getRecords(event, region, gender, show), + placeholderData: { rows: [], competitionsById: {} }, + }); + + const { rows, competitionsById } = data; + + if (isFetching) { + return ; + } + if (rows.length === 0) { return No results found; } From e3265533217b04f5349a8b165c1768a496db550e Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Mon, 3 Feb 2025 22:22:12 +0900 Subject: [PATCH 43/70] Add cubing icon to sub-table headers --- .../components/Results/Records/MixedRecordsTables.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/webpacker/components/Results/Records/MixedRecordsTables.jsx b/app/webpacker/components/Results/Records/MixedRecordsTables.jsx index 60971432a31..c699cf1111f 100644 --- a/app/webpacker/components/Results/Records/MixedRecordsTables.jsx +++ b/app/webpacker/components/Results/Records/MixedRecordsTables.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Header, Table } from 'semantic-ui-react'; +import { Header, Icon, Table } from 'semantic-ui-react'; import { events, WCA_EVENT_IDS } from '../../../lib/wca-data.js.erb'; import { RecordRow } from '../TableRows'; import { MixedHeader } from '../TableHeaders'; @@ -23,7 +23,10 @@ export default function MixedRecordsTables({ function MixedRecordsTable({ record, eventId }) { return ( <> -
{events.byId[eventId].name}
+
+ + {events.byId[eventId].name} +
From a51dfca6bf9dbada3fd16b9cf47232a5be2b1a52 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Mon, 3 Feb 2025 22:51:00 +0900 Subject: [PATCH 44/70] Use shared cell models --- app/controllers/results_controller.rb | 2 +- .../Results/Rankings/RankingsTable.jsx | 42 +++++++------------ .../components/Results/TableCells.jsx | 4 +- 3 files changed, 18 insertions(+), 30 deletions(-) diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index f3bde378a71..fdb5d369c58 100644 --- a/app/controllers/results_controller.rb +++ b/app/controllers/results_controller.rb @@ -170,7 +170,7 @@ def rankings if @is_by_region rows = compute_rankings_by_region(rows, @continent, @country) end - competitions_by_id = Competition.where(id: comp_ids).index_by(&:id).transform_values { |comp| comp.as_json(methods: %w[], include: [], only: %w[cellName id countryId]) } + competitions_by_id = Competition.where(id: comp_ids).index_by(&:id).transform_values { |comp| comp.as_json(methods: %w[country], include: [], only: %w[cellName id]) } { rows: rows.as_json, competitionsById: competitions_by_id } diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 8ec949641c7..552b6a1c9d6 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -5,11 +5,15 @@ import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-tabl import { useQuery } from '@tanstack/react-query'; import I18n from '../../../lib/i18n'; import { formatAttemptResult } from '../../../lib/wca-live/attempts'; -import CountryFlag from '../../wca/CountryFlag'; import { continents, countries } from '../../../lib/wca-data.js.erb'; -import { personUrl } from '../../../lib/requests/routes.js.erb'; import { getRankings } from '../api/rankings'; import Loading from '../../Requests/Loading'; +import { + AttemptsCells, + CompetitionCell, + CountryCell, + PersonCell, +} from '../TableCells'; function getCountryOrContinent(result, firstContinentIndex, firstCountryIndex, index) { if (index < firstContinentIndex) { @@ -21,16 +25,6 @@ function getCountryOrContinent(result, firstContinentIndex, firstCountryIndex, i return countries.byId[result.countryId]; } -function CountryCell({ country }) { - return ( - - {country.iso2 && } - {' '} - {country.name} - - ); -} - function ResultRow({ result, competition, rank, isAverage, show, country, }) { @@ -46,25 +40,19 @@ function ResultRow({ {show === 'by region' ? : {rank} } - - {result.personName} - + {formatAttemptResult(result.value, result.eventId)} {show !== 'by region' && } - - - {' '} - {competition.cellName} - - {isAverage && (attempts.map((a, i) => ( - - { attempts.length === 5 - && (i === bestResultIndex || i === worstResultIndex) - ? `(${formatAttemptResult(a, result.eventId)})` : formatAttemptResult(a, result.eventId)} - - )) + + {isAverage && ( + )} ); diff --git a/app/webpacker/components/Results/TableCells.jsx b/app/webpacker/components/Results/TableCells.jsx index 026918d0e0b..a570a2f5926 100644 --- a/app/webpacker/components/Results/TableCells.jsx +++ b/app/webpacker/components/Results/TableCells.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { Table } from 'semantic-ui-react'; import CountryFlag from '../wca/CountryFlag'; import EventIcon from '../wca/EventIcon'; -import { personUrl } from '../../lib/requests/routes.js.erb'; +import { competitionUrl, personUrl } from '../../lib/requests/routes.js.erb'; import { formatAttemptResult } from '../../lib/wca-live/attempts'; import { events } from '../../lib/wca-data.js.erb'; @@ -36,7 +36,7 @@ export function CompetitionCell({ competition }) { {' '} - {competition.cellName} + {competition.cellName} ); } From 92ae2be06f7e9bed29ae2cd7743aaa65fd37d2a7 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Mon, 3 Feb 2025 22:58:14 +0900 Subject: [PATCH 45/70] Add cache iso2 backwards compatibility --- .../components/Results/Rankings/RankingsTable.jsx | 5 ++++- app/webpacker/components/Results/TableCells.jsx | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 552b6a1c9d6..0a785f1a274 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -45,7 +45,10 @@ function ResultRow({ {formatAttemptResult(result.value, result.eventId)} {show !== 'by region' && } - + {isAverage && ( - + {' '} {competition.cellName} From 7470bf87bc95f0cba40551f4ec01725a13a93e01 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Mon, 3 Feb 2025 23:02:52 +0900 Subject: [PATCH 46/70] Make sure the iso2 fallback is only used wheen necessary --- app/webpacker/components/Results/Rankings/RankingsTable.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 0a785f1a274..e497b5611dc 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -47,7 +47,7 @@ function ResultRow({ {show !== 'by region' && } {isAverage && ( Date: Tue, 4 Feb 2025 14:47:59 +0900 Subject: [PATCH 47/70] WIP Integrate Tanstack Table rendering mechanism --- .../Results/Rankings/RankingsTable.jsx | 84 ++++++++++++++----- 1 file changed, 62 insertions(+), 22 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 6348a32e13b..28ab04db729 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -66,18 +66,42 @@ export default function RankingsTable({ filterState }) { }); const columns = useMemo(() => { + const rankColumn = { + accessorKey: 'rank', + header: '#', + cell: ({ row }) => ( + {row.original.rank} + ), + }; + + const regionColumn = { + accessorKey: 'country', + header: I18n.t('results.table_elements.region'), + cell: ({ row }) => ( + + ), + }; + const commonColumns = [ - { - accessorKey: 'rank', - header: show === 'by region' ? I18n.t('results.table_elements.region') : '#', - }, + show === 'by region' ? regionColumn : rankColumn, { accessorKey: 'result.name', header: I18n.t('results.table_elements.name'), + cell: ({ row }) => ( + + ), }, { accessorKey: 'result.value', header: I18n.t('results.table_elements.result'), + cell: ({ row }) => ( + + {formatAttemptResult(row.original.result.value, row.original.result.eventId)} + + ), }, ]; @@ -85,12 +109,19 @@ export default function RankingsTable({ filterState }) { commonColumns.push({ accessorKey: 'country.name', header: I18n.t('results.table_elements.representing'), + cell: ({ row }) => (), }); } commonColumns.push({ accessorKey: 'competition.name', header: I18n.t('results.table_elements.competition'), + cell: ({ row }) => ( + + ), }); if (isAverage) { @@ -99,6 +130,26 @@ export default function RankingsTable({ filterState }) { accessorKey: 'solves', header: I18n.t('results.table_elements.solves'), colSpan: 5, + cell: ({ row }) => { + const { result } = row.original; + + const attempts = [result.value1, result.value2, result.value3, result.value4, result.value5] + .filter(Boolean); + + const bestResult = _.max(attempts); + const worstResult = _.min(attempts); + const bestResultIndex = attempts.indexOf(bestResult); + const worstResultIndex = attempts.indexOf(worstResult); + + return ( + + ); + }, }); } @@ -130,24 +181,13 @@ export default function RankingsTable({ filterState }) { ))} - {table.getRowModel().rows.map((row) => { - const { - country, result, competition, rank, tiedPrevious, - } = row.original; - - return ( - - ); - })} + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => { + return flexRender(cell.column.columnDef.cell, cell.getContext()); + })} + + ))} From 61ca071e9837e15827f4a8c7e29e0a291e310152 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Wed, 5 Feb 2025 14:53:45 +0900 Subject: [PATCH 48/70] Collapse Records tables into reusable table skeletons --- .../Results/Records/GroupedEventsTable.jsx | 24 ++++++ .../Records/GroupedRankingTypesTable.jsx | 17 ++++ .../Results/Records/HistoryRecordsTables.jsx | 55 ++++-------- .../Records/MixedHistoryRecordsTable.jsx | 39 ++++----- .../Results/Records/MixedRecordsTables.jsx | 56 ++++--------- .../Results/Records/RankingTypeTable.jsx | 22 +++++ .../Results/Records/RecordsTable.jsx | 71 ++++++++++++++++ .../Results/Records/SeparateRecordsTables.jsx | 37 -------- .../Results/Records/SlimRecordsTable.jsx | 11 ++- .../components/Results/Records/index.jsx | 84 +------------------ .../components/Results/Records/utils.js | 14 +++- .../components/Results/RecordsTable.jsx | 8 +- .../components/Results/TableRows.jsx | 8 +- 13 files changed, 217 insertions(+), 229 deletions(-) create mode 100644 app/webpacker/components/Results/Records/GroupedEventsTable.jsx create mode 100644 app/webpacker/components/Results/Records/GroupedRankingTypesTable.jsx create mode 100644 app/webpacker/components/Results/Records/RankingTypeTable.jsx create mode 100644 app/webpacker/components/Results/Records/RecordsTable.jsx delete mode 100644 app/webpacker/components/Results/Records/SeparateRecordsTables.jsx diff --git a/app/webpacker/components/Results/Records/GroupedEventsTable.jsx b/app/webpacker/components/Results/Records/GroupedEventsTable.jsx new file mode 100644 index 00000000000..8d0c930e76d --- /dev/null +++ b/app/webpacker/components/Results/Records/GroupedEventsTable.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Header, Icon } from 'semantic-ui-react'; +import _ from 'lodash'; +import { events, WCA_EVENT_IDS } from '../../../lib/wca-data.js.erb'; + +export default function GroupedEventsTable({ + results, + children, +}) { + const resultsByEvent = _.groupBy(results, 'result.eventId'); + + const eventIds = Object.keys(resultsByEvent); + const eventsToRender = WCA_EVENT_IDS.filter((id) => eventIds.includes(id)); + + return eventsToRender.map((eventId) => ( + +
+ + {events.byId[eventId].name} +
+ {children(resultsByEvent[eventId])} +
+ )); +} diff --git a/app/webpacker/components/Results/Records/GroupedRankingTypesTable.jsx b/app/webpacker/components/Results/Records/GroupedRankingTypesTable.jsx new file mode 100644 index 00000000000..2ba06c28e50 --- /dev/null +++ b/app/webpacker/components/Results/Records/GroupedRankingTypesTable.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import {Header} from 'semantic-ui-react'; +import I18n from '../../../lib/i18n'; + +export default function GroupedRankingTypesTable({ results, children }) { + const [, singleRecords, averageRecords] = results; + + return ( + <> +
{I18n.t('results.selector_elements.type_selector.single')}
+ {children(singleRecords, 'single')} +
{I18n.t('results.selector_elements.type_selector.average')}
+ {children(averageRecords, 'average')} + + ); +} + diff --git a/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx b/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx index 485debc79df..e61f4ca73af 100644 --- a/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx +++ b/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx @@ -1,45 +1,26 @@ import React from 'react'; -import { Header, Table } from 'semantic-ui-react'; -import { events, WCA_EVENT_IDS } from '../../../lib/wca-data.js.erb'; +import { Table } from 'semantic-ui-react'; import { HistoryRow } from '../TableRows'; import { HistoryHeader } from '../TableHeaders'; -import { augmentAndGroupResults } from './utils'; import RecordsTable from '../RecordsTable'; -export default function HistoryRecordsTables({ - rows, competitionsById, -}) { - const results = augmentAndGroupResults(rows, competitionsById); - - return ( -
- {WCA_EVENT_IDS - .filter((id) => Object.keys(results).includes(id)) - .map((id) => )} -
- ); -} - -function HistoryRecordsTable({ record, eventId }) { +export default function HistoryRecordsTable({ results }) { return ( - <> -
{events.byId[eventId].name}
- - - - {record.map((r) => ( - - ))} - - - + + + + {results.map((r) => ( + + ))} + + ); } diff --git a/app/webpacker/components/Results/Records/MixedHistoryRecordsTable.jsx b/app/webpacker/components/Results/Records/MixedHistoryRecordsTable.jsx index 29c7ecd7c40..d8d7c79f34d 100644 --- a/app/webpacker/components/Results/Records/MixedHistoryRecordsTable.jsx +++ b/app/webpacker/components/Results/Records/MixedHistoryRecordsTable.jsx @@ -2,32 +2,27 @@ import React from 'react'; import { Table } from 'semantic-ui-react'; import { HistoryRow } from '../TableRows'; import { HistoryHeader } from '../TableHeaders'; -import { augmentResults } from './utils'; import RecordsTable from '../RecordsTable'; export default function MixedHistoryRecordsTable({ - rows, competitionsById, + results, }) { - const results = augmentResults(rows, competitionsById); - return ( -
- - - - {results.map((r) => ( - - ))} - - -
+ + + + {results.map((r) => ( + + ))} + + ); } diff --git a/app/webpacker/components/Results/Records/MixedRecordsTables.jsx b/app/webpacker/components/Results/Records/MixedRecordsTables.jsx index c699cf1111f..4f1ce066b8a 100644 --- a/app/webpacker/components/Results/Records/MixedRecordsTables.jsx +++ b/app/webpacker/components/Results/Records/MixedRecordsTables.jsx @@ -1,47 +1,25 @@ import React from 'react'; -import { Header, Icon, Table } from 'semantic-ui-react'; -import { events, WCA_EVENT_IDS } from '../../../lib/wca-data.js.erb'; +import { Table } from 'semantic-ui-react'; import { RecordRow } from '../TableRows'; import { MixedHeader } from '../TableHeaders'; -import { augmentAndGroupResults } from './utils'; import RecordsTable from '../RecordsTable'; -export default function MixedRecordsTables({ - rows, competitionsById, -}) { - const results = augmentAndGroupResults(rows, competitionsById); - - return ( -
- {WCA_EVENT_IDS - .filter((id) => Object.keys(results).includes(id)) - .map((id) => )} -
- ); -} - -function MixedRecordsTable({ record, eventId }) { +export default function MixedRecordsTable({ results }) { return ( - <> -
- - {events.byId[eventId].name} -
- - - - {record.map((r) => ( - - ))} - - - + + + + {results.map((r) => ( + + ))} + + ); } diff --git a/app/webpacker/components/Results/Records/RankingTypeTable.jsx b/app/webpacker/components/Results/Records/RankingTypeTable.jsx new file mode 100644 index 00000000000..0a0346f48ce --- /dev/null +++ b/app/webpacker/components/Results/Records/RankingTypeTable.jsx @@ -0,0 +1,22 @@ +import { Table } from 'semantic-ui-react'; +import React from 'react'; +import RecordsTable from '../RecordsTable'; +import { SeparateHeader } from '../TableHeaders'; +import { SeparateRecordsRow } from '../TableRows'; + +export default function RankingTypeTable({ results, rankingType }) { + return ( + + + + {results.map((row) => ( + + ))} + + + ); +} diff --git a/app/webpacker/components/Results/Records/RecordsTable.jsx b/app/webpacker/components/Results/Records/RecordsTable.jsx new file mode 100644 index 00000000000..ecd91525831 --- /dev/null +++ b/app/webpacker/components/Results/Records/RecordsTable.jsx @@ -0,0 +1,71 @@ +import { useQuery } from '@tanstack/react-query'; +import { Segment } from 'semantic-ui-react'; +import React from 'react'; +import { getRecords } from '../api/records'; +import Loading from '../../Requests/Loading'; +import MixedRecordsTables from './MixedRecordsTables'; +import SlimRecordTable from './SlimRecordsTable'; +import HistoryRecordsTables from './HistoryRecordsTables'; +import MixedHistoryRecordsTable from './MixedHistoryRecordsTable'; +import { augmentApiResults } from './utils'; +import GroupedEventsTable from './GroupedEventsTable'; +import GroupedRankingTypesTable from './GroupedRankingTypesTable'; +import RankingTypeTable from './RankingTypeTable'; + +export default function RecordsTable({ filterState }) { + const { + event, region, gender, show, + } = filterState; + + const { data: rows, isFetching } = useQuery({ + queryKey: ['records', event, region, gender, show], + queryFn: () => getRecords(event, region, gender, show), + select: (recordsData) => augmentApiResults(recordsData, show), + }); + + if (isFetching) { + return ; + } + + if (rows.length === 0) { + return No results found; + } + + switch (show) { + case 'mixed': + return ( + + {(eventResults) => } + + ); + + case 'slim': + return ; + + case 'separate': + return ( + + {(rankingResults, rankingType) => ( + + )} + + ); + + case 'history': + return ( + + {(eventResults) => } + + ); + + case 'mixed history': + return ; + + default: + console.error(`Invalid record table: ${show}`); + return null; + } +} diff --git a/app/webpacker/components/Results/Records/SeparateRecordsTables.jsx b/app/webpacker/components/Results/Records/SeparateRecordsTables.jsx deleted file mode 100644 index 4a3a46105e6..00000000000 --- a/app/webpacker/components/Results/Records/SeparateRecordsTables.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { Header, Table } from 'semantic-ui-react'; -import I18n from '../../../lib/i18n'; -import { SeparateHeader } from '../TableHeaders'; -import { SeparateRecordsRow } from '../TableRows'; -import RecordsTable from '../RecordsTable'; - -export default function SeparateRecordsTables({ rows, competitionsById }) { - const [, singleRecords, averageRecords] = rows; - - return ( - <> -
{I18n.t('results.selector_elements.type_selector.single')}
- -
{I18n.t('results.selector_elements.type_selector.average')}
- - - ); -} - -function RankingTypeTable({ records, rankingType, competitionsById }) { - return ( - - - - {records.map((row) => ( - - ))} - - - ); -} diff --git a/app/webpacker/components/Results/Records/SlimRecordsTable.jsx b/app/webpacker/components/Results/Records/SlimRecordsTable.jsx index a5ea24153fd..c2a597f7d21 100644 --- a/app/webpacker/components/Results/Records/SlimRecordsTable.jsx +++ b/app/webpacker/components/Results/Records/SlimRecordsTable.jsx @@ -2,16 +2,19 @@ import React from 'react'; import { Table } from 'semantic-ui-react'; import { SlimHeader } from '../TableHeaders'; import { SlimRecordsRow } from '../TableRows'; +import RecordsTable from '../RecordsTable'; + +export default function SlimRecordsTable({ results }) { + const [slimmedRows] = results; -export default function SlimRecordsTable({ rows }) { return ( - + - {rows.map((row) => ( + {slimmedRows.map((row) => ( ))} -
+
); } diff --git a/app/webpacker/components/Results/Records/index.jsx b/app/webpacker/components/Results/Records/index.jsx index d5fd4e7558d..8d1f4b6edb6 100644 --- a/app/webpacker/components/Results/Records/index.jsx +++ b/app/webpacker/components/Results/Records/index.jsx @@ -1,18 +1,9 @@ -import React, { - useEffect, useMemo, useReducer, -} from 'react'; -import { Container, Segment } from 'semantic-ui-react'; -import { useQuery } from '@tanstack/react-query'; +import React, { useEffect, useMemo, useReducer } from 'react'; +import { Container } from 'semantic-ui-react'; import WCAQueryClientProvider from '../../../lib/providers/WCAQueryClientProvider'; -import { getRecords } from '../api/records'; -import Loading from '../../Requests/Loading'; import { recordsUrl } from '../../../lib/requests/routes.js.erb'; import ResultsFilter from '../ResultsFilter'; -import SlimRecordTable from './SlimRecordsTable'; -import SeparateRecordsTables from './SeparateRecordsTables'; -import MixedRecordsTables from './MixedRecordsTables'; -import HistoryRecordsTables from './HistoryRecordsTables'; -import MixedHistoryRecordsTable from './MixedHistoryRecordsTable'; +import RecordsTable from './RecordsTable'; const ActionTypes = { SET_EVENT: 'SET_EVENT', @@ -95,74 +86,7 @@ export function Records() { isRecords showCategories={SHOW_CATEGORIES} /> - + ); } - -function RecordsTables({ filterState }) { - const { - event, region, gender, show, - } = filterState; - - const { data, isFetching } = useQuery({ - queryKey: ['records', event, region, gender, show], - queryFn: () => getRecords(event, region, gender, show), - placeholderData: { rows: [], competitionsById: {} }, - }); - - const { rows, competitionsById } = data; - - if (isFetching) { - return ; - } - - if (rows.length === 0) { - return No results found; - } - - switch (show) { - case 'mixed': - return ( - - ); - - case 'slim': - return ( - - ); - - case 'separate': - return ( - - ); - - case 'history': - return ( - - ); - - case 'mixed history': - return ( - - ); - - default: - console.error(`Invalid record table: ${show}`); - return null; - } -} diff --git a/app/webpacker/components/Results/Records/utils.js b/app/webpacker/components/Results/Records/utils.js index b7a11c0e60c..29ab858c759 100644 --- a/app/webpacker/components/Results/Records/utils.js +++ b/app/webpacker/components/Results/Records/utils.js @@ -1,4 +1,3 @@ -import _ from 'lodash'; import { countries } from '../../../lib/wca-data.js.erb'; export function augmentResults(results, competitionsById) { @@ -15,6 +14,15 @@ export function augmentResults(results, competitionsById) { }); } -export function augmentAndGroupResults(results, competitionsById) { - return _.groupBy(augmentResults(results, competitionsById), 'result.eventId'); +export function augmentApiResults(data, show) { + const { rows, competitionsById } = data; + + const isSlim = show === 'slim'; + const isSeparate = show === 'separate'; + + if (isSlim || isSeparate) { + return data.map((resultGroup) => augmentResults(resultGroup, competitionsById)); + } + + return augmentResults(rows, competitionsById); } diff --git a/app/webpacker/components/Results/RecordsTable.jsx b/app/webpacker/components/Results/RecordsTable.jsx index f3222fa0e0c..02df4ea18ac 100644 --- a/app/webpacker/components/Results/RecordsTable.jsx +++ b/app/webpacker/components/Results/RecordsTable.jsx @@ -3,8 +3,10 @@ import { Table } from 'semantic-ui-react'; export default function RecordsTable({ children }) { return ( - - {children} -
+
+ + {children} +
+
); } diff --git a/app/webpacker/components/Results/TableRows.jsx b/app/webpacker/components/Results/TableRows.jsx index 26ca1df72cb..4fd6efd4863 100644 --- a/app/webpacker/components/Results/TableRows.jsx +++ b/app/webpacker/components/Results/TableRows.jsx @@ -42,16 +42,16 @@ export function SlimRecordsRow({ row }) { ); } -export function SeparateRecordsRow({ result, competition, rankingType }) { +export function SeparateRecordsRow({ result, rankingType }) { const [attempts, bestResultIndex, worstResultIndex] = resultAttempts(result); - const country = countries.real.find((c) => c.id === result.countryId); + return ( {formatAttemptResult(result.value, result.eventId)} - - + + {rankingType === 'average' && ( Date: Wed, 5 Feb 2025 15:36:24 +0900 Subject: [PATCH 49/70] Fix augmenting flakes --- .../Results/Records/GroupedRankingTypesTable.jsx | 1 - .../components/Results/Records/RankingTypeTable.jsx | 6 ++++-- app/webpacker/components/Results/Records/utils.js | 12 +++++++++++- app/webpacker/components/Results/TableRows.jsx | 11 ++++++----- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/app/webpacker/components/Results/Records/GroupedRankingTypesTable.jsx b/app/webpacker/components/Results/Records/GroupedRankingTypesTable.jsx index 2ba06c28e50..cc006918e29 100644 --- a/app/webpacker/components/Results/Records/GroupedRankingTypesTable.jsx +++ b/app/webpacker/components/Results/Records/GroupedRankingTypesTable.jsx @@ -14,4 +14,3 @@ export default function GroupedRankingTypesTable({ results, children }) { ); } - diff --git a/app/webpacker/components/Results/Records/RankingTypeTable.jsx b/app/webpacker/components/Results/Records/RankingTypeTable.jsx index 0a0346f48ce..5eed62a8579 100644 --- a/app/webpacker/components/Results/Records/RankingTypeTable.jsx +++ b/app/webpacker/components/Results/Records/RankingTypeTable.jsx @@ -11,9 +11,11 @@ export default function RankingTypeTable({ results, rankingType }) { {results.map((row) => ( ))} diff --git a/app/webpacker/components/Results/Records/utils.js b/app/webpacker/components/Results/Records/utils.js index 29ab858c759..df3a6440f9a 100644 --- a/app/webpacker/components/Results/Records/utils.js +++ b/app/webpacker/components/Results/Records/utils.js @@ -2,6 +2,8 @@ import { countries } from '../../../lib/wca-data.js.erb'; export function augmentResults(results, competitionsById) { return results.map((result) => { + if (result === null) return null; + const competition = competitionsById[result.competitionId]; const country = countries.real.find((c) => c.id === result.countryId); @@ -21,7 +23,15 @@ export function augmentApiResults(data, show) { const isSeparate = show === 'separate'; if (isSlim || isSeparate) { - return data.map((resultGroup) => augmentResults(resultGroup, competitionsById)); + const [slimmed, singleRows, averageRows] = rows; + + const augmentSlimmed = slimmed.map((pair) => augmentResults(pair, competitionsById)); + + return [ + augmentSlimmed, + augmentResults(singleRows, competitionsById), + augmentResults(averageRows, competitionsById), + ]; } return augmentResults(rows, competitionsById); diff --git a/app/webpacker/components/Results/TableRows.jsx b/app/webpacker/components/Results/TableRows.jsx index 4fd6efd4863..46130bc9806 100644 --- a/app/webpacker/components/Results/TableRows.jsx +++ b/app/webpacker/components/Results/TableRows.jsx @@ -7,7 +7,6 @@ import I18n from '../../lib/i18n'; import { AttemptsCells, CompetitionCell, CountryCell, EventCell, PersonCell, } from './TableCells'; -import { countries } from '../../lib/wca-data.js.erb'; function resultAttempts(result) { const attempts = [result?.value1, result?.value2, result?.value3, result?.value4, result?.value5]; @@ -19,7 +18,7 @@ function resultAttempts(result) { } export function SlimRecordsRow({ row }) { - const [single, average] = row; + const [single, average] = row.map((augResult) => augResult?.result); const [attempts, bestResultIndex, worstResultIndex] = resultAttempts(average); return ( @@ -42,7 +41,9 @@ export function SlimRecordsRow({ row }) { ); } -export function SeparateRecordsRow({ result, rankingType }) { +export function SeparateRecordsRow({ + result, competition, rankingType, country, +}) { const [attempts, bestResultIndex, worstResultIndex] = resultAttempts(result); return ( @@ -50,8 +51,8 @@ export function SeparateRecordsRow({ result, rankingType }) { {formatAttemptResult(result.value, result.eventId)} - - + + {rankingType === 'average' && ( Date: Wed, 5 Feb 2025 15:42:37 +0900 Subject: [PATCH 50/70] Fix Slim records view key quirk --- .../Results/Records/GroupedRankingTypesTable.jsx | 2 +- .../components/Results/Records/SlimRecordsTable.jsx | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/webpacker/components/Results/Records/GroupedRankingTypesTable.jsx b/app/webpacker/components/Results/Records/GroupedRankingTypesTable.jsx index cc006918e29..4d391489a8b 100644 --- a/app/webpacker/components/Results/Records/GroupedRankingTypesTable.jsx +++ b/app/webpacker/components/Results/Records/GroupedRankingTypesTable.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import {Header} from 'semantic-ui-react'; +import { Header } from 'semantic-ui-react'; import I18n from '../../../lib/i18n'; export default function GroupedRankingTypesTable({ results, children }) { diff --git a/app/webpacker/components/Results/Records/SlimRecordsTable.jsx b/app/webpacker/components/Results/Records/SlimRecordsTable.jsx index c2a597f7d21..36686209a8c 100644 --- a/app/webpacker/components/Results/Records/SlimRecordsTable.jsx +++ b/app/webpacker/components/Results/Records/SlimRecordsTable.jsx @@ -11,9 +11,16 @@ export default function SlimRecordsTable({ results }) { - {slimmedRows.map((row) => ( - - ))} + {slimmedRows.map((row) => { + const combinedKey = [ + row[0]?.key, + row[1]?.key, + ].filter(Boolean).join('-'); + + return ( + + ); + })} ); From 335bb2ab2d40e4622c6193e497404d13b5c80bd4 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Wed, 5 Feb 2025 16:54:20 +0900 Subject: [PATCH 51/70] Provide React-Table configs for every Records view --- .../components/Results/Records/DataTable.jsx | 40 +++ .../Results/Records/HistoryRecordsTables.jsx | 23 +- .../Records/MixedHistoryRecordsTable.jsx | 23 +- .../Results/Records/MixedRecordsTables.jsx | 24 +- .../Results/Records/RankingTypeTable.jsx | 21 +- .../Results/Records/RecordsTable.jsx | 8 +- .../Results/Records/SlimRecordsTable.jsx | 26 +- .../components/Results/RecordsTable.jsx | 12 - .../components/Results/TableHeaders.jsx | 90 ------ .../components/Results/TableRows.jsx | 278 ++++++++++++------ 10 files changed, 257 insertions(+), 288 deletions(-) create mode 100644 app/webpacker/components/Results/Records/DataTable.jsx delete mode 100644 app/webpacker/components/Results/RecordsTable.jsx delete mode 100644 app/webpacker/components/Results/TableHeaders.jsx diff --git a/app/webpacker/components/Results/Records/DataTable.jsx b/app/webpacker/components/Results/Records/DataTable.jsx new file mode 100644 index 00000000000..e3c7506db81 --- /dev/null +++ b/app/webpacker/components/Results/Records/DataTable.jsx @@ -0,0 +1,40 @@ +import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; +import { Table } from 'semantic-ui-react'; +import React from 'react'; + +export default function DataTable({ rows, config }) { + const table = useReactTable({ + data: rows || [], + columns: config, + getCoreRowModel: getCoreRowModel(), + }); + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => { + return flexRender(cell.column.columnDef.cell, cell.getContext()); + })} + + ))} + +
+
+ ); +} diff --git a/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx b/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx index e61f4ca73af..450e68d7314 100644 --- a/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx +++ b/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx @@ -1,26 +1,9 @@ import React from 'react'; -import { Table } from 'semantic-ui-react'; -import { HistoryRow } from '../TableRows'; -import { HistoryHeader } from '../TableHeaders'; -import RecordsTable from '../RecordsTable'; +import { historyConfig } from '../TableRows'; +import DataTable from './DataTable'; export default function HistoryRecordsTable({ results }) { return ( - - - - {results.map((r) => ( - - ))} - - + ); } diff --git a/app/webpacker/components/Results/Records/MixedHistoryRecordsTable.jsx b/app/webpacker/components/Results/Records/MixedHistoryRecordsTable.jsx index d8d7c79f34d..039155bd332 100644 --- a/app/webpacker/components/Results/Records/MixedHistoryRecordsTable.jsx +++ b/app/webpacker/components/Results/Records/MixedHistoryRecordsTable.jsx @@ -1,28 +1,11 @@ import React from 'react'; -import { Table } from 'semantic-ui-react'; -import { HistoryRow } from '../TableRows'; -import { HistoryHeader } from '../TableHeaders'; -import RecordsTable from '../RecordsTable'; +import { historyConfig } from '../TableRows'; +import DataTable from './DataTable'; export default function MixedHistoryRecordsTable({ results, }) { return ( - - - - {results.map((r) => ( - - ))} - - + ); } diff --git a/app/webpacker/components/Results/Records/MixedRecordsTables.jsx b/app/webpacker/components/Results/Records/MixedRecordsTables.jsx index 4f1ce066b8a..ebcd682862d 100644 --- a/app/webpacker/components/Results/Records/MixedRecordsTables.jsx +++ b/app/webpacker/components/Results/Records/MixedRecordsTables.jsx @@ -1,25 +1,7 @@ import React from 'react'; -import { Table } from 'semantic-ui-react'; -import { RecordRow } from '../TableRows'; -import { MixedHeader } from '../TableHeaders'; -import RecordsTable from '../RecordsTable'; +import { mixedRecordsConfig } from '../TableRows'; +import DataTable from './DataTable'; export default function MixedRecordsTable({ results }) { - return ( - - - - {results.map((r) => ( - - ))} - - - ); + return ; } diff --git a/app/webpacker/components/Results/Records/RankingTypeTable.jsx b/app/webpacker/components/Results/Records/RankingTypeTable.jsx index 5eed62a8579..a1d8cfa568f 100644 --- a/app/webpacker/components/Results/Records/RankingTypeTable.jsx +++ b/app/webpacker/components/Results/Records/RankingTypeTable.jsx @@ -1,24 +1,9 @@ -import { Table } from 'semantic-ui-react'; import React from 'react'; -import RecordsTable from '../RecordsTable'; -import { SeparateHeader } from '../TableHeaders'; -import { SeparateRecordsRow } from '../TableRows'; +import { separateRecordsConfig } from '../TableRows'; +import DataTable from './DataTable'; export default function RankingTypeTable({ results, rankingType }) { return ( - - - - {results.map((row) => ( - - ))} - - + ); } diff --git a/app/webpacker/components/Results/Records/RecordsTable.jsx b/app/webpacker/components/Results/Records/RecordsTable.jsx index ecd91525831..4c952817815 100644 --- a/app/webpacker/components/Results/Records/RecordsTable.jsx +++ b/app/webpacker/components/Results/Records/RecordsTable.jsx @@ -1,13 +1,13 @@ -import { useQuery } from '@tanstack/react-query'; -import { Segment } from 'semantic-ui-react'; +import {useQuery} from '@tanstack/react-query'; +import {Segment} from 'semantic-ui-react'; import React from 'react'; -import { getRecords } from '../api/records'; +import {getRecords} from '../api/records'; import Loading from '../../Requests/Loading'; import MixedRecordsTables from './MixedRecordsTables'; import SlimRecordTable from './SlimRecordsTable'; import HistoryRecordsTables from './HistoryRecordsTables'; import MixedHistoryRecordsTable from './MixedHistoryRecordsTable'; -import { augmentApiResults } from './utils'; +import {augmentApiResults} from './utils'; import GroupedEventsTable from './GroupedEventsTable'; import GroupedRankingTypesTable from './GroupedRankingTypesTable'; import RankingTypeTable from './RankingTypeTable'; diff --git a/app/webpacker/components/Results/Records/SlimRecordsTable.jsx b/app/webpacker/components/Results/Records/SlimRecordsTable.jsx index 36686209a8c..c4fd151a68c 100644 --- a/app/webpacker/components/Results/Records/SlimRecordsTable.jsx +++ b/app/webpacker/components/Results/Records/SlimRecordsTable.jsx @@ -1,27 +1,15 @@ import React from 'react'; -import { Table } from 'semantic-ui-react'; -import { SlimHeader } from '../TableHeaders'; -import { SlimRecordsRow } from '../TableRows'; -import RecordsTable from '../RecordsTable'; +import { slimConfig } from '../TableRows'; +import DataTable from './DataTable'; export default function SlimRecordsTable({ results }) { const [slimmedRows] = results; - return ( - - - - {slimmedRows.map((row) => { - const combinedKey = [ - row[0]?.key, - row[1]?.key, - ].filter(Boolean).join('-'); + // Need to re-key with `single` and `average` indices so that React-Table + // will have an easier time operating on the data. + const slimmedData = slimmedRows.map(([single, average]) => ({ single, average })); - return ( - - ); - })} - - + return ( + ); } diff --git a/app/webpacker/components/Results/RecordsTable.jsx b/app/webpacker/components/Results/RecordsTable.jsx deleted file mode 100644 index 02df4ea18ac..00000000000 --- a/app/webpacker/components/Results/RecordsTable.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import { Table } from 'semantic-ui-react'; - -export default function RecordsTable({ children }) { - return ( -
- - {children} -
-
- ); -} diff --git a/app/webpacker/components/Results/TableHeaders.jsx b/app/webpacker/components/Results/TableHeaders.jsx deleted file mode 100644 index 0f9abe844ac..00000000000 --- a/app/webpacker/components/Results/TableHeaders.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import { Table } from 'semantic-ui-react'; -import I18n from '../../lib/i18n'; - -const headerConfig = { - separate: [ - 'results.table_elements.event', - 'results.table_elements.result', - 'results.table_elements.name', - 'results.table_elements.region', - 'results.table_elements.competition', - { condition: 'isAverage', value: 'results.table_elements.solves' }, - { condition: 'isAverage', values: Array(4).fill('') }, - ], - history: [ - 'results.table_elements.date_circa', - { condition: 'mixed', value: 'results.table_elements.event' }, - 'results.table_elements.name', - 'common.single', - 'common.average', - 'results.table_elements.region', - 'results.table_elements.competition', - 'results.table_elements.solves', - ...Array(4).fill(''), - ], - mixed: [ - 'results.selector_elements.type_selector.type', - 'results.table_elements.name', - 'results.table_elements.result', - 'results.table_elements.region', - 'results.table_elements.competition', - 'results.table_elements.solves', - ...Array(4).fill(''), - ], - slim: [ - 'results.table_elements.name', - 'common.single', - 'results.table_elements.event', - 'common.average', - 'results.table_elements.name', - 'results.table_elements.solves', - ...Array(4).fill(''), - ], -}; - -function DynamicHeader({ type, props }) { - const config = headerConfig[type]; - - return ( - - - {config.map((item) => { - if (typeof item === 'string' && item !== '') { - return {I18n.t(item)}; - } - - if (item.condition) { - if (props[item.condition]) { - if (Array.isArray(item.values)) { - return item.values.map(() => ( - - )); - } - return {I18n.t(item.value)}; - } - return false; - } - - return ; - })} - - - ); -} - -export function SeparateHeader(props) { - return ; -} - -export function HistoryHeader(props) { - return ; -} - -export function MixedHeader(props) { - return ; -} - -export function SlimHeader() { - return ; -} diff --git a/app/webpacker/components/Results/TableRows.jsx b/app/webpacker/components/Results/TableRows.jsx index 46130bc9806..79a5a657d64 100644 --- a/app/webpacker/components/Results/TableRows.jsx +++ b/app/webpacker/components/Results/TableRows.jsx @@ -4,108 +4,218 @@ import { Table } from 'semantic-ui-react'; import { DateTime } from 'luxon'; import { formatAttemptResult } from '../../lib/wca-live/attempts'; import I18n from '../../lib/i18n'; +import { countries } from '../../lib/wca-data.js.erb'; import { - AttemptsCells, CompetitionCell, CountryCell, EventCell, PersonCell, + AttemptsCells, + CompetitionCell, + CountryCell, + EventCell, + PersonCell, } from './TableCells'; function resultAttempts(result) { - const attempts = [result?.value1, result?.value2, result?.value3, result?.value4, result?.value5]; + const attempts = [result?.value1, result?.value2, result?.value3, result?.value4, result?.value5] + .filter(Boolean); + const bestResult = _.max(attempts); const worstResult = _.min(attempts); + const bestResultIndex = attempts.indexOf(bestResult); const worstResultIndex = attempts.indexOf(worstResult); + return [attempts, bestResultIndex, worstResultIndex]; } -export function SlimRecordsRow({ row }) { - const [single, average] = row.map((augResult) => augResult?.result); - const [attempts, bestResultIndex, worstResultIndex] = resultAttempts(average); - return ( - - - {formatAttemptResult(single.value, single.eventId)} - - {average && ( - <> - {formatAttemptResult(average.value, average.eventId)} - - - - )} - - ); -} +const resultsFiveWideColumn = { + accessorKey: 'result', + header: I18n.t('results.table_elements.solves'), + colSpan: 5, + cell: ({ getValue }) => { + const result = getValue(); -export function SeparateRecordsRow({ - result, competition, rankingType, country, -}) { - const [attempts, bestResultIndex, worstResultIndex] = resultAttempts(result); - - return ( - - - {formatAttemptResult(result.value, result.eventId)} - - - - {rankingType === 'average' && ( - - )} - - ); -} + const [attempts, bestResultIndex, worstResultIndex] = resultAttempts(result); -export function HistoryRow({ - result, competition, isMixed, country, -}) { - const [attempts, bestResultIndex, worstResultIndex] = resultAttempts(result); - return ( - - {DateTime.fromISO(result.start_date).toFormat('MMM dd, yyyy')} - {isMixed && } - - {result.type === 'average' && } - {formatAttemptResult(result.value, result.eventId)} - {result.type === 'single' && } - - + return ( - - ); -} + ); + }, +}; -export function RecordRow({ - result, competition, country, -}) { - const [attempts, bestResultIndex, worstResultIndex] = resultAttempts(result); - return ( - - {I18n.t(`results.selector_elements.type_selector.${result.type}`)} - - {formatAttemptResult(result.value, result.eventId)} - - - ( + + ), +}; + +const regionColumn = { + accessorKey: 'country', + header: I18n.t('results.table_elements.region'), + cell: ({ getValue }) => ( + + ), +}; + +const attemptResultColumn = { + accessorKey: 'result.value', + header: I18n.t('results.table_elements.result'), + cell: ({ row, getValue }) => ( + + {formatAttemptResult(getValue(), row.original.result.eventId)} + + ), +}; + +const personColumn = { + accessorKey: 'result.name', + header: I18n.t('results.table_elements.name'), + cell: ({ row }) => ( + + ), +}; + +const eventColumn = { + accessorKey: 'result.eventId', + header: I18n.t('results.table_elements.event'), + cell: ({ getValue }) => , +}; + +export const slimConfig = [ + { + accessorKey: 'single.result.personName', + header: I18n.t('results.table_elements.name'), + cell: ({ row, getValue }) => ( + - - ); -} + ), + }, + { + accessorKey: 'single.result.value', + header: I18n.t('common.single'), + cell: ({ row, getValue }) => ( + + {formatAttemptResult(getValue(), row.original.single.result.eventId)} + + ), + }, + { + accessorKey: 'single.result.eventId', + header: I18n.t('results.table_elements.event'), + cell: ({ getValue }) => , + }, + { + accessorFn: (res) => res.average?.result?.value, + header: I18n.t('common.average'), + cell: ({ row, getValue }) => ( + + {getValue() && formatAttemptResult(getValue(), row.original.average?.result?.eventId)} + + ), + }, + { + accessorFn: (res) => res.average?.result?.personName, + header: I18n.t('results.table_elements.name'), + cell: ({ row, getValue }) => { + if (getValue() === undefined) return ; + + return ( + + ); + }, + }, + { + accessorFn: (res) => res.average?.result, + header: I18n.t('results.table_elements.solves'), + colSpan: 5, + cell: ({ getValue }) => { + if (getValue() === undefined) return ; + + const result = getValue(); + + const [attempts, bestResultIndex, worstResultIndex] = resultAttempts(result); + + return ( + + ); + }, + }, +]; + +export const separateRecordsConfig = (rankingType) => [ + eventColumn, + attemptResultColumn, + personColumn, + regionColumn, + competitionColumn, + rankingType === 'average' && resultsFiveWideColumn, +].filter(Boolean); + +export const historyConfig = (isMixed) => [ + { + accessorKey: 'result.start_date', + header: I18n.t('results.table_elements.date_circa'), + cell: ({ getValue }) => ( + {DateTime.fromISO(getValue()).toFormat('MMM dd, yyyy')} + ), + }, + isMixed && eventColumn, + personColumn, + { + accessorKey: 'result.value', + header: I18n.t('common.single'), + cell: ({ row, getValue }) => ( + + {row.original.result.type === 'single' && formatAttemptResult(getValue(), row.original.result.eventId)} + + ), + }, + { + accessorKey: 'result.value', + header: I18n.t('common.average'), + cell: ({ row, getValue }) => ( + + {row.original.result.type === 'average' && formatAttemptResult(getValue(), row.original.result.eventId)} + + ), + }, + regionColumn, + competitionColumn, + resultsFiveWideColumn, +].filter(Boolean); + +export const mixedRecordsConfig = [ + { + accessorKey: 'result.type', + header: I18n.t('results.selector_elements.type_selector.type'), + cell: ({ getValue }) => ( + {I18n.t(`results.selector_elements.type_selector.${getValue()}`)} + ), + }, + personColumn, + attemptResultColumn, + regionColumn, + competitionColumn, + resultsFiveWideColumn, +]; From 735424eeb55dffd03a7fc4f3dc5abdf031927ac9 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Wed, 5 Feb 2025 17:00:05 +0900 Subject: [PATCH 52/70] Be economical about augmenting slim data --- .../Results/Records/SlimRecordsTable.jsx | 1 + .../components/Results/Records/utils.js | 4 +--- .../components/Results/TableRows.jsx | 24 +++++++++---------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/app/webpacker/components/Results/Records/SlimRecordsTable.jsx b/app/webpacker/components/Results/Records/SlimRecordsTable.jsx index c4fd151a68c..d42c59b3917 100644 --- a/app/webpacker/components/Results/Records/SlimRecordsTable.jsx +++ b/app/webpacker/components/Results/Records/SlimRecordsTable.jsx @@ -8,6 +8,7 @@ export default function SlimRecordsTable({ results }) { // Need to re-key with `single` and `average` indices so that React-Table // will have an easier time operating on the data. const slimmedData = slimmedRows.map(([single, average]) => ({ single, average })); + console.log(slimmedData); return ( diff --git a/app/webpacker/components/Results/Records/utils.js b/app/webpacker/components/Results/Records/utils.js index df3a6440f9a..e980aba77f7 100644 --- a/app/webpacker/components/Results/Records/utils.js +++ b/app/webpacker/components/Results/Records/utils.js @@ -25,10 +25,8 @@ export function augmentApiResults(data, show) { if (isSlim || isSeparate) { const [slimmed, singleRows, averageRows] = rows; - const augmentSlimmed = slimmed.map((pair) => augmentResults(pair, competitionsById)); - return [ - augmentSlimmed, + slimmed, // The 'slim' view does not need augmented data augmentResults(singleRows, competitionsById), augmentResults(averageRows, competitionsById), ]; diff --git a/app/webpacker/components/Results/TableRows.jsx b/app/webpacker/components/Results/TableRows.jsx index 79a5a657d64..69200eaeb09 100644 --- a/app/webpacker/components/Results/TableRows.jsx +++ b/app/webpacker/components/Results/TableRows.jsx @@ -94,61 +94,61 @@ const eventColumn = { export const slimConfig = [ { - accessorKey: 'single.result.personName', + accessorKey: 'single.personName', header: I18n.t('results.table_elements.name'), cell: ({ row, getValue }) => ( ), }, { - accessorKey: 'single.result.value', + accessorKey: 'single.value', header: I18n.t('common.single'), cell: ({ row, getValue }) => ( - {formatAttemptResult(getValue(), row.original.single.result.eventId)} + {formatAttemptResult(getValue(), row.original.single.eventId)} ), }, { - accessorKey: 'single.result.eventId', + accessorKey: 'single.eventId', header: I18n.t('results.table_elements.event'), cell: ({ getValue }) => , }, { - accessorFn: (res) => res.average?.result?.value, + accessorFn: (res) => res.average?.value, header: I18n.t('common.average'), cell: ({ row, getValue }) => ( - {getValue() && formatAttemptResult(getValue(), row.original.average?.result?.eventId)} + {getValue() && formatAttemptResult(getValue(), row.original.average?.eventId)} ), }, { - accessorFn: (res) => res.average?.result?.personName, + accessorFn: (res) => res.average?.personName, header: I18n.t('results.table_elements.name'), cell: ({ row, getValue }) => { if (getValue() === undefined) return ; return ( ); }, }, { - accessorFn: (res) => res.average?.result, + accessorFn: (res) => res.average, header: I18n.t('results.table_elements.solves'), colSpan: 5, cell: ({ getValue }) => { - if (getValue() === undefined) return ; - const result = getValue(); + if (!result) return ; + const [attempts, bestResultIndex, worstResultIndex] = resultAttempts(result); return ( From 30c532cea43e9de426e0b93be2a12f579083658b Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Wed, 5 Feb 2025 17:25:47 +0900 Subject: [PATCH 53/70] Share column definitions between Rankings & Records --- .../Results/{Records => }/DataTable.jsx | 4 +- .../Results/Rankings/RankingsTable.jsx | 185 ++---------------- .../Results/Records/HistoryRecordsTables.jsx | 9 - .../Records/MixedHistoryRecordsTable.jsx | 11 -- .../Results/Records/MixedRecordsTables.jsx | 7 - .../Results/Records/RankingTypeTable.jsx | 9 - .../Results/Records/RecordsTable.jsx | 55 +++--- .../Results/Records/SlimRecordsTable.jsx | 16 -- .../components/Results/TableCells.jsx | 1 + .../components/Results/TableRows.jsx | 26 ++- 10 files changed, 73 insertions(+), 250 deletions(-) rename app/webpacker/components/Results/{Records => }/DataTable.jsx (88%) delete mode 100644 app/webpacker/components/Results/Records/HistoryRecordsTables.jsx delete mode 100644 app/webpacker/components/Results/Records/MixedHistoryRecordsTable.jsx delete mode 100644 app/webpacker/components/Results/Records/MixedRecordsTables.jsx delete mode 100644 app/webpacker/components/Results/Records/RankingTypeTable.jsx delete mode 100644 app/webpacker/components/Results/Records/SlimRecordsTable.jsx diff --git a/app/webpacker/components/Results/Records/DataTable.jsx b/app/webpacker/components/Results/DataTable.jsx similarity index 88% rename from app/webpacker/components/Results/Records/DataTable.jsx rename to app/webpacker/components/Results/DataTable.jsx index e3c7506db81..81a8786b056 100644 --- a/app/webpacker/components/Results/Records/DataTable.jsx +++ b/app/webpacker/components/Results/DataTable.jsx @@ -28,9 +28,7 @@ export default function DataTable({ rows, config }) { {table.getRowModel().rows.map((row) => ( - {row.getVisibleCells().map((cell) => { - return flexRender(cell.column.columnDef.cell, cell.getContext()); - })} + {row.getVisibleCells().map((cell) => flexRender(cell.column.columnDef.cell, cell.getContext()))} ))} diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 28ab04db729..1d733ae4c7e 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -1,19 +1,19 @@ import React, { useMemo } from 'react'; -import { Table } from 'semantic-ui-react'; -import _ from 'lodash'; -import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; import { useQuery } from '@tanstack/react-query'; import I18n from '../../../lib/i18n'; -import { formatAttemptResult } from '../../../lib/wca-live/attempts'; import { continents, countries } from '../../../lib/wca-data.js.erb'; import { getRankings } from '../api/rankings'; import Loading from '../../Requests/Loading'; +import DataTable from '../DataTable'; import { - AttemptsCells, - CompetitionCell, - CountryCell, - PersonCell, -} from '../TableCells'; + attemptResultColumn, + competitionColumn, + personColumn, + rankColumn, + regionColumn, + representingColumn, + resultsFiveWideColumn, +} from '../TableRows'; function getCountryOrContinent(result, firstContinentIndex, firstCountryIndex, index) { if (index < firstContinentIndex) { @@ -65,167 +65,18 @@ export default function RankingsTable({ filterState }) { select: (rankingsData) => mapRankingsData(rankingsData, show === 'by region'), }); - const columns = useMemo(() => { - const rankColumn = { - accessorKey: 'rank', - header: '#', - cell: ({ row }) => ( - {row.original.rank} - ), - }; - - const regionColumn = { - accessorKey: 'country', - header: I18n.t('results.table_elements.region'), - cell: ({ row }) => ( - - ), - }; - - const commonColumns = [ - show === 'by region' ? regionColumn : rankColumn, - { - accessorKey: 'result.name', - header: I18n.t('results.table_elements.name'), - cell: ({ row }) => ( - - ), - }, - { - accessorKey: 'result.value', - header: I18n.t('results.table_elements.result'), - cell: ({ row }) => ( - - {formatAttemptResult(row.original.result.value, row.original.result.eventId)} - - ), - }, - ]; - - if (show !== 'by region') { - commonColumns.push({ - accessorKey: 'country.name', - header: I18n.t('results.table_elements.representing'), - cell: ({ row }) => (), - }); - } - - commonColumns.push({ - accessorKey: 'competition.name', - header: I18n.t('results.table_elements.competition'), - cell: ({ row }) => ( - - ), - }); - - if (isAverage) { - // One Cell per Solve of an Average - commonColumns.push({ - accessorKey: 'solves', - header: I18n.t('results.table_elements.solves'), - colSpan: 5, - cell: ({ row }) => { - const { result } = row.original; - - const attempts = [result.value1, result.value2, result.value3, result.value4, result.value5] - .filter(Boolean); - - const bestResult = _.max(attempts); - const worstResult = _.min(attempts); - const bestResultIndex = attempts.indexOf(bestResult); - const worstResultIndex = attempts.indexOf(worstResult); - - return ( - - ); - }, - }); - } - - return commonColumns; - }, [show, isAverage]); - - const table = useReactTable({ - data: data || [], - columns, - getCoreRowModel: getCoreRowModel(), - }); + const columns = useMemo(() => [ + show === 'by region' ? regionColumn : rankColumn, + personColumn, + attemptResultColumn, + show !== 'by region' && representingColumn, + competitionColumn, + isAverage && resultsFiveWideColumn, + ].filter(Boolean), [show, isAverage]); if (isFetching) return ; return ( -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder - ? null - : flexRender(header.column.columnDef.header, header.getContext())} - - ))} - - ))} - - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => { - return flexRender(cell.column.columnDef.cell, cell.getContext()); - })} - - ))} - -
-
- ); -} - -function ResultRow({ - result, competition, rank, isAverage, show, country, -}) { - const attempts = [result.value1, result.value2, result.value3, result.value4, result.value5] - .filter(Boolean); - - const bestResult = _.max(attempts); - const worstResult = _.min(attempts); - const bestResultIndex = attempts.indexOf(bestResult); - const worstResultIndex = attempts.indexOf(worstResult); - - return ( - - {show === 'by region' ? - : {rank} } - - - {formatAttemptResult(result.value, result.eventId)} - - {show !== 'by region' && } - - {isAverage && ( - - )} - + ); } diff --git a/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx b/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx deleted file mode 100644 index 450e68d7314..00000000000 --- a/app/webpacker/components/Results/Records/HistoryRecordsTables.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { historyConfig } from '../TableRows'; -import DataTable from './DataTable'; - -export default function HistoryRecordsTable({ results }) { - return ( - - ); -} diff --git a/app/webpacker/components/Results/Records/MixedHistoryRecordsTable.jsx b/app/webpacker/components/Results/Records/MixedHistoryRecordsTable.jsx deleted file mode 100644 index 039155bd332..00000000000 --- a/app/webpacker/components/Results/Records/MixedHistoryRecordsTable.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { historyConfig } from '../TableRows'; -import DataTable from './DataTable'; - -export default function MixedHistoryRecordsTable({ - results, -}) { - return ( - - ); -} diff --git a/app/webpacker/components/Results/Records/MixedRecordsTables.jsx b/app/webpacker/components/Results/Records/MixedRecordsTables.jsx deleted file mode 100644 index ebcd682862d..00000000000 --- a/app/webpacker/components/Results/Records/MixedRecordsTables.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; -import { mixedRecordsConfig } from '../TableRows'; -import DataTable from './DataTable'; - -export default function MixedRecordsTable({ results }) { - return ; -} diff --git a/app/webpacker/components/Results/Records/RankingTypeTable.jsx b/app/webpacker/components/Results/Records/RankingTypeTable.jsx deleted file mode 100644 index a1d8cfa568f..00000000000 --- a/app/webpacker/components/Results/Records/RankingTypeTable.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { separateRecordsConfig } from '../TableRows'; -import DataTable from './DataTable'; - -export default function RankingTypeTable({ results, rankingType }) { - return ( - - ); -} diff --git a/app/webpacker/components/Results/Records/RecordsTable.jsx b/app/webpacker/components/Results/Records/RecordsTable.jsx index 4c952817815..3e204b48066 100644 --- a/app/webpacker/components/Results/Records/RecordsTable.jsx +++ b/app/webpacker/components/Results/Records/RecordsTable.jsx @@ -1,16 +1,30 @@ -import {useQuery} from '@tanstack/react-query'; -import {Segment} from 'semantic-ui-react'; +import { useQuery } from '@tanstack/react-query'; +import { Segment } from 'semantic-ui-react'; import React from 'react'; -import {getRecords} from '../api/records'; +import { getRecords } from '../api/records'; import Loading from '../../Requests/Loading'; -import MixedRecordsTables from './MixedRecordsTables'; -import SlimRecordTable from './SlimRecordsTable'; -import HistoryRecordsTables from './HistoryRecordsTables'; -import MixedHistoryRecordsTable from './MixedHistoryRecordsTable'; -import {augmentApiResults} from './utils'; +import { augmentApiResults } from './utils'; import GroupedEventsTable from './GroupedEventsTable'; import GroupedRankingTypesTable from './GroupedRankingTypesTable'; -import RankingTypeTable from './RankingTypeTable'; +import { + historyConfig, + mixedRecordsConfig, + separateRecordsConfig, + slimConfig, +} from '../TableRows'; +import DataTable from '../DataTable'; + +function SlimRecordsTable({ results }) { + const [slimmedRows] = results; + + // Need to re-key with `single` and `average` indices so that React-Table + // will have an easier time operating on the data. + const slimmedData = slimmedRows.map(([single, average]) => ({ single, average })); + + return ( + + ); +} export default function RecordsTable({ filterState }) { const { @@ -23,32 +37,27 @@ export default function RecordsTable({ filterState }) { select: (recordsData) => augmentApiResults(recordsData, show), }); - if (isFetching) { - return ; - } - - if (rows.length === 0) { - return No results found; - } + if (isFetching) return ; + if (rows.length === 0) return No results found; switch (show) { case 'mixed': return ( - {(eventResults) => } + {(eventResults) => } ); case 'slim': - return ; + return ; case 'separate': return ( {(rankingResults, rankingType) => ( - )} @@ -57,12 +66,12 @@ export default function RecordsTable({ filterState }) { case 'history': return ( - {(eventResults) => } + {(eventResults) => } ); case 'mixed history': - return ; + return ; default: console.error(`Invalid record table: ${show}`); diff --git a/app/webpacker/components/Results/Records/SlimRecordsTable.jsx b/app/webpacker/components/Results/Records/SlimRecordsTable.jsx deleted file mode 100644 index d42c59b3917..00000000000 --- a/app/webpacker/components/Results/Records/SlimRecordsTable.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import { slimConfig } from '../TableRows'; -import DataTable from './DataTable'; - -export default function SlimRecordsTable({ results }) { - const [slimmedRows] = results; - - // Need to re-key with `single` and `average` indices so that React-Table - // will have an easier time operating on the data. - const slimmedData = slimmedRows.map(([single, average]) => ({ single, average })); - console.log(slimmedData); - - return ( - - ); -} diff --git a/app/webpacker/components/Results/TableCells.jsx b/app/webpacker/components/Results/TableCells.jsx index 0cd07d1d34a..0c6204bc83d 100644 --- a/app/webpacker/components/Results/TableCells.jsx +++ b/app/webpacker/components/Results/TableCells.jsx @@ -20,6 +20,7 @@ export function AttemptsCells({ attempts, bestResultIndex, worstResultIndex, eventId, }) { return attempts.map((a, i) => ( + // One Cell per Solve of an Average {attempts.filter(Boolean).length === 5 && (i === bestResultIndex || i === worstResultIndex) ? ( diff --git a/app/webpacker/components/Results/TableRows.jsx b/app/webpacker/components/Results/TableRows.jsx index 69200eaeb09..e33ccdba5c6 100644 --- a/app/webpacker/components/Results/TableRows.jsx +++ b/app/webpacker/components/Results/TableRows.jsx @@ -26,7 +26,7 @@ function resultAttempts(result) { return [attempts, bestResultIndex, worstResultIndex]; } -const resultsFiveWideColumn = { +export const resultsFiveWideColumn = { accessorKey: 'result', header: I18n.t('results.table_elements.solves'), colSpan: 5, @@ -46,7 +46,7 @@ const resultsFiveWideColumn = { }, }; -const competitionColumn = { +export const competitionColumn = { accessorKey: 'competition', header: I18n.t('results.table_elements.competition'), cell: ({ getValue }) => ( @@ -57,7 +57,7 @@ const competitionColumn = { ), }; -const regionColumn = { +export const regionColumn = { accessorKey: 'country', header: I18n.t('results.table_elements.region'), cell: ({ getValue }) => ( @@ -65,7 +65,15 @@ const regionColumn = { ), }; -const attemptResultColumn = { +export const representingColumn = { + accessorKey: 'country', + header: I18n.t('results.table_elements.region'), + cell: ({ getValue }) => ( + + ), +}; + +export const attemptResultColumn = { accessorKey: 'result.value', header: I18n.t('results.table_elements.result'), cell: ({ row, getValue }) => ( @@ -75,7 +83,7 @@ const attemptResultColumn = { ), }; -const personColumn = { +export const personColumn = { accessorKey: 'result.name', header: I18n.t('results.table_elements.name'), cell: ({ row }) => ( @@ -92,6 +100,14 @@ const eventColumn = { cell: ({ getValue }) => , }; +export const rankColumn = { + accessorKey: 'rank', + header: '#', + cell: ({ getValue }) => ( + {getValue()} + ), +}; + export const slimConfig = [ { accessorKey: 'single.personName', From e9aebc971109bc0707f537ee7584506f973e9ac0 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Wed, 5 Feb 2025 17:33:20 +0900 Subject: [PATCH 54/70] Hack rendering exception for multi-column cell --- .../components/Results/DataTable.jsx | 14 ++++- .../components/Results/TableCells.jsx | 22 +++---- .../components/Results/TableRows.jsx | 57 +++++++------------ 3 files changed, 43 insertions(+), 50 deletions(-) diff --git a/app/webpacker/components/Results/DataTable.jsx b/app/webpacker/components/Results/DataTable.jsx index 81a8786b056..2b136df9869 100644 --- a/app/webpacker/components/Results/DataTable.jsx +++ b/app/webpacker/components/Results/DataTable.jsx @@ -28,7 +28,19 @@ export default function DataTable({ rows, config }) { {table.getRowModel().rows.map((row) => ( - {row.getVisibleCells().map((cell) => flexRender(cell.column.columnDef.cell, cell.getContext()))} + {row.getVisibleCells().map((cell) => ( + cell.column.columnDef.isMultiAttemptsHack + ? ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ) + : ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ) + ))} ))} diff --git a/app/webpacker/components/Results/TableCells.jsx b/app/webpacker/components/Results/TableCells.jsx index 0c6204bc83d..2bdf7b845cf 100644 --- a/app/webpacker/components/Results/TableCells.jsx +++ b/app/webpacker/components/Results/TableCells.jsx @@ -8,11 +8,11 @@ import { events } from '../../lib/wca-data.js.erb'; export function CountryCell({ country }) { return ( - + <> {country.iso2 && } {' '} {country.name} - + ); } @@ -20,8 +20,10 @@ export function AttemptsCells({ attempts, bestResultIndex, worstResultIndex, eventId, }) { return attempts.map((a, i) => ( - // One Cell per Solve of an Average - + // One Cell per Solve of an Average. The exact same result may occur multiple times + // in the same average (think FMC), so we use the iteration index as key. + // eslint-disable-next-line react/no-array-index-key + {attempts.filter(Boolean).length === 5 && (i === bestResultIndex || i === worstResultIndex) ? ( `(${formatAttemptResult(a, eventId)})` @@ -40,28 +42,26 @@ export function CompetitionCell({ competition, compatIso2 }) { const iso2 = compatIso2 || competition.country.iso2; return ( - + <> {' '} {competition.cellName} - + ); } export function PersonCell({ personId, personName }) { return ( - - {personName} - + {personName} ); } export function EventCell({ eventId }) { return ( - + <> {' '} {events.byId[eventId].name} - + ); } diff --git a/app/webpacker/components/Results/TableRows.jsx b/app/webpacker/components/Results/TableRows.jsx index e33ccdba5c6..3da696c7622 100644 --- a/app/webpacker/components/Results/TableRows.jsx +++ b/app/webpacker/components/Results/TableRows.jsx @@ -30,6 +30,7 @@ export const resultsFiveWideColumn = { accessorKey: 'result', header: I18n.t('results.table_elements.solves'), colSpan: 5, + isMultiAttemptsHack: true, cell: ({ getValue }) => { const result = getValue(); @@ -76,11 +77,7 @@ export const representingColumn = { export const attemptResultColumn = { accessorKey: 'result.value', header: I18n.t('results.table_elements.result'), - cell: ({ row, getValue }) => ( - - {formatAttemptResult(getValue(), row.original.result.eventId)} - - ), + cell: ({ row, getValue }) => formatAttemptResult(getValue(), row.original.result.eventId), }; export const personColumn = { @@ -103,9 +100,8 @@ const eventColumn = { export const rankColumn = { accessorKey: 'rank', header: '#', - cell: ({ getValue }) => ( - {getValue()} - ), + textAlign: 'center', + cell: ({ getValue }) => getValue(), }; export const slimConfig = [ @@ -122,11 +118,7 @@ export const slimConfig = [ { accessorKey: 'single.value', header: I18n.t('common.single'), - cell: ({ row, getValue }) => ( - - {formatAttemptResult(getValue(), row.original.single.eventId)} - - ), + cell: ({ row, getValue }) => formatAttemptResult(getValue(), row.original.single.eventId), }, { accessorKey: 'single.eventId', @@ -137,29 +129,24 @@ export const slimConfig = [ accessorFn: (res) => res.average?.value, header: I18n.t('common.average'), cell: ({ row, getValue }) => ( - - {getValue() && formatAttemptResult(getValue(), row.original.average?.eventId)} - + getValue() && formatAttemptResult(getValue(), row.original.average?.eventId) ), }, { accessorFn: (res) => res.average?.personName, header: I18n.t('results.table_elements.name'), - cell: ({ row, getValue }) => { - if (getValue() === undefined) return ; - - return ( - - ); - }, + cell: ({ row, getValue }) => getValue && ( + + ), }, { accessorFn: (res) => res.average, header: I18n.t('results.table_elements.solves'), colSpan: 5, + isMultiAttemptsHack: true, cell: ({ getValue }) => { const result = getValue(); @@ -192,9 +179,7 @@ export const historyConfig = (isMixed) => [ { accessorKey: 'result.start_date', header: I18n.t('results.table_elements.date_circa'), - cell: ({ getValue }) => ( - {DateTime.fromISO(getValue()).toFormat('MMM dd, yyyy')} - ), + cell: ({ getValue }) => DateTime.fromISO(getValue()).toFormat('MMM dd, yyyy'), }, isMixed && eventColumn, personColumn, @@ -202,18 +187,16 @@ export const historyConfig = (isMixed) => [ accessorKey: 'result.value', header: I18n.t('common.single'), cell: ({ row, getValue }) => ( - - {row.original.result.type === 'single' && formatAttemptResult(getValue(), row.original.result.eventId)} - + row.original.result.type === 'single' + && formatAttemptResult(getValue(), row.original.result.eventId) ), }, { accessorKey: 'result.value', header: I18n.t('common.average'), cell: ({ row, getValue }) => ( - - {row.original.result.type === 'average' && formatAttemptResult(getValue(), row.original.result.eventId)} - + row.original.result.type === 'average' + && formatAttemptResult(getValue(), row.original.result.eventId) ), }, regionColumn, @@ -225,9 +208,7 @@ export const mixedRecordsConfig = [ { accessorKey: 'result.type', header: I18n.t('results.selector_elements.type_selector.type'), - cell: ({ getValue }) => ( - {I18n.t(`results.selector_elements.type_selector.${getValue()}`)} - ), + cell: ({ getValue }) => I18n.t(`results.selector_elements.type_selector.${getValue()}`), }, personColumn, attemptResultColumn, From 2dd74f74d2924e80c9449e3051c8e848feffbec6 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Wed, 5 Feb 2025 17:44:08 +0900 Subject: [PATCH 55/70] Use accessorKey for slim averages --- app/webpacker/components/Results/TableRows.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/webpacker/components/Results/TableRows.jsx b/app/webpacker/components/Results/TableRows.jsx index 3da696c7622..b7b9fbcb685 100644 --- a/app/webpacker/components/Results/TableRows.jsx +++ b/app/webpacker/components/Results/TableRows.jsx @@ -126,14 +126,14 @@ export const slimConfig = [ cell: ({ getValue }) => , }, { - accessorFn: (res) => res.average?.value, + accessorKey: 'average.value', header: I18n.t('common.average'), cell: ({ row, getValue }) => ( getValue() && formatAttemptResult(getValue(), row.original.average?.eventId) ), }, { - accessorFn: (res) => res.average?.personName, + accessorKey: 'average.personName', header: I18n.t('results.table_elements.name'), cell: ({ row, getValue }) => getValue && ( res.average, + accessorKey: 'average', header: I18n.t('results.table_elements.solves'), colSpan: 5, isMultiAttemptsHack: true, From 5372707e570ef9bbb53a16e9548a6b0a3b32e426 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Wed, 5 Feb 2025 17:52:29 +0900 Subject: [PATCH 56/70] Fix duplicate key issue in history view --- app/webpacker/components/Results/TableRows.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/webpacker/components/Results/TableRows.jsx b/app/webpacker/components/Results/TableRows.jsx index b7b9fbcb685..de2f7523886 100644 --- a/app/webpacker/components/Results/TableRows.jsx +++ b/app/webpacker/components/Results/TableRows.jsx @@ -184,6 +184,7 @@ export const historyConfig = (isMixed) => [ isMixed && eventColumn, personColumn, { + id: 'single', accessorKey: 'result.value', header: I18n.t('common.single'), cell: ({ row, getValue }) => ( @@ -192,6 +193,7 @@ export const historyConfig = (isMixed) => [ ), }, { + id: 'average', accessorKey: 'result.value', header: I18n.t('common.average'), cell: ({ row, getValue }) => ( From f2ced0c0e3c08f0b9bac71675266c1d7d37a126c Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Wed, 5 Feb 2025 17:59:52 +0900 Subject: [PATCH 57/70] Shuffle column def code around --- .../Results/Rankings/RankingsTable.jsx | 2 +- .../Results/Records/RecordsTable.jsx | 4 +- .../components/Results/Records/utils.js | 113 +++++++++ .../components/Results/TableColumns.jsx | 106 +++++++++ .../components/Results/TableRows.jsx | 220 ------------------ 5 files changed, 222 insertions(+), 223 deletions(-) create mode 100644 app/webpacker/components/Results/TableColumns.jsx delete mode 100644 app/webpacker/components/Results/TableRows.jsx diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 1d733ae4c7e..5b2bfbd4452 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -13,7 +13,7 @@ import { regionColumn, representingColumn, resultsFiveWideColumn, -} from '../TableRows'; +} from '../TableColumns'; function getCountryOrContinent(result, firstContinentIndex, firstCountryIndex, index) { if (index < firstContinentIndex) { diff --git a/app/webpacker/components/Results/Records/RecordsTable.jsx b/app/webpacker/components/Results/Records/RecordsTable.jsx index 3e204b48066..86bf8209e75 100644 --- a/app/webpacker/components/Results/Records/RecordsTable.jsx +++ b/app/webpacker/components/Results/Records/RecordsTable.jsx @@ -3,15 +3,15 @@ import { Segment } from 'semantic-ui-react'; import React from 'react'; import { getRecords } from '../api/records'; import Loading from '../../Requests/Loading'; -import { augmentApiResults } from './utils'; import GroupedEventsTable from './GroupedEventsTable'; import GroupedRankingTypesTable from './GroupedRankingTypesTable'; import { + augmentApiResults, historyConfig, mixedRecordsConfig, separateRecordsConfig, slimConfig, -} from '../TableRows'; +} from './utils'; import DataTable from '../DataTable'; function SlimRecordsTable({ results }) { diff --git a/app/webpacker/components/Results/Records/utils.js b/app/webpacker/components/Results/Records/utils.js index e980aba77f7..a6496e43818 100644 --- a/app/webpacker/components/Results/Records/utils.js +++ b/app/webpacker/components/Results/Records/utils.js @@ -1,4 +1,20 @@ +import { DateTime } from 'luxon'; +import React from 'react'; import { countries } from '../../../lib/wca-data.js.erb'; +import I18n from '../../../lib/i18n'; +import { + EventCell, + PersonCell, +} from '../TableCells'; +import { formatAttemptResult } from '../../../lib/wca-live/attempts'; +import { + attemptResultColumn, + competitionColumn, + personColumn, + regionColumn, + resultsFiveWideColumn, + eventColumn, +} from '../TableColumns'; export function augmentResults(results, competitionsById) { return results.map((result) => { @@ -34,3 +50,100 @@ export function augmentApiResults(data, show) { return augmentResults(rows, competitionsById); } + +export const slimConfig = [ + { + accessorKey: 'single.personName', + header: I18n.t('results.table_elements.name'), + cell: ({ row, getValue }) => ( + + ), + }, + { + accessorKey: 'single.value', + header: I18n.t('common.single'), + cell: ({ row, getValue }) => formatAttemptResult(getValue(), row.original.single.eventId), + }, + { + accessorKey: 'single.eventId', + header: I18n.t('results.table_elements.event'), + cell: ({ getValue }) => , + }, + { + accessorKey: 'average.value', + header: I18n.t('common.average'), + cell: ({ row, getValue }) => ( + getValue() && formatAttemptResult(getValue(), row.original.average?.eventId) + ), + }, + { + accessorKey: 'average.personName', + header: I18n.t('results.table_elements.name'), + cell: ({ row, getValue }) => getValue && ( + + ), + }, + { + ...resultsFiveWideColumn, + accessorKey: 'average', + }, +]; + +export const separateRecordsConfig = (rankingType) => [ + eventColumn, + attemptResultColumn, + personColumn, + regionColumn, + competitionColumn, + rankingType === 'average' && resultsFiveWideColumn, +].filter(Boolean); + +export const historyConfig = (isMixed) => [ + { + accessorKey: 'result.start_date', + header: I18n.t('results.table_elements.date_circa'), + cell: ({ getValue }) => DateTime.fromISO(getValue()).toFormat('MMM dd, yyyy'), + }, + isMixed && eventColumn, + personColumn, + { + id: 'single', + accessorKey: 'result.value', + header: I18n.t('common.single'), + cell: ({ row, getValue }) => ( + row.original.result.type === 'single' + && formatAttemptResult(getValue(), row.original.result.eventId) + ), + }, + { + id: 'average', + accessorKey: 'result.value', + header: I18n.t('common.average'), + cell: ({ row, getValue }) => ( + row.original.result.type === 'average' + && formatAttemptResult(getValue(), row.original.result.eventId) + ), + }, + regionColumn, + competitionColumn, + resultsFiveWideColumn, +].filter(Boolean); + +export const mixedRecordsConfig = [ + { + accessorKey: 'result.type', + header: I18n.t('results.selector_elements.type_selector.type'), + cell: ({ getValue }) => I18n.t(`results.selector_elements.type_selector.${getValue()}`), + }, + personColumn, + attemptResultColumn, + regionColumn, + competitionColumn, + resultsFiveWideColumn, +]; diff --git a/app/webpacker/components/Results/TableColumns.jsx b/app/webpacker/components/Results/TableColumns.jsx new file mode 100644 index 00000000000..460a1ed7ac0 --- /dev/null +++ b/app/webpacker/components/Results/TableColumns.jsx @@ -0,0 +1,106 @@ +import React from 'react'; +import _ from 'lodash'; +import { Table } from 'semantic-ui-react'; +import { formatAttemptResult } from '../../lib/wca-live/attempts'; +import I18n from '../../lib/i18n'; +import { countries } from '../../lib/wca-data.js.erb'; +import { + AttemptsCells, + CompetitionCell, + CountryCell, + EventCell, + PersonCell, +} from './TableCells'; + +function resultAttempts(result) { + const attempts = [result?.value1, result?.value2, result?.value3, result?.value4, result?.value5] + .filter(Boolean); + + const bestResult = _.max(attempts); + const worstResult = _.min(attempts); + + const bestResultIndex = attempts.indexOf(bestResult); + const worstResultIndex = attempts.indexOf(worstResult); + + return [attempts, bestResultIndex, worstResultIndex]; +} + +export const resultsFiveWideColumn = { + accessorKey: 'result', + header: I18n.t('results.table_elements.solves'), + colSpan: 5, + isMultiAttemptsHack: true, + cell: ({ getValue }) => { + const result = getValue(); + + if (!result) return (); + + const [attempts, bestResultIndex, worstResultIndex] = resultAttempts(result); + + return ( + + ); + }, +}; + +export const competitionColumn = { + accessorKey: 'competition', + header: I18n.t('results.table_elements.competition'), + cell: ({ getValue }) => ( + + ), +}; + +export const regionColumn = { + accessorKey: 'country', + header: I18n.t('results.table_elements.region'), + cell: ({ getValue }) => ( + + ), +}; + +export const representingColumn = { + accessorKey: 'country', + header: I18n.t('results.table_elements.region'), + cell: ({ getValue }) => ( + + ), +}; + +export const attemptResultColumn = { + accessorKey: 'result.value', + header: I18n.t('results.table_elements.result'), + cell: ({ row, getValue }) => formatAttemptResult(getValue(), row.original.result.eventId), +}; + +export const personColumn = { + accessorKey: 'result.personName', + header: I18n.t('results.table_elements.name'), + cell: ({ row, getValue }) => ( + + ), +}; + +export const eventColumn = { + accessorKey: 'result.eventId', + header: I18n.t('results.table_elements.event'), + cell: ({ getValue }) => , +}; + +export const rankColumn = { + accessorKey: 'rank', + header: '#', + textAlign: 'center', + cell: ({ getValue }) => getValue(), +}; diff --git a/app/webpacker/components/Results/TableRows.jsx b/app/webpacker/components/Results/TableRows.jsx deleted file mode 100644 index de2f7523886..00000000000 --- a/app/webpacker/components/Results/TableRows.jsx +++ /dev/null @@ -1,220 +0,0 @@ -import React from 'react'; -import _ from 'lodash'; -import { Table } from 'semantic-ui-react'; -import { DateTime } from 'luxon'; -import { formatAttemptResult } from '../../lib/wca-live/attempts'; -import I18n from '../../lib/i18n'; -import { countries } from '../../lib/wca-data.js.erb'; -import { - AttemptsCells, - CompetitionCell, - CountryCell, - EventCell, - PersonCell, -} from './TableCells'; - -function resultAttempts(result) { - const attempts = [result?.value1, result?.value2, result?.value3, result?.value4, result?.value5] - .filter(Boolean); - - const bestResult = _.max(attempts); - const worstResult = _.min(attempts); - - const bestResultIndex = attempts.indexOf(bestResult); - const worstResultIndex = attempts.indexOf(worstResult); - - return [attempts, bestResultIndex, worstResultIndex]; -} - -export const resultsFiveWideColumn = { - accessorKey: 'result', - header: I18n.t('results.table_elements.solves'), - colSpan: 5, - isMultiAttemptsHack: true, - cell: ({ getValue }) => { - const result = getValue(); - - const [attempts, bestResultIndex, worstResultIndex] = resultAttempts(result); - - return ( - - ); - }, -}; - -export const competitionColumn = { - accessorKey: 'competition', - header: I18n.t('results.table_elements.competition'), - cell: ({ getValue }) => ( - - ), -}; - -export const regionColumn = { - accessorKey: 'country', - header: I18n.t('results.table_elements.region'), - cell: ({ getValue }) => ( - - ), -}; - -export const representingColumn = { - accessorKey: 'country', - header: I18n.t('results.table_elements.region'), - cell: ({ getValue }) => ( - - ), -}; - -export const attemptResultColumn = { - accessorKey: 'result.value', - header: I18n.t('results.table_elements.result'), - cell: ({ row, getValue }) => formatAttemptResult(getValue(), row.original.result.eventId), -}; - -export const personColumn = { - accessorKey: 'result.name', - header: I18n.t('results.table_elements.name'), - cell: ({ row }) => ( - - ), -}; - -const eventColumn = { - accessorKey: 'result.eventId', - header: I18n.t('results.table_elements.event'), - cell: ({ getValue }) => , -}; - -export const rankColumn = { - accessorKey: 'rank', - header: '#', - textAlign: 'center', - cell: ({ getValue }) => getValue(), -}; - -export const slimConfig = [ - { - accessorKey: 'single.personName', - header: I18n.t('results.table_elements.name'), - cell: ({ row, getValue }) => ( - - ), - }, - { - accessorKey: 'single.value', - header: I18n.t('common.single'), - cell: ({ row, getValue }) => formatAttemptResult(getValue(), row.original.single.eventId), - }, - { - accessorKey: 'single.eventId', - header: I18n.t('results.table_elements.event'), - cell: ({ getValue }) => , - }, - { - accessorKey: 'average.value', - header: I18n.t('common.average'), - cell: ({ row, getValue }) => ( - getValue() && formatAttemptResult(getValue(), row.original.average?.eventId) - ), - }, - { - accessorKey: 'average.personName', - header: I18n.t('results.table_elements.name'), - cell: ({ row, getValue }) => getValue && ( - - ), - }, - { - accessorKey: 'average', - header: I18n.t('results.table_elements.solves'), - colSpan: 5, - isMultiAttemptsHack: true, - cell: ({ getValue }) => { - const result = getValue(); - - if (!result) return ; - - const [attempts, bestResultIndex, worstResultIndex] = resultAttempts(result); - - return ( - - ); - }, - }, -]; - -export const separateRecordsConfig = (rankingType) => [ - eventColumn, - attemptResultColumn, - personColumn, - regionColumn, - competitionColumn, - rankingType === 'average' && resultsFiveWideColumn, -].filter(Boolean); - -export const historyConfig = (isMixed) => [ - { - accessorKey: 'result.start_date', - header: I18n.t('results.table_elements.date_circa'), - cell: ({ getValue }) => DateTime.fromISO(getValue()).toFormat('MMM dd, yyyy'), - }, - isMixed && eventColumn, - personColumn, - { - id: 'single', - accessorKey: 'result.value', - header: I18n.t('common.single'), - cell: ({ row, getValue }) => ( - row.original.result.type === 'single' - && formatAttemptResult(getValue(), row.original.result.eventId) - ), - }, - { - id: 'average', - accessorKey: 'result.value', - header: I18n.t('common.average'), - cell: ({ row, getValue }) => ( - row.original.result.type === 'average' - && formatAttemptResult(getValue(), row.original.result.eventId) - ), - }, - regionColumn, - competitionColumn, - resultsFiveWideColumn, -].filter(Boolean); - -export const mixedRecordsConfig = [ - { - accessorKey: 'result.type', - header: I18n.t('results.selector_elements.type_selector.type'), - cell: ({ getValue }) => I18n.t(`results.selector_elements.type_selector.${getValue()}`), - }, - personColumn, - attemptResultColumn, - regionColumn, - competitionColumn, - resultsFiveWideColumn, -]; From e29990453a05efc11749ae7b344fef599c698fac Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Wed, 5 Feb 2025 18:51:14 +0900 Subject: [PATCH 58/70] Fix results DNF/DNS quirk --- .../components/Results/TableColumns.jsx | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/app/webpacker/components/Results/TableColumns.jsx b/app/webpacker/components/Results/TableColumns.jsx index 460a1ed7ac0..12a47d0cb33 100644 --- a/app/webpacker/components/Results/TableColumns.jsx +++ b/app/webpacker/components/Results/TableColumns.jsx @@ -13,16 +13,24 @@ import { } from './TableCells'; function resultAttempts(result) { - const attempts = [result?.value1, result?.value2, result?.value3, result?.value4, result?.value5] - .filter(Boolean); + const definedAttempts = [ + result?.value1, + result?.value2, + result?.value3, + result?.value4, + result?.value5, + ].filter((res) => res !== undefined); - const bestResult = _.max(attempts); - const worstResult = _.min(attempts); + const validAttempts = definedAttempts.filter((res) => res !== 0); + const completedAttempts = validAttempts.filter((res) => res > 0); - const bestResultIndex = attempts.indexOf(bestResult); - const worstResultIndex = attempts.indexOf(worstResult); + const worstResult = _.max(completedAttempts); + const bestResult = _.min(completedAttempts); - return [attempts, bestResultIndex, worstResultIndex]; + const bestResultIndex = definedAttempts.indexOf(bestResult); + const worstResultIndex = definedAttempts.indexOf(worstResult); + + return [definedAttempts, bestResultIndex, worstResultIndex]; } export const resultsFiveWideColumn = { From b18d903c3cad787e81a9ba36b97c83b9d83f8cc0 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Wed, 5 Feb 2025 19:08:34 +0900 Subject: [PATCH 59/70] Refactor controller results grouping into common method --- app/controllers/results_controller.rb | 61 +++++++++++++-------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index fdb5d369c58..07a734c01f5 100644 --- a/app/controllers/results_controller.rb +++ b/app/controllers/results_controller.rb @@ -161,22 +161,8 @@ def rankings @ranking_timestamp = ComputeAuxiliaryData.successful_start_date || Date.current - respond_to do |format| - format.html {} - format.json do - cached_data = Rails.cache.fetch ["results-page-api", *@cache_params, @ranking_timestamp] do - rows = DbHelper.execute_cached_query(@cache_params, @ranking_timestamp, @query) - comp_ids = rows.map { |r| r["competitionId"] }.uniq - if @is_by_region - rows = compute_rankings_by_region(rows, @continent, @country) - end - competitions_by_id = Competition.where(id: comp_ids).index_by(&:id).transform_values { |comp| comp.as_json(methods: %w[country], include: [], only: %w[cellName id]) } - { - rows: rows.as_json, competitionsById: competitions_by_id - } - end - render json: cached_data - end + respond_from_cache("results-page-api") do |rows| + @is_by_region ? compute_rankings_by_region(rows, @continent, @country) : rows end end @@ -265,22 +251,8 @@ def records @record_timestamp = ComputeAuxiliaryData.successful_start_date || Date.current - respond_to do |format| - format.html {} - format.json do - cached_data = Rails.cache.fetch ["records-page-api", *@cache_params, @record_timestamp] do - rows = DbHelper.execute_cached_query(@cache_params, @record_timestamp, @query) - comp_ids = rows.map { |r| r["competitionId"] }.uniq - if @is_slim || @is_separate - rows = compute_slim_or_separate_records(rows) - end - competitions_by_id = Competition.where(id: comp_ids).index_by(&:id).transform_values { |comp| comp.as_json(methods: %w[country], include: [], only: %w[cellName id]) } - { - rows: rows.as_json, competitionsById: competitions_by_id - } - end - render json: cached_data - end + respond_from_cache("records-page-api") do |rows| + @is_slim || @is_separate ? compute_slim_or_separate_records(rows) : rows end end @@ -457,4 +429,29 @@ def records rows_to_display = world_rows + continents_rows + countries_rows [rows_to_display, first_continent_index, first_country_index] end + + private def respond_from_cache(key_prefix, &) + respond_to do |format| + format.html {} + format.json do + cached_data = Rails.cache.fetch [key_prefix, *@cache_params, @record_timestamp] do + rows = DbHelper.execute_cached_query(@cache_params, @record_timestamp, @query) + + # First, extract unique competitions + comp_ids = rows.map { |r| r["competitionId"] }.uniq + competitions_by_id = Competition.where(id: comp_ids) + .index_by(&:id) + .transform_values { |comp| comp.as_json(methods: %w[country], include: [], only: %w[cellName id]) } + + # Now that we've remembered all competitions, we can safely transform the rows + rows = yield rows if block_given? + + { + rows: rows.as_json, competitionsById: competitions_by_id + } + end + render json: cached_data + end + end + end end From 618d7bf79d57ff4412a3a4966c5959481e6282e8 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Wed, 5 Feb 2025 22:08:35 +0900 Subject: [PATCH 60/70] Fix null pointer for tied average records in Slim view --- app/webpacker/components/Results/Records/utils.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/webpacker/components/Results/Records/utils.js b/app/webpacker/components/Results/Records/utils.js index a6496e43818..41110f773e1 100644 --- a/app/webpacker/components/Results/Records/utils.js +++ b/app/webpacker/components/Results/Records/utils.js @@ -55,7 +55,7 @@ export const slimConfig = [ { accessorKey: 'single.personName', header: I18n.t('results.table_elements.name'), - cell: ({ row, getValue }) => ( + cell: ({ row, getValue }) => getValue() && ( formatAttemptResult(getValue(), row.original.single.eventId), + cell: ({ row, getValue }) => ( + getValue() && formatAttemptResult(getValue(), row.original.single.eventId) + ), }, { accessorKey: 'single.eventId', header: I18n.t('results.table_elements.event'), - cell: ({ getValue }) => , + cell: ({ getValue }) => getValue() && , }, { accessorKey: 'average.value', From ae8dafc0cf9b0ecdb0d3c214ddb0a99bdb96151f Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Wed, 5 Feb 2025 22:42:23 +0900 Subject: [PATCH 61/70] No pop-ups for Country Flags --- app/webpacker/components/Results/TableCells.jsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/webpacker/components/Results/TableCells.jsx b/app/webpacker/components/Results/TableCells.jsx index 2bdf7b845cf..99ba8f2e553 100644 --- a/app/webpacker/components/Results/TableCells.jsx +++ b/app/webpacker/components/Results/TableCells.jsx @@ -1,6 +1,5 @@ import React from 'react'; -import { Table } from 'semantic-ui-react'; -import CountryFlag from '../wca/CountryFlag'; +import { Flag, Table } from 'semantic-ui-react'; import EventIcon from '../wca/EventIcon'; import { competitionUrl, personUrl } from '../../lib/requests/routes.js.erb'; import { formatAttemptResult } from '../../lib/wca-live/attempts'; @@ -9,7 +8,7 @@ import { events } from '../../lib/wca-data.js.erb'; export function CountryCell({ country }) { return ( <> - {country.iso2 && } + {country.iso2 && } {' '} {country.name} @@ -43,7 +42,7 @@ export function CompetitionCell({ competition, compatIso2 }) { return ( <> - + {' '} {competition.cellName} From ea8afcbcd50c16e4d49becf6ab31af28c081841b Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Thu, 6 Feb 2025 16:07:26 +0900 Subject: [PATCH 62/70] Revert unrelated change on CompetitionsFilteers --- .../components/CompetitionsOverview/CompetitionsFilters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js b/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js index 7efb8f1a281..4a84261ac58 100644 --- a/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js +++ b/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js @@ -162,7 +162,7 @@ function TimeOrderButtonGroup({ filterState, dispatchFilter }) { return ( <> - + - { event !== '333mbf' && } + {event !== '333mbf' && }
)} From aa0d2407d8a8ec34fcf7d3b4fddf3876c305e4a7 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Thu, 6 Feb 2025 16:10:29 +0900 Subject: [PATCH 64/70] Review: Use 'clear' button instead of 'all' button in events selector --- app/webpacker/components/Results/ResultsFilter.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/webpacker/components/Results/ResultsFilter.jsx b/app/webpacker/components/Results/ResultsFilter.jsx index 6d7c763a27f..0e505186c31 100644 --- a/app/webpacker/components/Results/ResultsFilter.jsx +++ b/app/webpacker/components/Results/ResultsFilter.jsx @@ -48,8 +48,8 @@ export default function ResultsFilter({ title={I18n.t('results.selector_elements.events_selector.event')} selectedEvents={[event]} onEventSelection={({ eventId }) => setEvent(eventId)} - hideAllButton={!isRecords} - hideClearButton + hideAllButton + hideClearButton={!isRecords} showBreakBeforeButtons={false} />
From 4bbe0a4a3daa2a4ee7c1cf09c9e1d9ef150ac3be Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Thu, 6 Feb 2025 16:13:49 +0900 Subject: [PATCH 65/70] Review: Remove manually computed 'key' --- app/webpacker/components/Results/Records/utils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/webpacker/components/Results/Records/utils.js b/app/webpacker/components/Results/Records/utils.js index 41110f773e1..35adc4bb690 100644 --- a/app/webpacker/components/Results/Records/utils.js +++ b/app/webpacker/components/Results/Records/utils.js @@ -27,7 +27,6 @@ export function augmentResults(results, competitionsById) { result, competition, country, - key: `${result.id}-${result.type}`, }; }); } From 3dfb4f08570293c1ad3b6f18e97d53d3068c44a8 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Thu, 6 Feb 2025 16:14:43 +0900 Subject: [PATCH 66/70] Review: Code cleanup --- app/webpacker/components/Results/Records/utils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/webpacker/components/Results/Records/utils.js b/app/webpacker/components/Results/Records/utils.js index 35adc4bb690..9d8de4504e5 100644 --- a/app/webpacker/components/Results/Records/utils.js +++ b/app/webpacker/components/Results/Records/utils.js @@ -16,8 +16,10 @@ import { eventColumn, } from '../TableColumns'; -export function augmentResults(results, competitionsById) { +function augmentResults(results, competitionsById) { return results.map((result) => { + // This happens particularly during "Slim" view augmenting, + // where we iterate over [single, average] tuples and the average does not exist. if (result === null) return null; const competition = competitionsById[result.competitionId]; From 3da44c53e07f5b027764846079ec68d9f1335b63 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Thu, 6 Feb 2025 16:18:46 +0900 Subject: [PATCH 67/70] Consider DNF/DNS as worst attempt --- app/webpacker/components/Results/TableColumns.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/webpacker/components/Results/TableColumns.jsx b/app/webpacker/components/Results/TableColumns.jsx index 12a47d0cb33..147fba76b72 100644 --- a/app/webpacker/components/Results/TableColumns.jsx +++ b/app/webpacker/components/Results/TableColumns.jsx @@ -23,8 +23,12 @@ function resultAttempts(result) { const validAttempts = definedAttempts.filter((res) => res !== 0); const completedAttempts = validAttempts.filter((res) => res > 0); + const uncompletedAttempts = validAttempts.filter((res) => res < 0); - const worstResult = _.max(completedAttempts); + // DNF/DNS values are very small. If all solves were successful, + // then `uncompletedAttempts` is empty and the min is `undefined`, + // which means we fall back to the actually slowest value. + const worstResult = _.min(uncompletedAttempts) || _.max(validAttempts); const bestResult = _.min(completedAttempts); const bestResultIndex = definedAttempts.indexOf(bestResult); From e5f0a6a3f47ee2549b3108c37d39e23d294e5138 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Thu, 6 Feb 2025 16:24:33 +0900 Subject: [PATCH 68/70] Review: move Rankings selector fn to separatte utils file --- .../Results/Rankings/RankingsTable.jsx | 40 +------------------ .../components/Results/Rankings/utils.js | 39 ++++++++++++++++++ 2 files changed, 40 insertions(+), 39 deletions(-) create mode 100644 app/webpacker/components/Results/Rankings/utils.js diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 5b2bfbd4452..4462c274458 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -1,7 +1,5 @@ import React, { useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; -import I18n from '../../../lib/i18n'; -import { continents, countries } from '../../../lib/wca-data.js.erb'; import { getRankings } from '../api/rankings'; import Loading from '../../Requests/Loading'; import DataTable from '../DataTable'; @@ -14,43 +12,7 @@ import { representingColumn, resultsFiveWideColumn, } from '../TableColumns'; - -function getCountryOrContinent(result, firstContinentIndex, firstCountryIndex, index) { - if (index < firstContinentIndex) { - return { name: I18n.t('results.table_elements.world') }; - } - if (index >= firstContinentIndex && index < firstCountryIndex) { - return continents.real.find((c) => c.id === countries.byId[result.countryId].continentId); - } - return countries.byId[result.countryId]; -} - -function mapRankingsData(data, isByRegion) { - const { rows, competitionsById } = data; - const [rowsToMap, firstContinentIndex, firstCountryIndex] = isByRegion ? rows : [rows, 0, 0]; - - return rowsToMap.reduce((acc, result, index) => { - const competition = competitionsById[result.competitionId]; - const { value } = result; - - const previousItem = acc[acc.length - 1]; - const previousValue = previousItem?.result.value || 0; - const previousRank = previousItem?.rank || 0; - - const rank = value === previousValue ? previousRank : index + 1; - const tiedPrevious = rank === previousRank; - - const country = getCountryOrContinent(result, firstContinentIndex, firstCountryIndex, index); - - return [...acc, { - result, - competition, - country, - rank, - tiedPrevious, - }]; - }, []); -} +import { mapRankingsData } from './utils'; export default function RankingsTable({ filterState }) { const { diff --git a/app/webpacker/components/Results/Rankings/utils.js b/app/webpacker/components/Results/Rankings/utils.js new file mode 100644 index 00000000000..4c3d7b80b41 --- /dev/null +++ b/app/webpacker/components/Results/Rankings/utils.js @@ -0,0 +1,39 @@ +import I18n from '../../../lib/i18n'; +import { continents, countries } from '../../../lib/wca-data.js.erb'; + +function getCountryOrContinent(result, firstContinentIndex, firstCountryIndex, index) { + if (index < firstContinentIndex) { + return { name: I18n.t('results.table_elements.world') }; + } + if (index >= firstContinentIndex && index < firstCountryIndex) { + return continents.real.find((c) => c.id === countries.byId[result.countryId].continentId); + } + return countries.byId[result.countryId]; +} + +export function mapRankingsData(data, isByRegion) { + const { rows, competitionsById } = data; + const [rowsToMap, firstContinentIndex, firstCountryIndex] = isByRegion ? rows : [rows, 0, 0]; + + return rowsToMap.reduce((acc, result, index) => { + const competition = competitionsById[result.competitionId]; + const { value } = result; + + const previousItem = acc[acc.length - 1]; + const previousValue = previousItem?.result.value || 0; + const previousRank = previousItem?.rank || 0; + + const rank = value === previousValue ? previousRank : index + 1; + const tiedPrevious = rank === previousRank; + + const country = getCountryOrContinent(result, firstContinentIndex, firstCountryIndex, index); + + return [...acc, { + result, + competition, + country, + rank, + tiedPrevious, + }]; + }, []); +} From 645ca8716b9e3998268ca3a5a5ab09ea7fbc347f Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Thu, 6 Feb 2025 16:28:41 +0900 Subject: [PATCH 69/70] Move over config as well so that the selector fn in utils doesn't feel lonely --- .../Results/Rankings/RankingsTable.jsx | 26 +++---------------- .../components/Results/Rankings/utils.js | 18 +++++++++++++ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 4462c274458..afaacdd1f74 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -1,18 +1,9 @@ -import React, { useMemo } from 'react'; +import React from 'react'; import { useQuery } from '@tanstack/react-query'; import { getRankings } from '../api/rankings'; import Loading from '../../Requests/Loading'; import DataTable from '../DataTable'; -import { - attemptResultColumn, - competitionColumn, - personColumn, - rankColumn, - regionColumn, - representingColumn, - resultsFiveWideColumn, -} from '../TableColumns'; -import { mapRankingsData } from './utils'; +import { mapRankingsData, rankingsConfig } from './utils'; export default function RankingsTable({ filterState }) { const { @@ -21,24 +12,15 @@ export default function RankingsTable({ filterState }) { const isAverage = rankingType === 'average'; - const { data, isFetching } = useQuery({ + const { data: rows, isFetching } = useQuery({ queryKey: ['rankings', event, region, rankingType, gender, show], queryFn: () => getRankings(event, rankingType, region, gender, show), select: (rankingsData) => mapRankingsData(rankingsData, show === 'by region'), }); - const columns = useMemo(() => [ - show === 'by region' ? regionColumn : rankColumn, - personColumn, - attemptResultColumn, - show !== 'by region' && representingColumn, - competitionColumn, - isAverage && resultsFiveWideColumn, - ].filter(Boolean), [show, isAverage]); - if (isFetching) return ; return ( - + ); } diff --git a/app/webpacker/components/Results/Rankings/utils.js b/app/webpacker/components/Results/Rankings/utils.js index 4c3d7b80b41..acd5ebfe0b5 100644 --- a/app/webpacker/components/Results/Rankings/utils.js +++ b/app/webpacker/components/Results/Rankings/utils.js @@ -1,5 +1,14 @@ import I18n from '../../../lib/i18n'; import { continents, countries } from '../../../lib/wca-data.js.erb'; +import {useMemo} from "react"; +import { + attemptResultColumn, + competitionColumn, + personColumn, + rankColumn, + regionColumn, + representingColumn, resultsFiveWideColumn +} from "../TableColumns"; function getCountryOrContinent(result, firstContinentIndex, firstCountryIndex, index) { if (index < firstContinentIndex) { @@ -37,3 +46,12 @@ export function mapRankingsData(data, isByRegion) { }]; }, []); } + +export const rankingsConfig = (show, isAverage) => [ + show === 'by region' ? regionColumn : rankColumn, + personColumn, + attemptResultColumn, + show !== 'by region' && representingColumn, + competitionColumn, + isAverage && resultsFiveWideColumn, +].filter(Boolean); From b435069c0408350f198ce15a43ed9df382122c25 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Thu, 6 Feb 2025 16:29:22 +0900 Subject: [PATCH 70/70] ESLint --- app/webpacker/components/Results/Rankings/utils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/utils.js b/app/webpacker/components/Results/Rankings/utils.js index acd5ebfe0b5..e826629f340 100644 --- a/app/webpacker/components/Results/Rankings/utils.js +++ b/app/webpacker/components/Results/Rankings/utils.js @@ -1,14 +1,14 @@ import I18n from '../../../lib/i18n'; import { continents, countries } from '../../../lib/wca-data.js.erb'; -import {useMemo} from "react"; import { attemptResultColumn, competitionColumn, personColumn, rankColumn, regionColumn, - representingColumn, resultsFiveWideColumn -} from "../TableColumns"; + representingColumn, + resultsFiveWideColumn, +} from '../TableColumns'; function getCountryOrContinent(result, firstContinentIndex, firstCountryIndex, index) { if (index < firstContinentIndex) {