From 7db290fef0c20b33c5ee595187d37a31eb5bbac3 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 7 Jan 2025 17:52:50 +0100 Subject: [PATCH 01/39] create rankings table --- app/views/results/rankings.html.erb | 12 +-- .../components/Results/Rankings/index.jsx | 97 +++++++++++++++++++ config/i18n.yml | 1 + 3 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 app/webpacker/components/Results/Rankings/index.jsx diff --git a/app/views/results/rankings.html.erb b/app/views/results/rankings.html.erb index eea83d9a466..40186269409 100644 --- a/app/views/results/rankings.html.erb +++ b/app/views/results/rankings.html.erb @@ -16,14 +16,8 @@ comp_ids = @rows.map { |r| r["competitionId"] }.uniq @competitions_by_id = Hash[Competition.where(id: comp_ids).map { |c| [c.id, c] }] %> -
-
- <% if @is_by_region %> - <%= render 'rankings_by_region_table' %> - <% else %> - <%= render 'rankings_table' %> - <% end %> -
-
+ <%= react_component("Results/Rankings", { + rows: @rows.as_json, competitionsById: @competitions_by_id.as_json({ methods: %w[country name id], includes: [] }), isAverage: @is_average, isByRegion: @is_by_region, + }) %> <% end %> diff --git a/app/webpacker/components/Results/Rankings/index.jsx b/app/webpacker/components/Results/Rankings/index.jsx new file mode 100644 index 00000000000..5a2f90f5082 --- /dev/null +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -0,0 +1,97 @@ +import React, { useMemo } from 'react'; +import { Container, 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 { countries, events } from '../../../lib/wca-data.js.erb'; + +function ResultRow({ + result, competition, rank, isAverage, +}) { + const attempts = [result.value1, result.value2, result.value3, result.value4, result.value5]; + const bestResult = _.max(attempts); + const worstResult = _.min(attempts); + const bestResultIndex = attempts.findIndex((a) => a === bestResult); + const worstResultIndex = attempts.findIndex((a) => a === worstResult); + return ( + + {rank} + + {result.personName} + + + {formatAttemptResult(result.value, result.eventId)} + + + c.id === result.countryId).iso2} /> + + + + {' '} + {competition.name} + + {isAverage && (attempts.map((a, i) => ( + + { events.byId[result.eventId].format.expectedSolveCount === 5 + && (i === bestResultIndex || i === worstResultIndex) + ? `(${formatAttemptResult(a, result.eventId)})` : formatAttemptResult(a, result.eventId)} + + )) + )} + + ); +} + +export default function Rankings({ rows, competitionsById, isAverage }) { + const r = useMemo(() => { + let previousValue = 0; + let previousRank = 0; + return rows.map((result, index) => { + const competition = competitionsById[result.competitionId]; + const { value } = result; + const rank = value === previousValue ? previousRank : index + 1; + const tiedPrevious = rank === previousRank; + + previousValue = value; + previousRank = rank; + + return ( + + ); + }); + }, [competitionsById, isAverage, rows]); + + return ( + + + + # + {I18n.t('results.table_elements.name')} + {I18n.t('results.table_elements.result')} + {I18n.t('results.table_elements.representing')} + {I18n.t('results.table_elements.competition')} + {isAverage && ( + <> + {I18n.t('results.table_elements.solves')} + + + + + + )} + + + {r} + +
+
+ ); +} diff --git a/config/i18n.yml b/config/i18n.yml index 5f2ebbbc48e..61ec2172ea2 100644 --- a/config/i18n.yml +++ b/config/i18n.yml @@ -75,3 +75,4 @@ translations: - "*.time_limit.*" - "*.users.edit.*" - "*.persons.index.*" + - "*.results.table_elements.*" From e0d12caf85f12ded021f0acf24f78a0480583ad3 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Wed, 8 Jan 2025 14:25:18 +0100 Subject: [PATCH 02/39] create seperate index for filters --- .../Results/Rankings/RankingsTable.jsx | 97 +++++++++++++++++++ .../components/Results/Rankings/index.jsx | 97 ------------------- 2 files changed, 97 insertions(+), 97 deletions(-) create mode 100644 app/webpacker/components/Results/Rankings/RankingsTable.jsx diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx new file mode 100644 index 00000000000..c66f3018cb5 --- /dev/null +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -0,0 +1,97 @@ +import React, { useMemo } from 'react'; +import { Container, 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 { countries, events } from '../../../lib/wca-data.js.erb'; + +function ResultRow({ + result, competition, rank, isAverage, +}) { + const attempts = [result.value1, result.value2, result.value3, result.value4, result.value5]; + const bestResult = _.max(attempts); + const worstResult = _.min(attempts); + const bestResultIndex = attempts.findIndex((a) => a === bestResult); + const worstResultIndex = attempts.findIndex((a) => a === worstResult); + return ( + + {rank} + + {result.personName} + + + {formatAttemptResult(result.value, result.eventId)} + + + c.id === result.countryId).iso2} /> + + + + {' '} + {competition.name} + + {isAverage && (attempts.map((a, i) => ( + + { events.byId[result.eventId].format.expectedSolveCount === 5 + && (i === bestResultIndex || i === worstResultIndex) + ? `(${formatAttemptResult(a, result.eventId)})` : formatAttemptResult(a, result.eventId)} + + )) + )} + + ); +} + +export default function RankingsTable({ rows, competitionsById, isAverage }) { + const r = useMemo(() => { + let previousValue = 0; + let previousRank = 0; + return rows.map((result, index) => { + const competition = competitionsById[result.competitionId]; + const { value } = result; + const rank = value === previousValue ? previousRank : index + 1; + const tiedPrevious = rank === previousRank; + + previousValue = value; + previousRank = rank; + + return ( + + ); + }); + }, [competitionsById, isAverage, rows]); + + return ( + + + + # + {I18n.t('results.table_elements.name')} + {I18n.t('results.table_elements.result')} + {I18n.t('results.table_elements.representing')} + {I18n.t('results.table_elements.competition')} + {isAverage && ( + <> + {I18n.t('results.table_elements.solves')} + + + + + + )} + + + {r} + +
+
+ ); +} diff --git a/app/webpacker/components/Results/Rankings/index.jsx b/app/webpacker/components/Results/Rankings/index.jsx index 5a2f90f5082..e69de29bb2d 100644 --- a/app/webpacker/components/Results/Rankings/index.jsx +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -1,97 +0,0 @@ -import React, { useMemo } from 'react'; -import { Container, 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 { countries, events } from '../../../lib/wca-data.js.erb'; - -function ResultRow({ - result, competition, rank, isAverage, -}) { - const attempts = [result.value1, result.value2, result.value3, result.value4, result.value5]; - const bestResult = _.max(attempts); - const worstResult = _.min(attempts); - const bestResultIndex = attempts.findIndex((a) => a === bestResult); - const worstResultIndex = attempts.findIndex((a) => a === worstResult); - return ( - - {rank} - - {result.personName} - - - {formatAttemptResult(result.value, result.eventId)} - - - c.id === result.countryId).iso2} /> - - - - {' '} - {competition.name} - - {isAverage && (attempts.map((a, i) => ( - - { events.byId[result.eventId].format.expectedSolveCount === 5 - && (i === bestResultIndex || i === worstResultIndex) - ? `(${formatAttemptResult(a, result.eventId)})` : formatAttemptResult(a, result.eventId)} - - )) - )} - - ); -} - -export default function Rankings({ rows, competitionsById, isAverage }) { - const r = useMemo(() => { - let previousValue = 0; - let previousRank = 0; - return rows.map((result, index) => { - const competition = competitionsById[result.competitionId]; - const { value } = result; - const rank = value === previousValue ? previousRank : index + 1; - const tiedPrevious = rank === previousRank; - - previousValue = value; - previousRank = rank; - - return ( - - ); - }); - }, [competitionsById, isAverage, rows]); - - return ( - - - - # - {I18n.t('results.table_elements.name')} - {I18n.t('results.table_elements.result')} - {I18n.t('results.table_elements.representing')} - {I18n.t('results.table_elements.competition')} - {isAverage && ( - <> - {I18n.t('results.table_elements.solves')} - - - - - - )} - - - {r} - -
-
- ); -} From 8b8c66ea41a04bae088131286d9e6174a9ca31d2 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Wed, 8 Jan 2025 17:17:21 +0100 Subject: [PATCH 03/39] implement backend loading and filters --- app/controllers/results_controller.rb | 19 +++- app/views/results/rankings.html.erb | 20 +--- .../Results/Rankings/RankingsTable.jsx | 43 +++++---- .../components/Results/Rankings/index.jsx | 95 +++++++++++++++++++ .../components/Results/api/rankings.js | 8 ++ .../components/wca/EventSelector.jsx | 37 ++++---- app/webpacker/lib/requests/routes.js.erb | 2 + 7 files changed, 169 insertions(+), 55 deletions(-) create mode 100644 app/webpacker/components/Results/api/rankings.js diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index 48d5f85fcf8..f95d4b70796 100644 --- a/app/controllers/results_controller.rb +++ b/app/controllers/results_controller.rb @@ -156,7 +156,24 @@ def rankings else flash[:danger] = t(".unknown_show") - redirect_to rankings_path + return redirect_to rankings_path + end + + @ranking_timestamp = ComputeAuxiliaryData.successful_start_date || Date.current + + respond_to do |format| + format.html + format.json do + cached_data = Rails.cache.fetch [*@cache_params, @ranking_timestamp] do + rows = DbHelper.execute_cached_query(@cache_params, @ranking_timestamp, @query) + comp_ids = rows.map { |r| r["competitionId"] }.uniq + competitions_by_id = Hash[Competition.where(id: comp_ids).map { |c| [c.id, c] }] + { + rows: rows.as_json, competitionsById: competitions_by_id.as_json({ methods: %w[country cellName id], includes: [] }), + } + end + render json: cached_data + end end end diff --git a/app/views/results/rankings.html.erb b/app/views/results/rankings.html.erb index 40186269409..1d367d83cbc 100644 --- a/app/views/results/rankings.html.erb +++ b/app/views/results/rankings.html.erb @@ -1,23 +1,11 @@ <% provide(:title, t(".title")) %> -<% ranking_timestamp = ComputeAuxiliaryData.successful_start_date || Date.current %> -<% @rows = DbHelper.execute_cached_query(@cache_params, ranking_timestamp, @query) %> -

<%= yield(:title) %>

-

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

+

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

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

-
- <%= render 'results_selector', show_rankings_options: true %> -
- <% cache [*@cache_params, ranking_timestamp, I18n.locale] do %> - <% - comp_ids = @rows.map { |r| r["competitionId"] }.uniq - @competitions_by_id = Hash[Competition.where(id: comp_ids).map { |c| [c.id, c] }] - %> - <%= react_component("Results/Rankings", { - rows: @rows.as_json, competitionsById: @competitions_by_id.as_json({ methods: %w[country name id], includes: [] }), isAverage: @is_average, isByRegion: @is_by_region, - }) %> - <% end %> + <%= react_component("Results/Rankings", { + event: params[:event_id], region: params[:region], rankingType: params[:type], year: params[:year], gender: params[:gender], + }) %>
diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index c66f3018cb5..e2811ac430e 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -1,10 +1,10 @@ import React, { useMemo } from 'react'; -import { Container, Table } from 'semantic-ui-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 { countries, events } from '../../../lib/wca-data.js.erb'; +import { countries } from '../../../lib/wca-data.js.erb'; function ResultRow({ result, competition, rank, isAverage, @@ -14,6 +14,7 @@ function ResultRow({ const worstResult = _.min(attempts); const bestResultIndex = attempts.findIndex((a) => a === bestResult); const worstResultIndex = attempts.findIndex((a) => a === worstResult); + const country = countries.real.find((c) => c.id === result.countryId); return ( {rank} @@ -24,16 +25,18 @@ function ResultRow({ {formatAttemptResult(result.value, result.eventId)} - c.id === result.countryId).iso2} /> + + {' '} + {country.name} {' '} - {competition.name} + {competition.cellName} {isAverage && (attempts.map((a, i) => ( - { events.byId[result.eventId].format.expectedSolveCount === 5 + { attempts.length === 5 && (i === bestResultIndex || i === worstResultIndex) ? `(${formatAttemptResult(a, result.eventId)})` : formatAttemptResult(a, result.eventId)} @@ -70,15 +73,14 @@ export default function RankingsTable({ rows, competitionsById, isAverage }) { }, [competitionsById, isAverage, rows]); return ( - - - - # - {I18n.t('results.table_elements.name')} - {I18n.t('results.table_elements.result')} - {I18n.t('results.table_elements.representing')} - {I18n.t('results.table_elements.competition')} - {isAverage && ( +
+ + # + {I18n.t('results.table_elements.name')} + {I18n.t('results.table_elements.result')} + {I18n.t('results.table_elements.representing')} + {I18n.t('results.table_elements.competition')} + {isAverage && ( <> {I18n.t('results.table_elements.solves')} @@ -86,12 +88,11 @@ export default function RankingsTable({ rows, competitionsById, isAverage }) { - )} - - - {r} - -
-
+ )} + + + {r} + + ); } diff --git a/app/webpacker/components/Results/Rankings/index.jsx b/app/webpacker/components/Results/Rankings/index.jsx index e69de29bb2d..8d99ba19f76 100644 --- a/app/webpacker/components/Results/Rankings/index.jsx +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -0,0 +1,95 @@ +import React, { useEffect, useState } from 'react'; +import { + Button, ButtonGroup, Container, Grid, Segment, +} from 'semantic-ui-react'; +import { useQuery } from '@tanstack/react-query'; +import RankingsTable from './RankingsTable'; +import WCAQueryClientProvider from '../../../lib/providers/WCAQueryClientProvider'; +import { getRankings } from '../api/rankings'; +import { EventSelector } from '../../wca/EventSelector'; +import { RegionSelector } from '../../CompetitionsOverview/CompetitionsFilters'; +import Loading from '../../Requests/Loading'; +import { rankingsUrl } from '../../../lib/requests/routes.js.erb'; + +export default function Wrapper({ + event, region, year, rankingType, gender, +}) { + return ( + + + + ); +} + +export function Rankings({ + initialEvent, initialRegion, initialYear, initialRankingType, initialGender, +}) { + const [event, setEvent] = useState(initialEvent); + const [region, setRegion] = useState(initialRegion ?? 'all'); + const [year, setYear] = useState(initialYear); + const [rankingType, setRankingType] = useState(initialRankingType); + const [gender, setGender] = useState(initialGender); + + const { data, isFetching } = useQuery({ + queryKey: ['rankings', event, region, year, rankingType], + queryFn: () => getRankings(event, rankingType, year, region), + }); + + useEffect(() => { + const queryParams = new URLSearchParams(); + + if (region !== 'all') { + queryParams.append('region', region); + } + if (year) { + queryParams.append('years', `only ${year}`); + } + if (rankingType === 'average') { + queryParams.append('gender', gender); // Replace with dynamic values if needed + } + + const newUrl = `${rankingsUrl(event, rankingType)}?${queryParams.toString()}`; + window.history.replaceState(null, '', newUrl); + }, [event, region, year, rankingType, gender]); + + if (isFetching) { + return ; + } + + return ( + + + + + setEvent(eventId)} showLabels={false} /> + setRegion(r)} /> + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/app/webpacker/components/Results/api/rankings.js b/app/webpacker/components/Results/api/rankings.js new file mode 100644 index 00000000000..f55d720a70e --- /dev/null +++ b/app/webpacker/components/Results/api/rankings.js @@ -0,0 +1,8 @@ +import { fetchJsonOrError } from '../../../lib/requests/fetchWithAuthenticityToken'; +import { rankingsUrl } from '../../../lib/requests/routes.js.erb'; + +// eslint-disable-next-line import/prefer-default-export +export async function getRankings(eventId, rankingType, year, region) { + const { data } = await fetchJsonOrError(rankingsUrl(eventId, rankingType, year, region), { headers: { Accept: 'application/json' } }); + return data; +} diff --git a/app/webpacker/components/wca/EventSelector.jsx b/app/webpacker/components/wca/EventSelector.jsx index 095540658b1..6252e859173 100644 --- a/app/webpacker/components/wca/EventSelector.jsx +++ b/app/webpacker/components/wca/EventSelector.jsx @@ -22,26 +22,29 @@ export function EventSelector({ // Listing event as an argument here to indicate to developers that it's needed // eslint-disable-next-line no-unused-vars disabledText = (event) => {}, + showLabels = true, }) { return ( <> - + {showLabels && ( + + )} `; 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 rankingsUrl = (eventId, rankingType, year, region) => `<%= CGI.unescape(Rails.application.routes.url_helpers.rankings_path(event_id: "${eventId}", type: "${rankingType}")) %>`; From 4e1cb69b9fefa86d19f3b681ee5c7cd86b1d7854 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Wed, 8 Jan 2025 17:21:32 +0100 Subject: [PATCH 04/39] remove segment --- app/controllers/results_controller.rb | 4 +- .../components/Results/Rankings/index.jsx | 62 +++++++++---------- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index f95d4b70796..ee16eb2d4b6 100644 --- a/app/controllers/results_controller.rb +++ b/app/controllers/results_controller.rb @@ -167,9 +167,9 @@ def rankings cached_data = Rails.cache.fetch [*@cache_params, @ranking_timestamp] do rows = DbHelper.execute_cached_query(@cache_params, @ranking_timestamp, @query) comp_ids = rows.map { |r| r["competitionId"] }.uniq - competitions_by_id = Hash[Competition.where(id: comp_ids).map { |c| [c.id, c] }] + competitions_by_id = Competition.where(id: comp_ids).to_h { |c| [c.id, c] } { - rows: rows.as_json, competitionsById: competitions_by_id.as_json({ methods: %w[country cellName id], includes: [] }), + rows: rows.as_json, competitionsById: competitions_by_id.as_json({ methods: %w[country cellName id], includes: [] }) } end render json: cached_data diff --git a/app/webpacker/components/Results/Rankings/index.jsx b/app/webpacker/components/Results/Rankings/index.jsx index 8d99ba19f76..dd290b67a98 100644 --- a/app/webpacker/components/Results/Rankings/index.jsx +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -58,38 +58,36 @@ export function Rankings({ return ( - - - - setEvent(eventId)} showLabels={false} /> - setRegion(r)} /> - - - - - - - - - - - - - - - - - - - - - - - + + + setEvent(eventId)} showLabels={false} /> + setRegion(r)} /> + + + + + + + + + + + + + + + + + + + + + + ); } From f6f0d47c57ee068ea70d90b886a5bb5715645c58 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Fri, 10 Jan 2025 14:57:50 +0100 Subject: [PATCH 05/39] fix stylings in filters --- .../components/Results/Rankings/index.jsx | 63 +++++++------------ .../components/Results/resultsFilter.jsx | 61 ++++++++++++++++++ .../components/wca/EventSelector.jsx | 35 +++++------ 3 files changed, 101 insertions(+), 58 deletions(-) create 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 dd290b67a98..73afe0f77cb 100644 --- a/app/webpacker/components/Results/Rankings/index.jsx +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { Button, ButtonGroup, Container, Grid, Segment, } from 'semantic-ui-react'; @@ -6,10 +6,9 @@ import { useQuery } from '@tanstack/react-query'; import RankingsTable from './RankingsTable'; import WCAQueryClientProvider from '../../../lib/providers/WCAQueryClientProvider'; import { getRankings } from '../api/rankings'; -import { EventSelector } from '../../wca/EventSelector'; -import { RegionSelector } from '../../CompetitionsOverview/CompetitionsFilters'; import Loading from '../../Requests/Loading'; import { rankingsUrl } from '../../../lib/requests/routes.js.erb'; +import ResultsFilter from '../resultsFilter'; export default function Wrapper({ event, region, year, rankingType, gender, @@ -22,35 +21,45 @@ export default function Wrapper({ } export function Rankings({ - initialEvent, initialRegion, initialYear, initialRankingType, initialGender, + initialEvent, initialRegion, initialRankingType, initialGender, }) { const [event, setEvent] = useState(initialEvent); const [region, setRegion] = useState(initialRegion ?? 'all'); - const [year, setYear] = useState(initialYear); const [rankingType, setRankingType] = useState(initialRankingType); const [gender, setGender] = useState(initialGender); + const filterState = useMemo(() => ({ + event, + setEvent, + region, + setRegion, + rankingType, + setRankingType, + gender, + setGender, + }), [event, gender, rankingType, region]); + const { data, isFetching } = useQuery({ - queryKey: ['rankings', event, region, year, rankingType], - queryFn: () => getRankings(event, rankingType, year, region), + queryKey: ['rankings', event, region, rankingType], + queryFn: () => getRankings(event, rankingType, region), }); useEffect(() => { const queryParams = new URLSearchParams(); - if (region !== 'all') { + if (region !== 'world') { queryParams.append('region', region); } - if (year) { - queryParams.append('years', `only ${year}`); - } - if (rankingType === 'average') { - queryParams.append('gender', gender); // Replace with dynamic values if needed + // if (year) { + // queryParams.append('years', `only ${year}`); + // } + if (gender !== 'All') { + queryParams.append('gender', gender); } const newUrl = `${rankingsUrl(event, rankingType)}?${queryParams.toString()}`; window.history.replaceState(null, '', newUrl); - }, [event, region, year, rankingType, gender]); + }, [event, region, rankingType, gender]); if (isFetching) { return ; @@ -58,31 +67,7 @@ export function Rankings({ return ( - - - setEvent(eventId)} showLabels={false} /> - setRegion(r)} /> - - - - - - - - - - - - - - - - - - - - - + +
+ + setEvent(eventId)} + hideAllButton + hideClearButton + /> + setRegion(r)} /> + + + + + + + + + + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + + + + + + + + + + + + + + + + +
+ + ); +} diff --git a/app/webpacker/components/wca/EventSelector.jsx b/app/webpacker/components/wca/EventSelector.jsx index 6252e859173..40377338e81 100644 --- a/app/webpacker/components/wca/EventSelector.jsx +++ b/app/webpacker/components/wca/EventSelector.jsx @@ -22,29 +22,26 @@ export function EventSelector({ // Listing event as an argument here to indicate to developers that it's needed // eslint-disable-next-line no-unused-vars disabledText = (event) => {}, - showLabels = true, }) { return ( <> - {showLabels && ( - Date: Fri, 10 Jan 2025 15:31:37 +0100 Subject: [PATCH 06/39] fix gender and region not being filtered --- app/controllers/results_controller.rb | 2 +- .../Results/Rankings/RankingsTable.jsx | 7 ++++--- .../components/Results/Rankings/index.jsx | 19 +++---------------- .../components/Results/api/rankings.js | 4 ++-- .../components/Results/resultsFilter.jsx | 7 ++++++- app/webpacker/lib/requests/routes.js.erb | 15 ++++++++++++++- 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index ee16eb2d4b6..d2f98807b7a 100644 --- a/app/controllers/results_controller.rb +++ b/app/controllers/results_controller.rb @@ -32,7 +32,7 @@ def rankings support_old_links! # Default params - params[:region] ||= REGION_WORLD + params[:region] ||= "all" params[:years] = YEARS_ALL # FIXME: this is disabling years filters for now params[:show] ||= SHOW_100_PERSONS params[:gender] ||= GENDER_ALL diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index e2811ac430e..ac0ec51a29a 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -5,6 +5,7 @@ import I18n from '../../../lib/i18n'; import { formatAttemptResult } from '../../../lib/wca-live/attempts'; import CountryFlag from '../../wca/CountryFlag'; import { countries } from '../../../lib/wca-data.js.erb'; +import { personUrl } from '../../../lib/requests/routes.js.erb'; function ResultRow({ result, competition, rank, isAverage, @@ -17,9 +18,9 @@ function ResultRow({ const country = countries.real.find((c) => c.id === result.countryId); return ( - {rank} + {rank} - {result.personName} + {result.personName} {formatAttemptResult(result.value, result.eventId)} @@ -75,7 +76,7 @@ export default function RankingsTable({ rows, competitionsById, isAverage }) { return ( - # + # {I18n.t('results.table_elements.name')} {I18n.t('results.table_elements.result')} {I18n.t('results.table_elements.representing')} diff --git a/app/webpacker/components/Results/Rankings/index.jsx b/app/webpacker/components/Results/Rankings/index.jsx index 73afe0f77cb..a5a0b5dda82 100644 --- a/app/webpacker/components/Results/Rankings/index.jsx +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -40,25 +40,12 @@ export function Rankings({ }), [event, gender, rankingType, region]); const { data, isFetching } = useQuery({ - queryKey: ['rankings', event, region, rankingType], - queryFn: () => getRankings(event, rankingType, region), + queryKey: ['rankings', event, region, rankingType, gender], + queryFn: () => getRankings(event, rankingType, region, gender), }); useEffect(() => { - const queryParams = new URLSearchParams(); - - if (region !== 'world') { - queryParams.append('region', region); - } - // if (year) { - // queryParams.append('years', `only ${year}`); - // } - if (gender !== 'All') { - queryParams.append('gender', gender); - } - - const newUrl = `${rankingsUrl(event, rankingType)}?${queryParams.toString()}`; - window.history.replaceState(null, '', newUrl); + window.history.replaceState(null, '', rankingsUrl(event, rankingType, region, gender)); }, [event, region, rankingType, gender]); if (isFetching) { diff --git a/app/webpacker/components/Results/api/rankings.js b/app/webpacker/components/Results/api/rankings.js index f55d720a70e..8040cd5a87a 100644 --- a/app/webpacker/components/Results/api/rankings.js +++ b/app/webpacker/components/Results/api/rankings.js @@ -2,7 +2,7 @@ import { fetchJsonOrError } from '../../../lib/requests/fetchWithAuthenticityTok import { rankingsUrl } from '../../../lib/requests/routes.js.erb'; // eslint-disable-next-line import/prefer-default-export -export async function getRankings(eventId, rankingType, year, region) { - const { data } = await fetchJsonOrError(rankingsUrl(eventId, rankingType, year, region), { headers: { Accept: 'application/json' } }); +export async function getRankings(eventId, rankingType, region, gender) { + const { data } = await fetchJsonOrError(rankingsUrl(eventId, rankingType, region, gender), { headers: { Accept: 'application/json' } }); return data; } diff --git a/app/webpacker/components/Results/resultsFilter.jsx b/app/webpacker/components/Results/resultsFilter.jsx index e7e97436989..35ae0a7a977 100644 --- a/app/webpacker/components/Results/resultsFilter.jsx +++ b/app/webpacker/components/Results/resultsFilter.jsx @@ -4,11 +4,13 @@ import { } from 'semantic-ui-react'; import { EventSelector } from '../wca/EventSelector'; import { RegionSelector } from '../CompetitionsOverview/CompetitionsFilters'; +import { countries } from '../../lib/wca-data.js.erb'; export default function ResultsFilter({ filterState }) { const { event, setEvent, region, setRegion, rankingType, setRankingType, gender, setGender, } = filterState; + const regionIso2 = countries.real.find((country) => country.id === region)?.iso2 ?? region; return (
@@ -19,7 +21,10 @@ export default function ResultsFilter({ filterState }) { hideAllButton hideClearButton /> - setRegion(r)} /> + setRegion(countries.byIso2[r]?.id ?? r)} + /> diff --git a/app/webpacker/lib/requests/routes.js.erb b/app/webpacker/lib/requests/routes.js.erb index 5edc022f8f4..ebd919c3980 100644 --- a/app/webpacker/lib/requests/routes.js.erb +++ b/app/webpacker/lib/requests/routes.js.erb @@ -1,3 +1,4 @@ +import {countries} from "../wca-data.js.erb"; function jsonToQueryString(json) { const jsonAfterRemovingUndefinedAndNull = Object.fromEntries( Object.entries(json).filter(([key, value]) => value !== null && value !== undefined) @@ -292,4 +293,16 @@ export const bulkUpdateRegistrationUrl = `<%= CGI.unescape(Rails.application.rou 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 rankingsUrl = (eventId, rankingType, year, region) => `<%= CGI.unescape(Rails.application.routes.url_helpers.rankings_path(event_id: "${eventId}", type: "${rankingType}")) %>`; +export const rankingsUrl = (eventId, rankingType, region, gender) => { + const url = `<%= CGI.unescape(Rails.application.routes.url_helpers.rankings_path(event_id: "${eventId}", type: "${rankingType}")) %>` + const queryParams = new URLSearchParams(); + + if (region !== 'all') { + queryParams.append('region', region); + } + + if (gender !== 'All') { + queryParams.append('gender', gender); + } + return `${url}?${queryParams.toString()}` +}; From 86e2ed8b643c0ef302a30843596aae328de7a867 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Fri, 10 Jan 2025 15:49:52 +0100 Subject: [PATCH 07/39] added show filters --- app/views/results/rankings.html.erb | 7 ++++- .../components/Results/Rankings/index.jsx | 30 ++++++++++++------- .../components/Results/api/rankings.js | 4 +-- .../components/Results/resultsFilter.jsx | 22 ++++++++++---- app/webpacker/lib/requests/routes.js.erb | 7 ++++- 5 files changed, 49 insertions(+), 21 deletions(-) diff --git a/app/views/results/rankings.html.erb b/app/views/results/rankings.html.erb index 1d367d83cbc..c9e36b70259 100644 --- a/app/views/results/rankings.html.erb +++ b/app/views/results/rankings.html.erb @@ -6,6 +6,11 @@

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

<%= react_component("Results/Rankings", { - event: params[:event_id], region: params[:region], rankingType: params[:type], year: params[:year], gender: params[:gender], + event: params[:event_id], + region: params[:region], + rankingType: params[:type], + year: params[:year], + gender: params[:gender], + show: params[:show], }) %> diff --git a/app/webpacker/components/Results/Rankings/index.jsx b/app/webpacker/components/Results/Rankings/index.jsx index a5a0b5dda82..d1275fc16dc 100644 --- a/app/webpacker/components/Results/Rankings/index.jsx +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -1,7 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { - Button, ButtonGroup, Container, Grid, Segment, -} from 'semantic-ui-react'; +import { Container } from 'semantic-ui-react'; import { useQuery } from '@tanstack/react-query'; import RankingsTable from './RankingsTable'; import WCAQueryClientProvider from '../../../lib/providers/WCAQueryClientProvider'; @@ -11,22 +9,30 @@ import { rankingsUrl } from '../../../lib/requests/routes.js.erb'; import ResultsFilter from '../resultsFilter'; export default function Wrapper({ - event, region, year, rankingType, gender, + event, region, year, rankingType, gender, show, }) { return ( - + ); } export function Rankings({ - initialEvent, initialRegion, initialRankingType, initialGender, + initialEvent, initialRegion, initialRankingType, initialGender, initialShow, }) { const [event, setEvent] = useState(initialEvent); const [region, setRegion] = useState(initialRegion ?? 'all'); const [rankingType, setRankingType] = useState(initialRankingType); const [gender, setGender] = useState(initialGender); + const [show, setShow] = useState(initialShow ?? 'Persons'); const filterState = useMemo(() => ({ event, @@ -37,16 +43,18 @@ export function Rankings({ setRankingType, gender, setGender, - }), [event, gender, rankingType, region]); + show, + setShow, + }), [event, gender, rankingType, region, show]); const { data, isFetching } = useQuery({ - queryKey: ['rankings', event, region, rankingType, gender], - queryFn: () => getRankings(event, rankingType, region, gender), + queryKey: ['rankings', event, region, rankingType, gender, show], + queryFn: () => getRankings(event, rankingType, region, gender, show), }); useEffect(() => { - window.history.replaceState(null, '', rankingsUrl(event, rankingType, region, gender)); - }, [event, region, rankingType, gender]); + window.history.replaceState(null, '', rankingsUrl(event, rankingType, region, gender, show)); + }, [event, region, rankingType, gender, show]); if (isFetching) { return ; diff --git a/app/webpacker/components/Results/api/rankings.js b/app/webpacker/components/Results/api/rankings.js index 8040cd5a87a..0ff4957cc7c 100644 --- a/app/webpacker/components/Results/api/rankings.js +++ b/app/webpacker/components/Results/api/rankings.js @@ -2,7 +2,7 @@ import { fetchJsonOrError } from '../../../lib/requests/fetchWithAuthenticityTok import { rankingsUrl } from '../../../lib/requests/routes.js.erb'; // eslint-disable-next-line import/prefer-default-export -export async function getRankings(eventId, rankingType, region, gender) { - const { data } = await fetchJsonOrError(rankingsUrl(eventId, rankingType, region, gender), { headers: { Accept: 'application/json' } }); +export async function getRankings(eventId, rankingType, region, gender, show) { + const { data } = await fetchJsonOrError(rankingsUrl(eventId, rankingType, 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 index 35ae0a7a977..d61cc7a5de3 100644 --- a/app/webpacker/components/Results/resultsFilter.jsx +++ b/app/webpacker/components/Results/resultsFilter.jsx @@ -8,7 +8,16 @@ import { countries } from '../../lib/wca-data.js.erb'; export default function ResultsFilter({ filterState }) { const { - event, setEvent, region, setRegion, rankingType, setRankingType, gender, setGender, + event, + setEvent, + region, + setRegion, + rankingType, + setRankingType, + gender, + setGender, + show, + setShow, } = filterState; const regionIso2 = countries.real.find((country) => country.id === region)?.iso2 ?? region; return ( @@ -29,7 +38,7 @@ export default function ResultsFilter({ filterState }) { - + @@ -54,9 +63,10 @@ export default function ResultsFilter({ filterState }) { - - - + + + + diff --git a/app/webpacker/lib/requests/routes.js.erb b/app/webpacker/lib/requests/routes.js.erb index ebd919c3980..c66eccab1db 100644 --- a/app/webpacker/lib/requests/routes.js.erb +++ b/app/webpacker/lib/requests/routes.js.erb @@ -293,7 +293,7 @@ export const bulkUpdateRegistrationUrl = `<%= CGI.unescape(Rails.application.rou 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 rankingsUrl = (eventId, rankingType, region, gender) => { +export const rankingsUrl = (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(); @@ -304,5 +304,10 @@ export const rankingsUrl = (eventId, rankingType, region, gender) => { if (gender !== 'All') { queryParams.append('gender', gender); } + + if (show !== 'Persons') { + queryParams.append('show', show); + } + return `${url}?${queryParams.toString()}` }; From 6f37b6da6f9c0c754ff77137b34504bfab6fd1ba Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Fri, 10 Jan 2025 16:33:50 +0100 Subject: [PATCH 08/39] implement by regions table --- app/controllers/results_controller.rb | 44 ++++++++++++++- app/helpers/results_helper.rb | 39 -------------- .../Results/Rankings/RankingsTable.jsx | 54 ++++++++++++++----- .../components/Results/Rankings/index.jsx | 1 + .../components/Results/resultsFilter.jsx | 17 ++++-- app/webpacker/lib/requests/routes.js.erb | 6 +-- 6 files changed, 99 insertions(+), 62 deletions(-) diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index d2f98807b7a..5001a3e778f 100644 --- a/app/controllers/results_controller.rb +++ b/app/controllers/results_controller.rb @@ -32,7 +32,7 @@ def rankings support_old_links! # Default params - params[:region] ||= "all" + params[:region] ||= REGION_WORLD params[:years] = YEARS_ALL # FIXME: this is disabling years filters for now params[:show] ||= SHOW_100_PERSONS params[:gender] ||= GENDER_ALL @@ -167,6 +167,9 @@ def rankings cached_data = Rails.cache.fetch [*@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).to_h { |c| [c.id, c] } { rows: rows.as_json, competitionsById: competitions_by_id.as_json({ methods: %w[country cellName id], includes: [] }) @@ -373,4 +376,43 @@ def records params[:show] = nil end end + + private def compute_rankings_by_region(rows, continent, country) + if rows.empty? + return [[], 0, 0] + end + best_value_of_world = rows.first["value"] + best_values_of_continents = {} + best_values_of_countries = {} + world_rows = [] + continents_rows = [] + countries_rows = [] + rows.each do |row| + result = LightResult.new(row) + value = row["value"] + + world_rows << row if value == best_value_of_world + + if best_values_of_continents[result.country.continent.id].nil? || value == best_values_of_continents[result.country.continent.id] + best_values_of_continents[result.country.continent.id] = value + + if (country.present? && country.continent.id == result.country.continent.id) || (continent.present? && continent.id == result.country.continent.id) || params[:region] == "world" + continents_rows << row + end + end + + if best_values_of_countries[result.country.id].nil? || value == best_values_of_countries[result.country.id] + best_values_of_countries[result.country.id] = value + + if (country.present? && country.id == result.country.id) || params[:region] == "world" + countries_rows << row + end + end + end + + first_continent_index = world_rows.length + first_country_index = first_continent_index + continents_rows.length + rows_to_display = world_rows + continents_rows + countries_rows + [rows_to_display, first_continent_index, first_country_index] + end end diff --git a/app/helpers/results_helper.rb b/app/helpers/results_helper.rb index dbdc746e870..44f6f757052 100644 --- a/app/helpers/results_helper.rb +++ b/app/helpers/results_helper.rb @@ -29,45 +29,6 @@ def historical_pb_markers(results) end end - def compute_rankings_by_region(rows, continent, country) - if rows.empty? - return [[], 0, 0] - end - best_value_of_world = rows.first["value"] - best_values_of_continents = {} - best_values_of_countries = {} - world_rows = [] - continents_rows = [] - countries_rows = [] - rows.each do |row| - result = LightResult.new(row) - value = row["value"] - - world_rows << row if value == best_value_of_world - - if best_values_of_continents[result.country.continent.id].nil? || value == best_values_of_continents[result.country.continent.id] - best_values_of_continents[result.country.continent.id] = value - - if (country.present? && country.continent.id == result.country.continent.id) || (continent.present? && continent.id == result.country.continent.id) || params[:region] == "world" - continents_rows << row - end - end - - if best_values_of_countries[result.country.id].nil? || value == best_values_of_countries[result.country.id] - best_values_of_countries[result.country.id] = value - - if (country.present? && country.id == result.country.id) || params[:region] == "world" - countries_rows << row - end - end - end - - first_continent_index = world_rows.length - first_country_index = first_continent_index + continents_rows.length - rows_to_display = world_rows + continents_rows + countries_rows - [rows_to_display, first_continent_index, first_country_index] - end - def compute_slim_or_separate_records(rows) single_rows = [] average_rows = [] diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index ac0ec51a29a..1bafa38cea3 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -4,32 +4,39 @@ import _ from 'lodash'; import I18n from '../../../lib/i18n'; import { formatAttemptResult } from '../../../lib/wca-live/attempts'; import CountryFlag from '../../wca/CountryFlag'; -import { countries } from '../../../lib/wca-data.js.erb'; +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, + result, competition, rank, isAverage, show, country, }) { const attempts = [result.value1, result.value2, result.value3, result.value4, result.value5]; const bestResult = _.max(attempts); const worstResult = _.min(attempts); const bestResultIndex = attempts.findIndex((a) => a === bestResult); const worstResultIndex = attempts.findIndex((a) => a === worstResult); - const country = countries.real.find((c) => c.id === result.countryId); return ( - {rank} + {show === 'by region' ? + : {rank} } {result.personName} {formatAttemptResult(result.value, result.eventId)} - - - {' '} - {country.name} - + {show !== 'by region' + && } {' '} @@ -47,39 +54,58 @@ function ResultRow({ ); } -export default function RankingsTable({ rows, competitionsById, isAverage }) { +export default function RankingsTable({ + rows, competitionsById, isAverage, show, +}) { const r = useMemo(() => { + let rowsToMap = rows; + let firstContinentIndex = 0; + let firstCountryIndex = 0; + if (show === 'by region') { + [rowsToMap, firstContinentIndex, firstCountryIndex] = rows; + } + let previousValue = 0; let previousRank = 0; - return rows.map((result, index) => { + return rowsToMap.map((result, index) => { const competition = competitionsById[result.competitionId]; const { value } = result; 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); + } previousValue = value; previousRank = rank; return ( ); }); - }, [competitionsById, isAverage, rows]); + }, [competitionsById, isAverage, rows, show]); return (
- # + {show !== 'by region' ? # + : {I18n.t('results.table_elements.region')}} {I18n.t('results.table_elements.name')} {I18n.t('results.table_elements.result')} - {I18n.t('results.table_elements.representing')} + {show !== 'by region' && {I18n.t('results.table_elements.representing')}} {I18n.t('results.table_elements.competition')} {isAverage && ( <> diff --git a/app/webpacker/components/Results/Rankings/index.jsx b/app/webpacker/components/Results/Rankings/index.jsx index d1275fc16dc..1454b7f6971 100644 --- a/app/webpacker/components/Results/Rankings/index.jsx +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -67,6 +67,7 @@ export function Rankings({ competitionsById={data.competitionsById} isAverage={rankingType === 'average'} rows={data.rows} + show={show} /> ); diff --git a/app/webpacker/components/Results/resultsFilter.jsx b/app/webpacker/components/Results/resultsFilter.jsx index d61cc7a5de3..00045f3060e 100644 --- a/app/webpacker/components/Results/resultsFilter.jsx +++ b/app/webpacker/components/Results/resultsFilter.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { Button, ButtonGroup, Form, Segment, } from 'semantic-ui-react'; @@ -19,7 +19,16 @@ export default function ResultsFilter({ filterState }) { show, setShow, } = filterState; - const regionIso2 = countries.real.find((country) => country.id === region)?.iso2 ?? region; + const regionIso2 = useMemo(() => { + if (region === 'world') { + return 'all'; + } + const iso2 = countries.real.find((country) => country.id === region)?.iso2; + if (iso2) { + return iso2; + } + return region; + }, [region]); return ( @@ -64,8 +73,8 @@ export default function ResultsFilter({ filterState }) { - - + + diff --git a/app/webpacker/lib/requests/routes.js.erb b/app/webpacker/lib/requests/routes.js.erb index c66eccab1db..e0cb28af65b 100644 --- a/app/webpacker/lib/requests/routes.js.erb +++ b/app/webpacker/lib/requests/routes.js.erb @@ -297,15 +297,13 @@ export const rankingsUrl = (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 !== 'all') { - queryParams.append('region', region); - } + queryParams.append('region', region); if (gender !== 'All') { queryParams.append('gender', gender); } - if (show !== 'Persons') { + if (show !== '100 persons') { queryParams.append('show', show); } From 985887cecea7342824b1ef2105f7e5822f2774b3 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Fri, 10 Jan 2025 16:40:32 +0100 Subject: [PATCH 09/39] add i18n to resultsfilter --- .../components/Results/resultsFilter.jsx | 23 ++++++++++--------- config/i18n.yml | 1 + 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/app/webpacker/components/Results/resultsFilter.jsx b/app/webpacker/components/Results/resultsFilter.jsx index 00045f3060e..409d55803d3 100644 --- a/app/webpacker/components/Results/resultsFilter.jsx +++ b/app/webpacker/components/Results/resultsFilter.jsx @@ -5,6 +5,7 @@ import { 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 }) { const { @@ -46,15 +47,15 @@ export default function ResultsFilter({ filterState }) { - + - + {/* */} @@ -63,19 +64,19 @@ export default function ResultsFilter({ filterState }) { {/* */} {/* */} - + - - - + + + - + - - - + + + diff --git a/config/i18n.yml b/config/i18n.yml index 61ec2172ea2..4425a6670a2 100644 --- a/config/i18n.yml +++ b/config/i18n.yml @@ -76,3 +76,4 @@ translations: - "*.users.edit.*" - "*.persons.index.*" - "*.results.table_elements.*" + - "*.results.selector_elements.*" From 81f81a4eecda380b944474ff029249ed8502d8e1 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 13 Jan 2025 11:57:05 +0100 Subject: [PATCH 10/39] fix competitions_by_id serialization --- app/controllers/results_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index 5001a3e778f..1addf9dd050 100644 --- a/app/controllers/results_controller.rb +++ b/app/controllers/results_controller.rb @@ -164,15 +164,15 @@ def rankings respond_to do |format| format.html format.json do - cached_data = Rails.cache.fetch [*@cache_params, @ranking_timestamp] 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).to_h { |c| [c.id, c] } + competitions_by_id = Competition.where(id: comp_ids).index_by(&:id).transform_values { |comp| comp.as_json(methods: %w[country cellName id], includes: [], only: []) } { - rows: rows.as_json, competitionsById: competitions_by_id.as_json({ methods: %w[country cellName id], includes: [] }) + rows: rows.as_json, competitionsById: competitions_by_id } end render json: cached_data From a7b0a7caecbf6ca7963c5768cf5494192a4bbf4d Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 13 Jan 2025 12:08:47 +0100 Subject: [PATCH 11/39] don't include organizers or delegates in competition serialization --- app/controllers/results_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index 1addf9dd050..a9cfb8be1b3 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[country cellName id], includes: [], only: []) } + 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 } From b47604553f6c71a54365c09401f64f54aa96f9f1 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 13 Jan 2025 12:35:45 +0100 Subject: [PATCH 12/39] fix indentation --- .../components/wca/EventSelector.jsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/webpacker/components/wca/EventSelector.jsx b/app/webpacker/components/wca/EventSelector.jsx index 40377338e81..095540658b1 100644 --- a/app/webpacker/components/wca/EventSelector.jsx +++ b/app/webpacker/components/wca/EventSelector.jsx @@ -29,16 +29,16 @@ export function EventSelector({ {`${I18n.t('competitions.competition_form.events')}`} {showBreakBeforeButtons ? (
) : (' ')} {hideAllButton || ( - - } - > - {I18n.t('competitions.registration_v2.register.event_limit', { - max_events: maxEvents, - })} - + + } + > + {I18n.t('competitions.registration_v2.register.event_limit', { + max_events: maxEvents, + })} + )} {hideClearButton || } From 42e8d19480d675bf23cfc2a502233b6ef8741e66 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 13 Jan 2025 12:38:31 +0100 Subject: [PATCH 13/39] fix country being imported in routes --- app/webpacker/lib/requests/routes.js.erb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/webpacker/lib/requests/routes.js.erb b/app/webpacker/lib/requests/routes.js.erb index 8a9164c0323..ea54b8b9c4a 100644 --- a/app/webpacker/lib/requests/routes.js.erb +++ b/app/webpacker/lib/requests/routes.js.erb @@ -1,4 +1,3 @@ -import {countries} from "../wca-data.js.erb"; function jsonToQueryString(json) { const jsonAfterRemovingUndefinedAndNull = Object.fromEntries( Object.entries(json).filter(([key, value]) => value !== null && value !== undefined) From 7fb11519dc9dc9d17017448937cde32751e6df45 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 13 Jan 2025 12:39:20 +0100 Subject: [PATCH 14/39] Update app/webpacker/components/Results/resultsFilter.jsx Co-authored-by: Kevin Matthews <49137025+kr-matthews@users.noreply.github.com> --- app/webpacker/components/Results/resultsFilter.jsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/webpacker/components/Results/resultsFilter.jsx b/app/webpacker/components/Results/resultsFilter.jsx index 409d55803d3..3b1e3902667 100644 --- a/app/webpacker/components/Results/resultsFilter.jsx +++ b/app/webpacker/components/Results/resultsFilter.jsx @@ -24,11 +24,7 @@ export default function ResultsFilter({ filterState }) { if (region === 'world') { return 'all'; } - const iso2 = countries.real.find((country) => country.id === region)?.iso2; - if (iso2) { - return iso2; - } - return region; + return countries.real.find((country) => country.id === region)?.iso2 || region; }, [region]); return ( From 1375c9f413f5c3900e65a2592b7226060156397a Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 13 Jan 2025 14:12:21 +0100 Subject: [PATCH 15/39] mobile changes --- app/controllers/results_controller.rb | 2 +- .../Results/Rankings/RankingsTable.jsx | 47 ++++++++++--------- .../components/Results/Rankings/index.jsx | 2 +- .../components/Results/resultsFilter.jsx | 6 +-- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index a9cfb8be1b3..299c0abcde4 100644 --- a/app/controllers/results_controller.rb +++ b/app/controllers/results_controller.rb @@ -162,7 +162,7 @@ def rankings @ranking_timestamp = ComputeAuxiliaryData.successful_start_date || Date.current respond_to do |format| - format.html + 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) diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 1bafa38cea3..538d61076d7 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -99,27 +99,30 @@ export default function RankingsTable({ }, [competitionsById, isAverage, 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')} - - - - - - )} - - - {r} - -
+
+ + + {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')} + + + + + + )} + + + {r} + +
+
); } diff --git a/app/webpacker/components/Results/Rankings/index.jsx b/app/webpacker/components/Results/Rankings/index.jsx index 1454b7f6971..c1f9c659b3c 100644 --- a/app/webpacker/components/Results/Rankings/index.jsx +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -61,7 +61,7 @@ export function Rankings({ } return ( - + - + @@ -69,7 +69,7 @@ export default function ResultsFilter({ filterState }) { - + From e03bbd4663e9877cd6e7b7405d81057975ef96c4 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 13 Jan 2025 14:51:47 +0100 Subject: [PATCH 16/39] Update app/webpacker/components/Results/Rankings/RankingsTable.jsx Co-authored-by: Kevin Matthews <49137025+kr-matthews@users.noreply.github.com> --- 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 538d61076d7..899dd4446fe 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -23,7 +23,7 @@ function ResultRow({ const attempts = [result.value1, result.value2, result.value3, result.value4, result.value5]; const bestResult = _.max(attempts); const worstResult = _.min(attempts); - const bestResultIndex = attempts.findIndex((a) => a === bestResult); + const bestResultIndex = attempts.indexOf(bestResult); const worstResultIndex = attempts.findIndex((a) => a === worstResult); return ( From 933017185bea99ba88cbb0a2415f558d697cf2d4 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 13 Jan 2025 15:41:31 +0100 Subject: [PATCH 17/39] change useMemo to try and fix by region rendering --- .../Results/Rankings/RankingsTable.jsx | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 899dd4446fe..672728392d0 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -18,15 +18,16 @@ function CountryCell({ country }) { } function ResultRow({ - result, competition, rank, isAverage, show, country, + result, competition, rank, isAverage, show, country, key, }) { - 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.findIndex((a) => a === worstResult); + const worstResultIndex = attempts.indexOf(worstResult); return ( - + {show === 'by region' ? : {rank} } @@ -57,7 +58,7 @@ function ResultRow({ export default function RankingsTable({ rows, competitionsById, isAverage, show, }) { - const r = useMemo(() => { + const results = useMemo(() => { let rowsToMap = rows; let firstContinentIndex = 0; let firstCountryIndex = 0; @@ -83,21 +84,16 @@ export default function RankingsTable({ previousValue = value; previousRank = rank; - return ( - - ); + return { + result, + competition, + country, + rank, + tiedPrevious, + key: `${result.id}-${show}`, + }; }); - }, [competitionsById, isAverage, rows, show]); - + }, [competitionsById, rows, show]); return (
@@ -120,7 +116,18 @@ export default function RankingsTable({ )} - {r} + {results.map((r) => ( + + ))}
From eb4208862bc83a680bcfbb468a02d1622592e2a0 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 13 Jan 2025 16:04:32 +0100 Subject: [PATCH 18/39] force Table Body to rerender when changing show modes --- .../components/Results/Rankings/RankingsTable.jsx | 2 +- app/webpacker/components/Results/resultsFilter.jsx | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 672728392d0..821e353c774 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -115,7 +115,7 @@ export default function RankingsTable({ )} - + {results.map((r) => ( setRegion(countries.byIso2[r]?.id ?? r)} + dispatchFilter={({ region: r }) => { + if (r === 'all') { + setRegion('world'); + } else { + setRegion(countries.byIso2[r]?.id ?? r); + } + }} />
From 5837c0abbe9e8b1e79bf76ff70e877650cc72e12 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 13 Jan 2025 16:24:23 +0100 Subject: [PATCH 19/39] use reduce instead of map --- .../Results/Rankings/RankingsTable.jsx | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 821e353c774..9b6dbc0a039 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -36,8 +36,7 @@ function ResultRow({ {formatAttemptResult(result.value, result.eventId)} - {show !== 'by region' - && } + {show !== 'by region' && } {' '} @@ -66,33 +65,35 @@ export default function RankingsTable({ [rowsToMap, firstContinentIndex, firstCountryIndex] = rows; } - let previousValue = 0; - let previousRank = 0; - return rowsToMap.map((result, index) => { + 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); + 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); } - previousValue = value; - previousRank = rank; - - return { + acc.push({ result, competition, country, rank, tiedPrevious, key: `${result.id}-${show}`, - }; - }); + }); + + return acc; + }, []); }, [competitionsById, rows, show]); return (
From d1c7b7e054f28a20bddc7a212c3b417fce0e34b6 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 13 Jan 2025 17:00:36 +0100 Subject: [PATCH 20/39] use a reducer instead of state --- .../components/Results/Rankings/index.jsx | 73 ++++++++++++++----- .../components/Results/resultsFilter.jsx | 16 ++-- 2 files changed, 64 insertions(+), 25 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/index.jsx b/app/webpacker/components/Results/Rankings/index.jsx index c1f9c659b3c..e4a5e344d00 100644 --- a/app/webpacker/components/Results/Rankings/index.jsx +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -1,4 +1,6 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { + useEffect, useMemo, useReducer, useState, +} from 'react'; import { Container } from 'semantic-ui-react'; import { useQuery } from '@tanstack/react-query'; import RankingsTable from './RankingsTable'; @@ -8,6 +10,31 @@ import Loading from '../../Requests/Loading'; import { rankingsUrl } 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 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({ event, region, year, rankingType, gender, show, }) { @@ -28,24 +55,32 @@ export default function Wrapper({ export function Rankings({ initialEvent, initialRegion, initialRankingType, initialGender, initialShow, }) { - const [event, setEvent] = useState(initialEvent); - const [region, setRegion] = useState(initialRegion ?? 'all'); - const [rankingType, setRankingType] = useState(initialRankingType); - const [gender, setGender] = useState(initialGender); - const [show, setShow] = useState(initialShow ?? 'Persons'); + // Define the initial state + const initialState = useMemo(() => ({ + event: initialEvent, + region: initialRegion, + rankingType: initialRankingType, + gender: initialGender, + show: initialShow, + }), [initialEvent, initialGender, initialRankingType, initialRegion, initialShow]); + + // 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 filterState = useMemo(() => ({ - event, - setEvent, - region, - setRegion, - rankingType, - setRankingType, - gender, - setGender, - show, - setShow, - }), [event, gender, rankingType, region, show]); + const { + event, region, rankingType, gender, show, + } = filterState; const { data, isFetching } = useQuery({ queryKey: ['rankings', event, region, rankingType, gender, show], @@ -62,7 +97,7 @@ export function Rankings({ return ( - + { if (region === 'world') { return 'all'; From 34616425d320a4e99371e1b4a6f048264de8e66d Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 13 Jan 2025 17:05:19 +0100 Subject: [PATCH 21/39] add title prop --- app/webpacker/components/Results/resultsFilter.jsx | 1 + app/webpacker/components/wca/EventSelector.jsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/webpacker/components/Results/resultsFilter.jsx b/app/webpacker/components/Results/resultsFilter.jsx index 82ae9646f51..fef2326e3ee 100644 --- a/app/webpacker/components/Results/resultsFilter.jsx +++ b/app/webpacker/components/Results/resultsFilter.jsx @@ -35,6 +35,7 @@ export default function ResultsFilter({ filterState, filterActions }) { setEvent(eventId)} hideAllButton diff --git a/app/webpacker/components/wca/EventSelector.jsx b/app/webpacker/components/wca/EventSelector.jsx index 095540658b1..8d75c047973 100644 --- a/app/webpacker/components/wca/EventSelector.jsx +++ b/app/webpacker/components/wca/EventSelector.jsx @@ -10,6 +10,7 @@ const WCA_EVENT_IDS = Object.values(events.official).map((e) => e.id); export function EventSelector({ selectedEvents, onEventSelection, + title = I18n.t('competitions.competition_form.events'), eventList = WCA_EVENT_IDS, disabled = false, maxEvents = Infinity, @@ -26,7 +27,7 @@ export function EventSelector({ return ( <>
diff --git a/app/webpacker/components/Results/Rankings/index.jsx b/app/webpacker/components/Results/Rankings/index.jsx index e4a5e344d00..6732e6f339c 100644 --- a/app/webpacker/components/Results/Rankings/index.jsx +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -1,5 +1,5 @@ import React, { - useEffect, useMemo, useReducer, useState, + useEffect, useMemo, useReducer, } from 'react'; import { Container } from 'semantic-ui-react'; import { useQuery } from '@tanstack/react-query'; @@ -18,6 +18,31 @@ const ActionTypes = { SET_SHOW: 'SET_SHOW', }; +function parseInitialStateFromUrl(url) { + const urlPattern = /\/results\/rankings\/(\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: @@ -35,34 +60,17 @@ function filterReducer(state, action) { } } -export default function Wrapper({ - event, region, year, rankingType, gender, show, -}) { +export default function Wrapper() { return ( - + ); } -export function Rankings({ - initialEvent, initialRegion, initialRankingType, initialGender, initialShow, -}) { +export function Rankings() { // Define the initial state - const initialState = useMemo(() => ({ - event: initialEvent, - region: initialRegion, - rankingType: initialRankingType, - gender: initialGender, - show: initialShow, - }), [initialEvent, initialGender, initialRankingType, initialRegion, initialShow]); + const initialState = useMemo(() => parseInitialStateFromUrl(window.location.href), []); // Use the reducer const [filterState, dispatch] = useReducer(filterReducer, initialState); diff --git a/app/webpacker/lib/requests/routes.js.erb b/app/webpacker/lib/requests/routes.js.erb index ea54b8b9c4a..2b4e4c27c34 100644 --- a/app/webpacker/lib/requests/routes.js.erb +++ b/app/webpacker/lib/requests/routes.js.erb @@ -303,7 +303,9 @@ export const rankingsUrl = (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(); - queryParams.append('region', region); + if (region !== 'world') { + queryParams.append('region', region); + } if (gender !== 'All') { queryParams.append('gender', gender); From 791a7436c2d28989b7ebdcd5d921d36e0f2c01a3 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 14 Jan 2025 10:19:11 +0100 Subject: [PATCH 23/39] Update app/webpacker/components/Results/Rankings/RankingsTable.jsx Co-authored-by: Kevin Matthews <49137025+kr-matthews@users.noreply.github.com> --- 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 9b6dbc0a039..3ee16de9ed1 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -27,7 +27,7 @@ function ResultRow({ const bestResultIndex = attempts.indexOf(bestResult); const worstResultIndex = attempts.indexOf(worstResult); return ( - + {show === 'by region' ? : {rank} } From fed3a8dee4b53dc52e4d0bb1b3a39c09b6151cdc Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Tue, 14 Jan 2025 19:28:17 +0900 Subject: [PATCH 24/39] Wrap table header into Row --- .../Results/Rankings/RankingsTable.jsx | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 3ee16de9ed1..0fb3d853d20 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -18,14 +18,16 @@ function CountryCell({ country }) { } function ResultRow({ - result, competition, rank, isAverage, show, country, key, + 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' ? @@ -61,8 +63,9 @@ export default function RankingsTable({ let rowsToMap = rows; let firstContinentIndex = 0; let firstCountryIndex = 0; + if (show === 'by region') { - [rowsToMap, firstContinentIndex, firstCountryIndex] = rows; + [rowsToMap, firstContinentIndex, firstCountryIndex] = rowsToMap; } return rowsToMap.reduce((acc, result, index) => { @@ -95,26 +98,29 @@ export default function RankingsTable({ 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')} - - - - - - )} + + {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) => ( From b8a4176a9bc91ff79baf1db59661b1f571b7fec3 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Tue, 14 Jan 2025 19:56:36 +0900 Subject: [PATCH 25/39] Improve filter box responsive layout --- .../CompetitionsOverview/CompetitionsFilters.js | 2 +- .../components/Results/resultsFilter.jsx | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 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 ( <> - + */} {/* */} {/* */} - + - + - + - + From 026dabe559bfa74d8170a08b6e3d84e0abfafc25 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 14 Jan 2025 12:07:35 +0100 Subject: [PATCH 26/39] don't use let in useMemo --- .../components/Results/Rankings/RankingsTable.jsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 0fb3d853d20..7c010ee7671 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -60,13 +60,8 @@ export default function RankingsTable({ rows, competitionsById, isAverage, show, }) { const results = useMemo(() => { - let rowsToMap = rows; - let firstContinentIndex = 0; - let firstCountryIndex = 0; - - if (show === 'by region') { - [rowsToMap, firstContinentIndex, firstCountryIndex] = rowsToMap; - } + const isByRegion = show === 'by region'; + const [rowsToMap, firstContinentIndex, firstCountryIndex] = isByRegion ? rows : [rows, 0, 0]; return rowsToMap.reduce((acc, result, index) => { const competition = competitionsById[result.competitionId]; From 4d3436cec1b0126b1ec480597db287164644466b Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 14 Jan 2025 12:16:12 +0100 Subject: [PATCH 27/39] use initializer Function --- app/webpacker/components/Results/Rankings/index.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/index.jsx b/app/webpacker/components/Results/Rankings/index.jsx index 6732e6f339c..fd5b50ad7f4 100644 --- a/app/webpacker/components/Results/Rankings/index.jsx +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -69,11 +69,11 @@ export default function Wrapper() { } export function Rankings() { - // Define the initial state - const initialState = useMemo(() => parseInitialStateFromUrl(window.location.href), []); - - // Use the reducer - const [filterState, dispatch] = useReducer(filterReducer, initialState); + const [filterState, dispatch] = useReducer( + filterReducer, + window.location.href, + parseInitialStateFromUrl, + ); const filterActions = useMemo( () => ({ From 78279c8171936d21d60a516913bd45a91b1ae29b Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Wed, 15 Jan 2025 10:22:28 +0100 Subject: [PATCH 28/39] add country.name to key --- app/webpacker/components/Results/Rankings/RankingsTable.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 7c010ee7671..926abb4c061 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -87,7 +87,7 @@ export default function RankingsTable({ country, rank, tiedPrevious, - key: `${result.id}-${show}`, + key: `${result.id}-${show}-${country.name}`, }); return acc; @@ -117,7 +117,7 @@ export default function RankingsTable({ )} - + {results.map((r) => ( Date: Fri, 17 Jan 2025 13:57:12 +0100 Subject: [PATCH 29/39] use react table to handle the keys --- .../Results/Rankings/RankingsTable.jsx | 110 +++++++++++++----- package.json | 1 + yarn.lock | 20 ++++ 3 files changed, 101 insertions(+), 30 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 926abb4c061..1deb3765df8 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -1,6 +1,7 @@ import React, { useMemo } from 'react'; import { Table } from 'semantic-ui-react'; import _ from 'lodash'; +import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; import I18n from '../../../lib/i18n'; import { formatAttemptResult } from '../../../lib/wca-live/attempts'; import CountryFlag from '../../wca/CountryFlag'; @@ -94,42 +95,91 @@ export default function RankingsTable({ }, []); }, [competitionsById, rows, show]); + const columns = useMemo(() => { + const commonColumns = [ + { + accessorKey: 'rank', + header: show !== 'by region' ? '#' : I18n.t('results.table_elements.region'), + cell: (info) => info.getValue(), + }, + { + accessorKey: 'result.name', + header: I18n.t('results.table_elements.name'), + cell: (info) => info.getValue(), + }, + { + accessorKey: 'result.value', + header: I18n.t('results.table_elements.result'), + cell: (info) => info.getValue(), + }, + ]; + + if (show !== 'by region') { + commonColumns.push({ + accessorKey: 'country.name', + header: I18n.t('results.table_elements.representing'), + cell: (info) => info.getValue(), + }); + } + + commonColumns.push({ + accessorKey: 'competition.name', + header: I18n.t('results.table_elements.competition'), + cell: (info) => info.getValue(), + }); + + if (isAverage) { + commonColumns.push( + ...Array(5).fill({ + accessorKey: 'solves', // Adjust as needed + header: I18n.t('results.table_elements.solves'), + }), + ); + } + + return commonColumns; + }, [show, isAverage]); + + const table = useReactTable({ + data: results, + columns, + getCoreRowModel: getCoreRowModel(), + }); + 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')} - - - - - - )} - + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + + ))} + + ))} - {results.map((r) => ( - - ))} + {table.getRowModel().rows.map((row) => { + const { + country, result, competition, rank, tiedPrevious, + } = row.original; + return ( + + ); + })}
diff --git a/package.json b/package.json index 559eea10257..1bf3706ef41 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@stripe/react-stripe-js": "^3.1.1", "@stripe/stripe-js": "^5.5.0", "@tanstack/react-query": "^5.64.0", + "@tanstack/react-table": "^8.20.6", "@wca/helpers": "^1.1.5", "autonumeric": "^4.10.7", "autoprefixer": "^10.4.20", diff --git a/yarn.lock b/yarn.lock index 25b66d8d0fb..dd884db7496 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2577,6 +2577,25 @@ __metadata: languageName: node linkType: hard +"@tanstack/react-table@npm:^8.20.6": + version: 8.20.6 + resolution: "@tanstack/react-table@npm:8.20.6" + dependencies: + "@tanstack/table-core": "npm:8.20.5" + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + checksum: 10c0/3213dc146f647fbd571f4e347007b969320819e588439b2ee95dd3a65efcbe30d097c24426dd82617041ed1e186182a5b303382bcebed5d61a1c6045a55c58d3 + languageName: node + linkType: hard + +"@tanstack/table-core@npm:8.20.5": + version: 8.20.5 + resolution: "@tanstack/table-core@npm:8.20.5" + checksum: 10c0/3c27b5debd61b6bd9bfbb40bfc7c5d5af90873ae1a566b20e3bf2d2f4f2e9a78061c081aacc5259a00e256f8df506ec250eb5472f5c01ff04baf9918b554982b + languageName: node + linkType: hard + "@trysound/sax@npm:0.2.0": version: 0.2.0 resolution: "@trysound/sax@npm:0.2.0" @@ -10222,6 +10241,7 @@ __metadata: "@stripe/react-stripe-js": "npm:^3.1.1" "@stripe/stripe-js": "npm:^5.5.0" "@tanstack/react-query": "npm:^5.64.0" + "@tanstack/react-table": "npm:^8.20.6" "@wca/helpers": "npm:^1.1.5" autonumeric: "npm:^4.10.7" autoprefixer: "npm:^10.4.20" From 7479bf65d78c3feeac096595a7e90c4929c4e332 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Fri, 17 Jan 2025 14:01:56 +0100 Subject: [PATCH 30/39] don't show mbld average --- app/webpacker/components/Results/Rankings/index.jsx | 2 +- app/webpacker/components/Results/resultsFilter.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/index.jsx b/app/webpacker/components/Results/Rankings/index.jsx index fd5b50ad7f4..a8fd70f60c1 100644 --- a/app/webpacker/components/Results/Rankings/index.jsx +++ b/app/webpacker/components/Results/Rankings/index.jsx @@ -19,7 +19,7 @@ const ActionTypes = { }; function parseInitialStateFromUrl(url) { - const urlPattern = /\/results\/rankings\/(\d+)\/(\w+)/; // Matches `/results/rankings/{event}/{rankingType}` + const urlPattern = /\/results\/rankings\/(\w+)\/(\w+)/; // Matches `/results/rankings/{event}/{rankingType}` const match = url.match(urlPattern); if (!match) { diff --git a/app/webpacker/components/Results/resultsFilter.jsx b/app/webpacker/components/Results/resultsFilter.jsx index 1ba0e1b31c8..254f318c53f 100644 --- a/app/webpacker/components/Results/resultsFilter.jsx +++ b/app/webpacker/components/Results/resultsFilter.jsx @@ -64,7 +64,7 @@ export default function ResultsFilter({ filterState, filterActions }) { > {I18n.t('results.selector_elements.type_selector.single')} - + { event !== '333mbf' && } {/* */} From 7f711e914cf49de62521863b2d380ae9bc1d729c Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 20 Jan 2025 12:19:05 +0100 Subject: [PATCH 31/39] fxi average header and remove cell call --- .../components/Results/Rankings/RankingsTable.jsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/webpacker/components/Results/Rankings/RankingsTable.jsx b/app/webpacker/components/Results/Rankings/RankingsTable.jsx index 1deb3765df8..21f1213e5bb 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -100,17 +100,14 @@ export default function RankingsTable({ { accessorKey: 'rank', header: show !== 'by region' ? '#' : I18n.t('results.table_elements.region'), - cell: (info) => info.getValue(), }, { accessorKey: 'result.name', header: I18n.t('results.table_elements.name'), - cell: (info) => info.getValue(), }, { accessorKey: 'result.value', header: I18n.t('results.table_elements.result'), - cell: (info) => info.getValue(), }, ]; @@ -118,21 +115,23 @@ export default function RankingsTable({ commonColumns.push({ accessorKey: 'country.name', header: I18n.t('results.table_elements.representing'), - cell: (info) => info.getValue(), }); } commonColumns.push({ accessorKey: 'competition.name', header: I18n.t('results.table_elements.competition'), - cell: (info) => info.getValue(), }); if (isAverage) { + commonColumns.push({ + accessorKey: 'solves', + header: I18n.t('results.table_elements.solves'), + }); commonColumns.push( - ...Array(5).fill({ - accessorKey: 'solves', // Adjust as needed - header: I18n.t('results.table_elements.solves'), + ...Array(4).fill({ + accessorKey: '', + header: ' ', }), ); } From 131831a9c73d2c7492bc6609f03fc6ab1fff4bd2 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 21 Jan 2025 12:17:26 +0100 Subject: [PATCH 32/39] introduce countries.byId --- app/controllers/results_controller.rb | 2 +- app/webpacker/components/Results/Rankings/RankingsTable.jsx | 4 ++-- app/webpacker/lib/wca-data.js.erb | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index 299c0abcde4..c98ae0ad614 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[country], include: [], only: %w[cellName id]) } + 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]) } { 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 21f1213e5bb..e30b4d04763 100644 --- a/app/webpacker/components/Results/Rankings/RankingsTable.jsx +++ b/app/webpacker/components/Results/Rankings/RankingsTable.jsx @@ -41,7 +41,7 @@ function ResultRow({
{show !== 'by region' && } - + {' '} {competition.cellName} @@ -75,7 +75,7 @@ export default function RankingsTable({ const rank = value === previousValue ? previousRank : index + 1; const tiedPrevious = rank === previousRank; - let country = countries.real.find((c) => c.id === result.countryId); + let country = countries.byId[result.countryId]; if (index < firstContinentIndex) { country = { name: I18n.t('results.table_elements.world') }; } else if (index >= firstContinentIndex && index < firstCountryIndex) { diff --git a/app/webpacker/lib/wca-data.js.erb b/app/webpacker/lib/wca-data.js.erb index 2615f4784b0..43927d13298 100644 --- a/app/webpacker/lib/wca-data.js.erb +++ b/app/webpacker/lib/wca-data.js.erb @@ -25,6 +25,7 @@ const realCountryData = countryData.filter((c) => !fictionalCountryIds.includes( export const countries = { byIso2: _.mapValues(_.keyBy(countryData, 'iso2'), extendCountries), + byId: _.mapValues(_.keyBy(countryData, 'id'), extendCountries), real: _.map(realCountryData, extendCountries), }; From 3fdd94413653386ba848784ca4a6ea1427f3871f Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 21 Jan 2025 12:22:51 +0100 Subject: [PATCH 33/39] revert gregor change --- .../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 09b731a4650..7d756053fe5 100644 --- a/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js +++ b/app/webpacker/components/CompetitionsOverview/CompetitionsFilters.js @@ -159,7 +159,7 @@ function TimeOrderButtonGroup({ filterState, dispatchFilter }) { return ( <> - + @@ -81,7 +81,7 @@ export default function ResultsFilter({ filterState, filterActions }) { - +
{I18n.t('results.selector_elements.show_selector.show')}
From 362e3ff84225e6bc98b170e972efd77fdffc3a8b Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 21 Jan 2025 14:19:29 +0100 Subject: [PATCH 39/39] move WCA_EVENT_IDS to wca-data.js.erb --- app/webpacker/components/CompetitionsOverview/filterUtils.js | 4 +--- app/webpacker/components/wca/EventSelector.jsx | 4 +--- app/webpacker/lib/wca-data.js.erb | 2 ++ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/webpacker/components/CompetitionsOverview/filterUtils.js b/app/webpacker/components/CompetitionsOverview/filterUtils.js index 3effb9e8a2c..5ac77387322 100644 --- a/app/webpacker/components/CompetitionsOverview/filterUtils.js +++ b/app/webpacker/components/CompetitionsOverview/filterUtils.js @@ -1,10 +1,8 @@ import { DateTime } from 'luxon'; import { - continents, countries, events, nonFutureCompetitionYears, + continents, countries, nonFutureCompetitionYears, WCA_EVENT_IDS, } from '../../lib/wca-data.js.erb'; -const WCA_EVENT_IDS = Object.values(events.official).map((e) => e.id); - // note: inconsistencies with previous search params // - year value was 'all+years', is now 'all_years' // --> handled by the fact that every non-number is interpreted as "default value" all_years diff --git a/app/webpacker/components/wca/EventSelector.jsx b/app/webpacker/components/wca/EventSelector.jsx index 0a3c3b9ac96..887c09dae33 100644 --- a/app/webpacker/components/wca/EventSelector.jsx +++ b/app/webpacker/components/wca/EventSelector.jsx @@ -2,11 +2,9 @@ import React from 'react'; import { Button, Icon, Popup, } from 'semantic-ui-react'; -import { events } from '../../lib/wca-data.js.erb'; +import { WCA_EVENT_IDS } from '../../lib/wca-data.js.erb'; import I18n from '../../lib/i18n'; -const WCA_EVENT_IDS = Object.values(events.official).map((e) => e.id); - // eslint-disable-next-line import/prefer-default-export export function EventSelector({ selectedEvents, diff --git a/app/webpacker/lib/wca-data.js.erb b/app/webpacker/lib/wca-data.js.erb index 43927d13298..19db59a8afc 100644 --- a/app/webpacker/lib/wca-data.js.erb +++ b/app/webpacker/lib/wca-data.js.erb @@ -102,6 +102,8 @@ export const events = { byId: _.mapValues(_.keyBy(eventsData, 'id'), extendEvents), }; +export const WCA_EVENT_IDS = Object.values(events.official).map((e) => e.id); + function extendEvents(rawEvent) { return { ...rawEvent,