From 43b53b012c2427e61cfa798ac26b1f62df3244a7 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Wed, 11 Dec 2024 18:13:39 +0100 Subject: [PATCH 01/49] Move Schedule and Event Tab to Tab Folder --- .../{ => CompetitionTabs}/EventsTable/index.jsx | 6 +++--- .../Schedule/AddToCalendar.jsx | 0 .../Schedule/CalendarView.jsx | 6 +++--- .../{ => CompetitionTabs}/Schedule/TableView.jsx | 16 ++++++++-------- .../{ => CompetitionTabs}/Schedule/TimeZone.jsx | 6 +++--- .../Schedule/VenuesAndRooms.jsx | 6 +++--- .../Schedule/ViewSelector.jsx | 2 +- .../{ => CompetitionTabs}/Schedule/index.jsx | 10 +++++----- 8 files changed, 26 insertions(+), 26 deletions(-) rename app/webpacker/components/{ => CompetitionTabs}/EventsTable/index.jsx (96%) rename app/webpacker/components/{ => CompetitionTabs}/Schedule/AddToCalendar.jsx (100%) rename app/webpacker/components/{ => CompetitionTabs}/Schedule/CalendarView.jsx (95%) rename app/webpacker/components/{ => CompetitionTabs}/Schedule/TableView.jsx (96%) rename app/webpacker/components/{ => CompetitionTabs}/Schedule/TimeZone.jsx (93%) rename app/webpacker/components/{ => CompetitionTabs}/Schedule/VenuesAndRooms.jsx (97%) rename app/webpacker/components/{ => CompetitionTabs}/Schedule/ViewSelector.jsx (92%) rename app/webpacker/components/{ => CompetitionTabs}/Schedule/index.jsx (94%) diff --git a/app/webpacker/components/EventsTable/index.jsx b/app/webpacker/components/CompetitionTabs/EventsTable/index.jsx similarity index 96% rename from app/webpacker/components/EventsTable/index.jsx rename to app/webpacker/components/CompetitionTabs/EventsTable/index.jsx index eec86a0980..1381817013 100644 --- a/app/webpacker/components/EventsTable/index.jsx +++ b/app/webpacker/components/CompetitionTabs/EventsTable/index.jsx @@ -7,15 +7,15 @@ import { TableHeaderCell, TableRow, } from 'semantic-ui-react'; -import I18n from '../../lib/i18n'; -import { events, formats } from '../../lib/wca-data.js.erb'; +import I18n from '../../../lib/i18n'; +import { events, formats } from '../../../lib/wca-data.js.erb'; import { advancementConditionToString, cutoffToString, eventQualificationToString, getRoundTypeId, timeLimitToString, -} from '../../lib/utils/wcif'; +} from '../../../lib/utils/wcif'; export default function EventsTable({ competitionInfo, wcifEvents }) { return ( diff --git a/app/webpacker/components/Schedule/AddToCalendar.jsx b/app/webpacker/components/CompetitionTabs/Schedule/AddToCalendar.jsx similarity index 100% rename from app/webpacker/components/Schedule/AddToCalendar.jsx rename to app/webpacker/components/CompetitionTabs/Schedule/AddToCalendar.jsx diff --git a/app/webpacker/components/Schedule/CalendarView.jsx b/app/webpacker/components/CompetitionTabs/Schedule/CalendarView.jsx similarity index 95% rename from app/webpacker/components/Schedule/CalendarView.jsx rename to app/webpacker/components/CompetitionTabs/Schedule/CalendarView.jsx index 4338523bad..f50b90d8ce 100644 --- a/app/webpacker/components/Schedule/CalendarView.jsx +++ b/app/webpacker/components/CompetitionTabs/Schedule/CalendarView.jsx @@ -9,9 +9,9 @@ import { isOrphanedActivity, latestTimeOfDayWithBuffer, localizeActivityName, -} from '../../lib/utils/activities'; -import { ACTIVITY_OTHER_GREY, getTextColor } from '../../lib/utils/calendar'; -import I18n from '../../lib/i18n'; +} from '../../../lib/utils/activities'; +import { ACTIVITY_OTHER_GREY, getTextColor } from '../../../lib/utils/calendar'; +import I18n from '../../../lib/i18n'; // We can render custom content for the individual fullcalendar events, by // passing in a render function as the `eventContent` param to the `FullCalendar` diff --git a/app/webpacker/components/Schedule/TableView.jsx b/app/webpacker/components/CompetitionTabs/Schedule/TableView.jsx similarity index 96% rename from app/webpacker/components/Schedule/TableView.jsx rename to app/webpacker/components/CompetitionTabs/Schedule/TableView.jsx index becb769519..71dea6e274 100644 --- a/app/webpacker/components/Schedule/TableView.jsx +++ b/app/webpacker/components/CompetitionTabs/Schedule/TableView.jsx @@ -12,20 +12,20 @@ import { groupActivities, isOrphanedActivity, localizeActivityName, -} from '../../lib/utils/activities'; -import { getSimpleTimeString } from '../../lib/utils/dates'; -import { toDegrees } from '../../lib/utils/edit-schedule'; +} from '../../../lib/utils/activities'; +import { getSimpleTimeString } from '../../../lib/utils/dates'; +import { toDegrees } from '../../../lib/utils/edit-schedule'; import AddToCalendar from './AddToCalendar'; -import useStoredState from '../../lib/hooks/useStoredState'; -import I18n from '../../lib/i18n'; -import { formats } from '../../lib/wca-data.js.erb'; +import useStoredState from '../../../lib/hooks/useStoredState'; +import I18n from '../../../lib/i18n'; +import { formats } from '../../../lib/wca-data.js.erb'; import { parseActivityCode, timeLimitToString, advancementConditionToString, cutoffToString, -} from '../../lib/utils/wcif'; -import '../../stylesheets/schedule_events.scss'; +} from '../../../lib/utils/wcif'; +import '../../../stylesheets/schedule_events.scss'; export default function TableView({ dates, diff --git a/app/webpacker/components/Schedule/TimeZone.jsx b/app/webpacker/components/CompetitionTabs/Schedule/TimeZone.jsx similarity index 93% rename from app/webpacker/components/Schedule/TimeZone.jsx rename to app/webpacker/components/CompetitionTabs/Schedule/TimeZone.jsx index ba262c4ac9..e72b0edfc2 100644 --- a/app/webpacker/components/Schedule/TimeZone.jsx +++ b/app/webpacker/components/CompetitionTabs/Schedule/TimeZone.jsx @@ -7,9 +7,9 @@ import { Segment, } from 'semantic-ui-react'; import _ from 'lodash'; -import I18n from '../../lib/i18n'; -import { backendTimezones } from '../../lib/wca-data.js.erb'; -import { sortByOffset } from '../../lib/utils/timezone'; +import I18n from '../../../lib/i18n'; +import { backendTimezones } from '../../../lib/wca-data.js.erb'; +import { sortByOffset } from '../../../lib/utils/timezone'; // Timezones that our Ruby backend knows about. They represent values that might be stored // in the 'competition_venues' table. diff --git a/app/webpacker/components/Schedule/VenuesAndRooms.jsx b/app/webpacker/components/CompetitionTabs/Schedule/VenuesAndRooms.jsx similarity index 97% rename from app/webpacker/components/Schedule/VenuesAndRooms.jsx rename to app/webpacker/components/CompetitionTabs/Schedule/VenuesAndRooms.jsx index 08abdd3c0f..926a7e980b 100644 --- a/app/webpacker/components/Schedule/VenuesAndRooms.jsx +++ b/app/webpacker/components/CompetitionTabs/Schedule/VenuesAndRooms.jsx @@ -3,9 +3,9 @@ import { Button, Checkbox, Grid, Header, Menu, Message, Segment, Transition, } from 'semantic-ui-react'; -import { getTextColor, TEXT_WHITE } from '../../lib/utils/calendar'; -import { toDegrees } from '../../lib/utils/edit-schedule'; -import I18n from '../../lib/i18n'; +import { getTextColor, TEXT_WHITE } from '../../../lib/utils/calendar'; +import { toDegrees } from '../../../lib/utils/edit-schedule'; +import I18n from '../../../lib/i18n'; export default function VenuesAndRooms({ venues, diff --git a/app/webpacker/components/Schedule/ViewSelector.jsx b/app/webpacker/components/CompetitionTabs/Schedule/ViewSelector.jsx similarity index 92% rename from app/webpacker/components/Schedule/ViewSelector.jsx rename to app/webpacker/components/CompetitionTabs/Schedule/ViewSelector.jsx index d4b016b2bf..d26c0cd8c4 100644 --- a/app/webpacker/components/Schedule/ViewSelector.jsx +++ b/app/webpacker/components/CompetitionTabs/Schedule/ViewSelector.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { Menu } from 'semantic-ui-react'; -import I18n from '../../lib/i18n'; +import I18n from '../../../lib/i18n'; const views = ['calendar', 'table']; diff --git a/app/webpacker/components/Schedule/index.jsx b/app/webpacker/components/CompetitionTabs/Schedule/index.jsx similarity index 94% rename from app/webpacker/components/Schedule/index.jsx rename to app/webpacker/components/CompetitionTabs/Schedule/index.jsx index de854c0792..2020eeecf7 100644 --- a/app/webpacker/components/Schedule/index.jsx +++ b/app/webpacker/components/CompetitionTabs/Schedule/index.jsx @@ -5,11 +5,11 @@ import TableView from './TableView'; import TimeZoneSelector from './TimeZone'; import VenuesAndRooms from './VenuesAndRooms'; import ViewSelector from './ViewSelector'; -import useStoredState from '../../lib/hooks/useStoredState'; -import { earliestWithLongestTieBreaker } from '../../lib/utils/activities'; -import { getDatesBetweenInclusive } from '../../lib/utils/dates'; -import { EventSelector } from '../CompetitionsOverview/CompetitionsFilters'; -import I18n from '../../lib/i18n'; +import useStoredState from '../../../lib/hooks/useStoredState'; +import { earliestWithLongestTieBreaker } from '../../../lib/utils/activities'; +import { getDatesBetweenInclusive } from '../../../lib/utils/dates'; +import { EventSelector } from '../../CompetitionsOverview/CompetitionsFilters'; +import I18n from '../../../lib/i18n'; const activeIdReducer = (state, { type, id, ids }) => { let newState = [...state]; From 51fb886761e196250bf55a0bde312a3e9344a8da Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Wed, 11 Dec 2024 18:13:52 +0100 Subject: [PATCH 02/49] create new tab component --- .../CompetitionTabs/CompetitionTab.jsx | 18 +++++++ .../CompetitionTabs/GeneralInfoTab.jsx | 15 ++++++ .../components/CompetitionTabs/index.jsx | 48 +++++++++++++++++++ .../components/CompetitionTabs/style.css | 5 ++ app/webpacker/lib/requests/routes.js.erb | 2 + 5 files changed, 88 insertions(+) create mode 100644 app/webpacker/components/CompetitionTabs/CompetitionTab.jsx create mode 100644 app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx create mode 100644 app/webpacker/components/CompetitionTabs/index.jsx create mode 100644 app/webpacker/components/CompetitionTabs/style.css diff --git a/app/webpacker/components/CompetitionTabs/CompetitionTab.jsx b/app/webpacker/components/CompetitionTabs/CompetitionTab.jsx new file mode 100644 index 0000000000..2e83711ad4 --- /dev/null +++ b/app/webpacker/components/CompetitionTabs/CompetitionTab.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import { Icon, TabPane } from 'semantic-ui-react'; +import Markdown from '../Markdown'; +import { editCompetitionTabUrl } from '../../lib/requests/routes.js.erb'; + +export default function CompetitionTab({ tab, canManage, competition }) { + return ( + + + { canManage && ( + + + Edit + + )} + + ); +} diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx new file mode 100644 index 0000000000..1e24751822 --- /dev/null +++ b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { TabPane } from 'semantic-ui-react'; + +export default function GeneralInfoTab({ competition }) { + return ( + + {/*
*/} + {/* < */} + {/* %= render layout: 'results_nav', locals: {layout_nav: false} do %> */} + {/* <%= render "results_table", results: @competition.winning_results, hide_pos: true, hide_round: true %> */} + {/* <% end %> */} + {/*
*/} +
+ ); +} diff --git a/app/webpacker/components/CompetitionTabs/index.jsx b/app/webpacker/components/CompetitionTabs/index.jsx new file mode 100644 index 0000000000..b476f85491 --- /dev/null +++ b/app/webpacker/components/CompetitionTabs/index.jsx @@ -0,0 +1,48 @@ +import React, { useMemo } from 'react'; +import { Tab, TabPane } from 'semantic-ui-react'; +import GeneralInfoTab from './GeneralInfoTab'; +import CompetitionTab from './CompetitionTab'; +import EventsTable from './EventsTable'; +import Schedule from './Schedule'; +import WCAQueryClientProvider from '../../lib/providers/WCAQueryClientProvider'; +import './style.css'; + +export default function Wrapper({ + tabs, competition, wcifEvents, wcifSchedule, locale, +}) { + const panes = useMemo(() => { + const p = [{ menuItem: 'General Info', render: () => }]; + if (competition['has_rounds?']) { + p.push({ + menuItem: 'Events', + render: () => ( + + + + ), + }); + } + if (competition['has_schedule?']) { + p.push({ + menuItem: 'Schedule', + render: () => ( + + + + ), + }); + } + tabs.map((tab) => p.push({ menuItem: tab.name, render: () => })); + return p; + }, [competition, locale, tabs, wcifEvents, wcifSchedule]); + return ( + + + + ); +} diff --git a/app/webpacker/components/CompetitionTabs/style.css b/app/webpacker/components/CompetitionTabs/style.css new file mode 100644 index 0000000000..27256999aa --- /dev/null +++ b/app/webpacker/components/CompetitionTabs/style.css @@ -0,0 +1,5 @@ +.tab-wrapped { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} diff --git a/app/webpacker/lib/requests/routes.js.erb b/app/webpacker/lib/requests/routes.js.erb index 5b182eb110..bce1a8e132 100644 --- a/app/webpacker/lib/requests/routes.js.erb +++ b/app/webpacker/lib/requests/routes.js.erb @@ -25,6 +25,8 @@ export const postsUrl = (page, format = 'json') => { return `<%= CGI.unescape(Rails.application.routes.url_helpers.posts_path(format: "${format}")) %>?page=${page}`; }; +export const editCompetitionTabUrl = (competitionId, tabId) => `<%= CGI.unescape(Rails.application.routes.url_helpers.edit_competition_tab_path(competition_id: "${competitionId}", id: "${tabId}")) %>?page=${page}`; + export const incidentsUrl = (perPage, page, tags = undefined, searchString = undefined, competitions = undefined, format = 'json') => { const searchParams = new URLSearchParams(`per_page=${perPage}&page=${page}`); if (tags && tags.length > 0) { From c0b81d1f10506b422e997e8c9a3310d4c3d636c7 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Wed, 11 Dec 2024 18:14:20 +0100 Subject: [PATCH 03/49] use tab component in old erb template --- app/views/competitions/_nav.html.erb | 27 ----------------- app/views/competitions/show.html.erb | 45 ++++------------------------ 2 files changed, 5 insertions(+), 67 deletions(-) diff --git a/app/views/competitions/_nav.html.erb b/app/views/competitions/_nav.html.erb index e0c862a5de..4b683d4bcf 100644 --- a/app/views/competitions/_nav.html.erb +++ b/app/views/competitions/_nav.html.erb @@ -296,32 +296,5 @@ }, }, }); - - $('#not-bookmarked').click(function() { - window.wca.cancelPendingAjaxAndAjax('bookmark', { - url: '<%= bookmark_path %>', - method: 'POST', - data: { - 'id': <%= @competition.id.to_json.html_safe %>, - }, - success: function(data) { - $('#not-bookmarked').toggle(); - $('#bookmarked').toggle(); - } - }); - }); - $('#bookmarked').click(function() { - window.wca.cancelPendingAjaxAndAjax('bookmark', { - url: '<%= unbookmark_path %>', - method: 'POST', - data: { - 'id': <%= @competition.id.to_json.html_safe %>, - }, - success: function(data) { - $('#not-bookmarked').toggle(); - $('#bookmarked').toggle(); - } - }); - }); })(); diff --git a/app/views/competitions/show.html.erb b/app/views/competitions/show.html.erb index b0e1a8761d..031413e35b 100644 --- a/app/views/competitions/show.html.erb +++ b/app/views/competitions/show.html.erb @@ -1,45 +1,10 @@ <% provide(:title, @competition.display_name) %> <%# We use this to hide the 'Events' information for competition which didn't declared rounds %> <%= render layout: 'nav' do %> - - -
-
- <%= render layout: 'results_nav', locals: { layout_nav: false } do %> - <%= render "results_table", results: @competition.winning_results, hide_pos: true, hide_round: true %> - <% end %> -
- <% if @competition.has_rounds? %> -
- <%= render "events_tab" %> -
- <% end %> - <% if @competition.has_schedule? %> -
- <%= render "competition_schedule_tab", competition: @competition %> -
- <% end %> - <% @competition.tabs.each do |tab| %> -
- <%=md tab.content %> - <% if current_user&.can_manage_competition?(@competition) %> - <%= link_to edit_competition_tab_path(@competition, tab), class: "btn btn-primary" do %> - <%= ui_icon("edit") %> Edit - <% end %> - <% end %> -
- <% end %> + <%= react_component("CompetitionTabs", { + tabs: @competition.tabs.as_json, + competition: @competition.as_json({ only: %w[name], methods: %w[has_rounds? has_schedule?]}), + wcifEvents: @competition.events_wcif, wcifSchedule: @competition.schedule_wcif, locale: I18n.locale, + }) %> <%= render "time_limit_cutoff_format_info", competition: @competition %> -
<% end %> From cab1a193d16e2a94b683a2704b1156832519dd7d Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Wed, 11 Dec 2024 20:13:23 +0100 Subject: [PATCH 04/49] registration requirements wip --- .../CompetitionTabs/GeneralInfoTab.jsx | 286 +++++++++++++++++- .../RegistrationRequirements.jsx | 131 ++++++++ .../components/CompetitionTabs/index.jsx | 4 +- app/webpacker/lib/requests/routes.js.erb | 2 +- 4 files changed, 409 insertions(+), 14 deletions(-) create mode 100644 app/webpacker/components/CompetitionTabs/RegistrationRequirements.jsx diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx index 1e24751822..5454fc841b 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx @@ -1,15 +1,279 @@ -import React from 'react'; -import { TabPane } from 'semantic-ui-react'; +import React, { useState } from 'react'; + +import { Button, Icon } from 'semantic-ui-react'; +import I18n from '../../lib/i18n'; +import { countries } from '../../lib/wca-data.js.erb'; +import { competitionUrl } from '../../lib/requests/routes.js.erb'; +import EventIcon from '../wca/EventIcon'; +import Markdown from '../Markdown'; +import RegistrationRequirements from './RegistrationRequirements'; + +const linkToGoogleMapsPlace = (latitude, longitude) => `https://www.google.com/maps/place/${latitude},${longitude}`; +const usersToSentence = () => ''; + +export default function GeneralInfoTab({ + competition, + userInfo, +}) { + const [showRegistrationRequirements, setShowRegistrationRequirements] = useState(!competition['is_probably_over?']); + const [showHighlights, setShowHighlights] = useState(false); -export default function GeneralInfoTab({ competition }) { return ( - - {/*
*/} - {/* < */} - {/* %= render layout: 'results_nav', locals: {layout_nav: false} do %> */} - {/* <%= render "results_table", results: @competition.winning_results, hide_pos: true, hide_round: true %> */} - {/* <% end %> */} - {/*
*/} -
+
+
+
+
{I18n.t('competitions.competition_info.date')}
+
+ {competition.date_range} + + + +
+ +
{I18n.t('competitions.competition_info.city')}
+
+ {competition.city} + {`, ${countries.byIso2[competition.country_iso2].name}`} +
+ +
{I18n.t('competitions.competition_info.venue')}
+
+ +
{I18n.t('competitions.competition_info.address')}
+
+ + {competition.venue_address} + +
+ + {competition.venue_details && ( + <> +
{I18n.t('competitions.competition_info.details')}
+
{competition.venue_details}
+ + )} + + {competition.external_website && ( + <> +
{I18n.t('competitions.competition_info.website')}
+
+ + {`${competition.name} website`} + +
+ + )} + +
{I18n.t('competitions.competition_info.contact')}
+
+ {competition.contact ? ( + + ) : ( + + {I18n.t('competitions.competition_info.organization_team')} + + )} +
+ + {competition.organizers.length > 0 && ( + <> +
{I18n.t('competitions.competition_info.organizer_plural', { count: competition.organizers.length })}
+
{usersToSentence(competition.organizers, true)}
+ + )} + +
{I18n.t('competitions.competition_info.delegate', { count: competition.delegates.length })}
+
{usersToSentence(competition.delegates, true)}
+
+ + {competition['has_schedule?'] && ( +
+
+
+ {I18n.t('competitions.competition_info.pdf.download_html', { + here: ( + + {I18n.t('common.here')} + + ), + })} +
+
+ )} +
+ +
+
+
{I18n.t('competitions.competition_info.information')}
+
+ +
+
+ +
+
{I18n.t('competitions.competition_info.events')}
+
+ {competition.events.map((event) => ( + + + + ))} +
+ + {competition.main_event_id && ( + <> +
{I18n.t('competitions.competition_info.main_event')}
+
+ +
+ + )} + + {competition['results_posted?'] && ( + <> +
{I18n.t('competitions.nav.menu.competitors')}
+
{competition.competitors.length}
+ + )} +
+ + {(competition.media.accepted ?? []).map((mediaType) => ( +
+ +
+ +
+
+ ))} + + {!competition['results_posted?'] && competition.competitor_limit_enabled && ( +
+
{I18n.t('competitions.competition_info.competitor_limit')}
+
{competition.competitor_limit}
+
+ )} + + {!competition['results_posted?'] && ( +
+
{I18n.t('competitions.competition_info.number_of_bookmarks')}
+
{competition.number_of_bookmarks}
+
+ )} +
+ +
+ {competition.registration_open && competition.registration_close && ( +
+
{I18n.t('competitions.competition_info.registration_period.label')}
+
+

+ {competition['registration_not_yet_opened?'] + ? I18n.t('competitions.competition_info.registration_period.range_future_html', { + startDateAndTime: competition.registration_open, + endDateAndTime: competition.registration_close, + }) + : competition['registration_past?'] + ? I18n.t('competitions.competition_info.registration_period.range_past_html', { + startDateAndTime: competition.registration_open, + endDateAndTime: competition.registration_close, + }) + : I18n.t('competitions.competition_info.registration_period.range_ongoing_html', { + startDateAndTime: competition.registration_open, + endDateAndTime: competition.registration_close, + })} +

+
+
+ )} + +
+
{I18n.t('competitions.competition_info.registration_requirements')}
+
+
+ {showRegistrationRequirements ? ( + <> +
+ +
+ + + ) : ( + + )} +
+
+
+ + {competition.userCanViewResults && (competition.mainEvent || records) && ( +
+
{I18n.t('competitions.competition_info.highlights')}
+
+
+ {showHighlights ? ( + <> + +
+ {competition.main_event_id &&

{winners(competition, competition.mainEvent)}

} + {records &&

{records}

} +
+ + ) : ( + + )} +
+
+
+ )} +
+
); } diff --git a/app/webpacker/components/CompetitionTabs/RegistrationRequirements.jsx b/app/webpacker/components/CompetitionTabs/RegistrationRequirements.jsx new file mode 100644 index 0000000000..44acb50b68 --- /dev/null +++ b/app/webpacker/components/CompetitionTabs/RegistrationRequirements.jsx @@ -0,0 +1,131 @@ +import React from 'react'; +import I18n from '../../lib/i18n'; +import I18nHTMLTranslate from '../I18nHTMLTranslate'; +import Markdown from '../Markdown'; + +export default function RegistrationRequirements({ competition, userInfo, showLinksToRegisterPage = false }) { + const formatMoney = (amount) => `$${(amount / 100).toFixed(2)}`; // Example formatting, adjust as needed + const wcaLocalTime = (time) => new Date(time).toLocaleString(); // Example formatting, adjust as needed + + return ( +
+ {competition.use_wca_registration && ( +
+ {!userInfo && ( + <> + ${I18n.t('common.here')}`, + }} + /> + {' '} + ${I18n.t('common.here')}`, + }} + /> + + )} + {userInfo && !userInfo.wca_id && !userInfo.unconfirmed_wca_id && ( + ${I18n.t('common.here')}`, + }} + /> + )} + {showLinksToRegisterPage ? ( + ${I18n.t('common.here')}`, + }} + /> + ) : ( +

{I18n.t('competitions.competition_info.register_below_html')}

+ )} +
+ )} + + {competition.external_registration_page && ( +

${I18n.t('common.here')}`, + }), + }} + /> + )} + + {competition['part_of_competition_series?'] && ( +

+

+ {I18n.t('competitions.competition_info.part_of_a_series_list', { + name: competition.competition_series.name, + })} +

+
    + {competition.series_sibling_competitions.map((comp) => ( +
  • + {comp.name} +
  • + ))} +
+

{I18n.t('competitions.competition_info.series_registration_warning_html')}

+
+ )} + + {competition.competitor_limit_enabled && ( +

+ {I18n.t( + competition.competitor_limit_enabled + ? 'competitions.competition_info.competitor_limit_is' + : 'competitions.competition_info.no_competitor_limit', + { competitor_limit: competition.competitor_limit }, + )} +

+ )} + + {competition['has_fees?'] && ( +
+

+ {competition.base_entry_fee_lowest_denomination + ? I18n.t('competitions.competition_info.entry_fee_is', { + base_entry_fee: formatMoney(competition.base_entry_fee), + }) + : I18n.t('competitions.competition_info.no_entry_fee')} +

+ {competition.competition_events.map((event) => (event['has_fee?'] ? ( +
+
{event.event.name}
+
{formatMoney(event.fee)}
+
+ ) : null))} +
+ )} + + {competition['using_payment_integrations?'] && ( +

${t( + 'common.here', + )}`, + }, + ), + }} + /> + )} + + {competition.extra_registration_requirements && ( + + )} +

+ ); +} diff --git a/app/webpacker/components/CompetitionTabs/index.jsx b/app/webpacker/components/CompetitionTabs/index.jsx index b476f85491..42ed1647a0 100644 --- a/app/webpacker/components/CompetitionTabs/index.jsx +++ b/app/webpacker/components/CompetitionTabs/index.jsx @@ -8,10 +8,10 @@ import WCAQueryClientProvider from '../../lib/providers/WCAQueryClientProvider'; import './style.css'; export default function Wrapper({ - tabs, competition, wcifEvents, wcifSchedule, locale, + tabs, competition, wcifEvents, wcifSchedule, locale, userInfo, }) { const panes = useMemo(() => { - const p = [{ menuItem: 'General Info', render: () => }]; + const p = [{ menuItem: 'General Info', render: () => }]; if (competition['has_rounds?']) { p.push({ menuItem: 'Events', diff --git a/app/webpacker/lib/requests/routes.js.erb b/app/webpacker/lib/requests/routes.js.erb index bce1a8e132..4ddd5e1232 100644 --- a/app/webpacker/lib/requests/routes.js.erb +++ b/app/webpacker/lib/requests/routes.js.erb @@ -47,7 +47,7 @@ export const incidentUrl = (id) => `<%= CGI.unescape(Rails.application.routes.ur export const homepageUrl = `<%= CGI.unescape(Rails.application.routes.url_helpers.root_path)%>` export const createCompetitionUrl = `<%= CGI.unescape(Rails.application.routes.url_helpers.competitions_path)%>`; -export const competitionUrl = (id) => `<%= CGI.unescape(Rails.application.routes.url_helpers.competition_path("${id}"))%>`; +export const competitionUrl = (id, format = 'html') => `<%= CGI.unescape(Rails.application.routes.url_helpers.competition_path("${id}", format: "${format}"))%>`; export const adminCompetitionUrl = (id) => `<%= CGI.unescape(Rails.application.routes.url_helpers.competition_admin_edit_path("${id}"))%>`; export const competitionReportUrl = (id) => `<%= CGI.unescape(Rails.application.routes.url_helpers.delegate_report_path("${id}"))%>`; From f3ce4bbd55897d595da306d61dd867ec54c262fc Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Wed, 11 Dec 2024 20:16:59 +0100 Subject: [PATCH 05/49] needed competition attributes --- app/views/competitions/show.html.erb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/views/competitions/show.html.erb b/app/views/competitions/show.html.erb index 031413e35b..ed7f85591d 100644 --- a/app/views/competitions/show.html.erb +++ b/app/views/competitions/show.html.erb @@ -1,10 +1,14 @@ <% provide(:title, @competition.display_name) %> -<%# We use this to hide the 'Events' information for competition which didn't declared rounds %> <%= render layout: 'nav' do %> <%= react_component("CompetitionTabs", { tabs: @competition.tabs.as_json, - competition: @competition.as_json({ only: %w[name], methods: %w[has_rounds? has_schedule?]}), + competition: @competition.as_json({ methods: %w[city registration_open registration_close extra_registration_requirements has_fees? competition_events base_entry_fee using_payment_integrations? series_sibling_competitions main_event_id has_rounds? external_registration_page part_of_competition_series? contact use_wca_registration is_probably_over? registration_not_yet_opened? registration_past? competitor_limit competitor_limit_enabled venue_details external_website venue_address latitude_degrees longitude_degrees website has_schedule? country_iso2 events media number_of_bookmarks date_range information]}), wcifEvents: @competition.events_wcif, wcifSchedule: @competition.schedule_wcif, locale: I18n.locale, + userInfo: current_user.as_json({ + only: %w[wca_id unconfirmed_wca_id], + methods: [], + include: [], + }) }) %> <%= render "time_limit_cutoff_format_info", competition: @competition %> <% end %> From 119e063fee0db79f98f579cd187bddfc99fb285c Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Thu, 12 Dec 2024 11:25:38 +0100 Subject: [PATCH 06/49] move markdown.jsx to use fetch --- app/webpacker/components/Markdown.jsx | 3 ++- app/webpacker/lib/utils/markdown.js | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 app/webpacker/lib/utils/markdown.js diff --git a/app/webpacker/components/Markdown.jsx b/app/webpacker/components/Markdown.jsx index 0bc792d688..9543b44a77 100644 --- a/app/webpacker/components/Markdown.jsx +++ b/app/webpacker/components/Markdown.jsx @@ -2,6 +2,7 @@ import React from 'react'; import { useQuery } from '@tanstack/react-query'; import DOMPurify from 'dompurify'; import Loading from './Requests/Loading'; +import renderMarkdownFetch from '../lib/utils/markdown'; /* @md: The markdown text as plain text @@ -13,7 +14,7 @@ export default function Markdown({ md, id = crypto.randomUUID() }) { isLoading, } = useQuery({ queryKey: ['markdown', id], - queryFn: () => window.wca.renderMarkdownRequest(md), + queryFn: () => renderMarkdownFetch(md), staleTime: Infinity, }); diff --git a/app/webpacker/lib/utils/markdown.js b/app/webpacker/lib/utils/markdown.js new file mode 100644 index 0000000000..2d9665bcd5 --- /dev/null +++ b/app/webpacker/lib/utils/markdown.js @@ -0,0 +1,14 @@ +import { fetchWithAuthenticityToken } from '../requests/fetchWithAuthenticityToken'; + +export default async function renderMarkdownFetch(markdownContent) { + const request = await fetchWithAuthenticityToken('/render_markdown', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + markdown_content: markdownContent, + }), + }); + return request.text(); +} From ea94b2d1c310d9c2f23299d503d75ef990a7561a Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Thu, 12 Dec 2024 12:02:14 +0100 Subject: [PATCH 07/49] correctly display organizers and delegates --- app/views/competitions/show.html.erb | 2 +- .../CompetitionTabs/GeneralInfoTab.jsx | 81 +++++++++++++------ 2 files changed, 57 insertions(+), 26 deletions(-) diff --git a/app/views/competitions/show.html.erb b/app/views/competitions/show.html.erb index ed7f85591d..944ebf4a81 100644 --- a/app/views/competitions/show.html.erb +++ b/app/views/competitions/show.html.erb @@ -2,7 +2,7 @@ <%= render layout: 'nav' do %> <%= react_component("CompetitionTabs", { tabs: @competition.tabs.as_json, - competition: @competition.as_json({ methods: %w[city registration_open registration_close extra_registration_requirements has_fees? competition_events base_entry_fee using_payment_integrations? series_sibling_competitions main_event_id has_rounds? external_registration_page part_of_competition_series? contact use_wca_registration is_probably_over? registration_not_yet_opened? registration_past? competitor_limit competitor_limit_enabled venue_details external_website venue_address latitude_degrees longitude_degrees website has_schedule? country_iso2 events media number_of_bookmarks date_range information]}), + competition: @competition.as_json({ methods: %w[city organizers delegates registration_open registration_close extra_registration_requirements has_fees? competition_events base_entry_fee using_payment_integrations? series_sibling_competitions main_event_id has_rounds? external_registration_page part_of_competition_series? contact use_wca_registration is_probably_over? registration_not_yet_opened? registration_past? competitor_limit competitor_limit_enabled venue_details external_website venue_address latitude_degrees longitude_degrees website has_schedule? country_iso2 events media number_of_bookmarks date_range information]}), wcifEvents: @competition.events_wcif, wcifSchedule: @competition.schedule_wcif, locale: I18n.locale, userInfo: current_user.as_json({ only: %w[wca_id unconfirmed_wca_id], diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx index 5454fc841b..d7dff9e5aa 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx @@ -1,15 +1,17 @@ import React, { useState } from 'react'; import { Button, Icon } from 'semantic-ui-react'; +import { DateTime } from 'luxon'; import I18n from '../../lib/i18n'; import { countries } from '../../lib/wca-data.js.erb'; -import { competitionUrl } from '../../lib/requests/routes.js.erb'; +import { competitionUrl, personUrl } from '../../lib/requests/routes.js.erb'; import EventIcon from '../wca/EventIcon'; import Markdown from '../Markdown'; import RegistrationRequirements from './RegistrationRequirements'; +import I18nHTMLTranslate from '../I18nHTMLTranslate'; +import { getFullDateTimeString } from '../../lib/utils/dates'; const linkToGoogleMapsPlace = (latitude, longitude) => `https://www.google.com/maps/place/${latitude},${longitude}`; -const usersToSentence = () => ''; export default function GeneralInfoTab({ competition, @@ -26,7 +28,7 @@ export default function GeneralInfoTab({
{competition.date_range} 0 && ( <>
{I18n.t('competitions.competition_info.organizer_plural', { count: competition.organizers.length })}
-
{usersToSentence(competition.organizers, true)}
+
+ {competition.organizers.map((user, i) => (user.wca_id ? ( + + {user.name} + {i !== competition.organizers.length - 1 && ', '} + + ) : `${user.name} `))} +
)}
{I18n.t('competitions.competition_info.delegate', { count: competition.delegates.length })}
-
{usersToSentence(competition.delegates, true)}
+
+ {competition.delegates.map((user, i) => (user.wca_id ? ( + + {user.name} + {i !== competition.delegates.length - 1 && ', '} + + ) : `${user.name} `))} +
{competition['has_schedule?'] && (
- {I18n.t('competitions.competition_info.pdf.download_html', { - here: ( - - {I18n.t('common.here')} - - ), - })} + + ${I18n.t('common.here')} + ` + ), + } + } + />
)} @@ -208,19 +228,26 @@ export default function GeneralInfoTab({
{I18n.t('competitions.competition_info.registration_period.label')}

+ {/* eslint-disable-next-line no-nested-ternary */} {competition['registration_not_yet_opened?'] ? I18n.t('competitions.competition_info.registration_period.range_future_html', { - startDateAndTime: competition.registration_open, - endDateAndTime: competition.registration_close, + start_date_and_time: + getFullDateTimeString(DateTime.fromISO(competition.registration_open)), + end_date_and_time: + getFullDateTimeString(DateTime.fromISO(competition.registration_close)), }) : competition['registration_past?'] ? I18n.t('competitions.competition_info.registration_period.range_past_html', { - startDateAndTime: competition.registration_open, - endDateAndTime: competition.registration_close, + start_date_and_time: + getFullDateTimeString(DateTime.fromISO(competition.registration_open)), + end_date_and_time: + getFullDateTimeString(DateTime.fromISO(competition.registration_close)), }) : I18n.t('competitions.competition_info.registration_period.range_ongoing_html', { - startDateAndTime: competition.registration_open, - endDateAndTime: competition.registration_close, + start_date_and_time: + getFullDateTimeString(DateTime.fromISO(competition.registration_open)), + end_date_and_time: + getFullDateTimeString(DateTime.fromISO(competition.registration_close)), })}

@@ -234,7 +261,11 @@ export default function GeneralInfoTab({ {showRegistrationRequirements ? ( <>
- +
+ + ) : ( + - - ) : ( - - )} - - - + )} + + + - {competition.userCanViewResults && (competition.main_event_id || records) && ( + {competition.userCanViewResults && (competition.main_event_id || records) && (
{I18n.t('competitions.competition_info.highlights')}
@@ -303,8 +306,9 @@ export default function GeneralInfoTab({
- )} - - + )} + + + ); } From 39d63be54375deaca965a7f139de689c14dc3930 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Thu, 12 Dec 2024 21:42:42 +0100 Subject: [PATCH 12/49] use a grid to build generalinfo --- .../CompetitionTabs/GeneralInfoTab.jsx | 253 +++++++++++------- 1 file changed, 156 insertions(+), 97 deletions(-) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx index bac013eff5..01227ca56a 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { - Button, Grid, GridColumn, GridRow, Icon, + Button, Grid, GridColumn, GridRow, Header, Icon, } from 'semantic-ui-react'; import { DateTime } from 'luxon'; import I18n from '../../lib/i18n'; @@ -26,122 +26,181 @@ export default function GeneralInfoTab({ -
-
{I18n.t('competitions.competition_info.date')}
-
- {competition.date_range} - - - -
+ + + +
{I18n.t('competitions.competition_info.date')}
+
+ + {competition.date_range} + + + + +
-
{I18n.t('competitions.competition_info.city')}
-
- {competition.city} - {`, ${countries.byIso2[competition.country_iso2].name}`} -
+ + +
{I18n.t('competitions.competition_info.city')}
+
+ + {competition.city} + {`, ${countries.byIso2[competition.country_iso2].name}`} + +
-
{I18n.t('competitions.competition_info.venue')}
-
+ + +
{I18n.t('competitions.competition_info.venue')}
+
+ + + +
-
{I18n.t('competitions.competition_info.address')}
-
- - {competition.venue_address} - -
+ + +
+ {I18n.t('competitions.competition_info.address')} +
+
+ + + {competition.venue_address} + + +
{competition.venue_details && ( - <> -
{I18n.t('competitions.competition_info.details')}
-
{competition.venue_details}
- + + +
+ {I18n.t('competitions.competition_info.details')} +
+
+ {competition.venue_details} +
)} {competition.external_website && ( - <> -
{I18n.t('competitions.competition_info.website')}
-
- - {`${competition.name} website`} - -
- + + +
+ {I18n.t('competitions.competition_info.website')} +
+
+ + + {`${competition.name} website`} + + +
)} -
{I18n.t('competitions.competition_info.contact')}
-
- {competition.contact ? ( - - ) : ( - - {I18n.t('competitions.competition_info.organization_team')} - - )} -
+ + +
+ {I18n.t('competitions.competition_info.contact')} +
+
+ + {competition.contact ? ( + + ) : ( + + {I18n.t('competitions.competition_info.organization_team')} + + )} + +
{competition.organizers.length > 0 && ( - <> -
{I18n.t('competitions.competition_info.organizer_plural', { count: competition.organizers.length })}
-
- {competition.organizers.map((user, i) => (user.wca_id ? ( - - {user.name} - {i !== competition.organizers.length - 1 && ', '} - - ) : `${user.name} `))} -
- + + +
+ {I18n.t('competitions.competition_info.organizer_plural', { + count: competition.organizers.length, + })} +
+
+ + {competition.organizers.map((user, i) => ( + + {user.wca_id ? ( + {user.name} + ) : ( + user.name + )} + {i !== competition.organizers.length - 1 && ', '} + + ))} + +
)} -
{I18n.t('competitions.competition_info.delegate', { count: competition.delegates.length })}
-
- {competition.delegates.map((user, i) => (user.wca_id ? ( - - {user.name} - {i !== competition.delegates.length - 1 && ', '} - - ) : `${user.name} `))} -
-
- - {competition['has_schedule?'] && ( -
-
-
- + +
+ {I18n.t('competitions.competition_info.delegate', { + count: competition.delegates.length, + })} +
+
+ + {competition.delegates.map((user, i) => ( + + {user.wca_id ? ( + {user.name} + ) : ( + user.name + )} + {i !== competition.delegates.length - 1 && ', '} + + ))} + + + {competition['has_schedule?'] && ( + + +
+ +
+
+ + ${I18n.t('common.here')} ` - ), - } - } - /> -
-
- )} + ), + } + } + /> +
+ + )} +
From 3c2f74e035a8e303379c17d1fdc75404181cd403 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Fri, 13 Dec 2024 00:10:44 +0100 Subject: [PATCH 13/49] move the rest to grid --- .../CompetitionTabs/GeneralInfoTab.jsx | 255 ++++++++++-------- .../CompetitionTabs/TimeLimitCutoffInfo.jsx | 92 +++++++ 2 files changed, 228 insertions(+), 119 deletions(-) create mode 100644 app/webpacker/components/CompetitionTabs/TimeLimitCutoffInfo.jsx diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx index 01227ca56a..d2d67ee089 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx @@ -204,143 +204,160 @@ export default function GeneralInfoTab({ -
-
{I18n.t('competitions.competition_info.information')}
-
- -
-
+ + + +
{I18n.t('competitions.competition_info.information')}
+
+ + + +
-
-
{I18n.t('competitions.competition_info.events')}
-
- {competition.events.map((event) => ( - - - - ))} -
+ + +
{I18n.t('competitions.competition_info.events')}
+
+ + {competition.events.map((event) => ( + + + + ))} + +
{competition.main_event_id && ( - <> -
{I18n.t('competitions.competition_info.main_event')}
-
- -
- + + +
{I18n.t('competitions.competition_info.main_event')}
+
+ + + +
)} {competition['results_posted?'] && ( - <> -
{I18n.t('competitions.nav.menu.competitors')}
-
{competition.competitors.length}
- + + +
{I18n.t('competitions.nav.menu.competitors')}
+
+ {competition.competitors.length} +
)} -
- - {(competition.media.accepted ?? []).map((mediaType) => ( -
- -
- + +
+
+ +
- - ))} + ))} - {!competition['results_posted?'] && competition.competitor_limit_enabled && ( -
-
{I18n.t('competitions.competition_info.competitor_limit')}
-
{competition.competitor_limit}
-
- )} + {!competition['results_posted?'] && competition.competitor_limit_enabled && ( + + +
{I18n.t('competitions.competition_info.competitor_limit')}
+
+ + {competition.competitor_limit} + +
+ )} + + {!competition['results_posted?'] && ( + + +
{I18n.t('competitions.competition_info.number_of_bookmarks')}
+
+ + {competition.number_of_bookmarks} + +
+ )} +
- {!competition['results_posted?'] && ( -
-
{I18n.t('competitions.competition_info.number_of_bookmarks')}
-
{competition.number_of_bookmarks}
-
- )}
- {competition.registration_open && competition.registration_close && ( -
-
{I18n.t('competitions.competition_info.registration_period.label')}
-
-

- {/* eslint-disable-next-line no-nested-ternary */} - {competition['registration_not_yet_opened?'] - ? I18n.t('competitions.competition_info.registration_period.range_future_html', { - start_date_and_time: - getFullDateTimeString(DateTime.fromISO(competition.registration_open)), - end_date_and_time: - getFullDateTimeString(DateTime.fromISO(competition.registration_close)), - }) - : competition['registration_past?'] - ? I18n.t('competitions.competition_info.registration_period.range_past_html', { - start_date_and_time: - getFullDateTimeString(DateTime.fromISO(competition.registration_open)), - end_date_and_time: - getFullDateTimeString(DateTime.fromISO(competition.registration_close)), - }) - : I18n.t('competitions.competition_info.registration_period.range_ongoing_html', { - start_date_and_time: - getFullDateTimeString(DateTime.fromISO(competition.registration_open)), - end_date_and_time: - getFullDateTimeString(DateTime.fromISO(competition.registration_close)), - })} -

-
-
- )} + + {competition.registration_open && competition.registration_close && ( + + +
{I18n.t('competitions.competition_info.registration_period.label')}
+
+ +

+ {competition['registration_not_yet_opened?'] + ? I18n.t('competitions.competition_info.registration_period.range_future_html', { + start_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_open)), + end_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_close)), + }) + : competition['registration_past?'] + ? I18n.t('competitions.competition_info.registration_period.range_past_html', { + start_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_open)), + end_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_close)), + }) + : I18n.t('competitions.competition_info.registration_period.range_ongoing_html', { + start_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_open)), + end_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_close)), + })} +

+
+
+ )} -
-
{I18n.t('competitions.competition_info.registration_requirements')}
-
-
- {showRegistrationRequirements ? ( - <> -
- -
- + + ) : ( + - - ) : ( - - )} -
-
-
+ )} + + + +
{competition.userCanViewResults && (competition.main_event_id || records) && (
diff --git a/app/webpacker/components/CompetitionTabs/TimeLimitCutoffInfo.jsx b/app/webpacker/components/CompetitionTabs/TimeLimitCutoffInfo.jsx new file mode 100644 index 0000000000..7317d9085d --- /dev/null +++ b/app/webpacker/components/CompetitionTabs/TimeLimitCutoffInfo.jsx @@ -0,0 +1,92 @@ +import React from 'react'; + +import I18n from '../../lib/i18n'; +import I18nHTMLTranslate from '../I18nHTMLTranslate'; + +export default function TimeLimitCutoffInfo({ competition }) { + // const showCumulativeOneRound = competition['uses_cumulative?']; + // const showCumulativeAcrossRounds = competition['uses_cumulative_across_rounds?']; + // const showCutoff = competition['uses_cutoff?']; + // const showQualifications = competition['uses_qualification?']; + // + // return ( + //
+ //

+ // { I18n.t("competitions.events.time_limit") } + //

+ //

+ // ${I18n.t("competitions.events.time_limit_information.regulation_link_text", { number: "A1a4" })}')}`} + // } + // { showCumulativeOneRound && } + // <% if show_cumulative_one_round %> + //
+ // <% cumulative_one_round = content_tag :strong, + // t("competitions.events.time_limit_information.cumulative_time_limit"), id: "cumulative-time-limit" %> + // <%= t("competitions.events.time_limit_information.cumulative_one_round_html", + // cumulative_time_limit: cumulative_one_round, + // regulation_link: link_to(t("competitions.events.time_limit_information.regulation_link_text", number: "A1a2"), + // regulations_path + "#A1a2", target: "_blank")) %> + // <% end %> + // <% if show_cumulative_across_rounds %> + //
+ // < + // % cumulative_across_rounds = content_tag :strong, + // t("competitions.events.time_limit_information.cumulative_time_limit"), id: "cumulative-across-rounds-time-limit" + // %> + // <%= t("competitions.events.time_limit_information.cumulative_across_rounds_html", + // cumulative_time_limit: cumulative_across_rounds, + // guideline_link: link_to(t("competitions.events.time_limit_information.guideline_link_text", number: "A1a2++"), + // regulations_path + "/guidelines.html#A1a2++", target: "_blank")) %> + // <% end %> + //

+ // + // < + // % if show_cutoff %> + //

+ // < + // %= t("competitions.events.cutoff") %>

+ //

+ // < + // %= t("competitions.events.time_limit_information.cutoff_html", + // regulation_link: link_to(t("competitions.events.time_limit_information.regulation_link_text", number: "9g"), + // regulations_path + "#9g", target: "_blank")) %> + //

+ // < + // % end %> + //

+ // < + // %= t("competitions.events.format") %>

+ //

+ // < + // %= t("competitions.events.time_limit_information.format_html", + // link_to_9b: link_to(t("competitions.events.time_limit_information.regulation_link_text", number: "9b"), + // regulations_path + "#9b", target: "_blank"), + // link_to_9f: link_to(t("competitions.events.time_limit_information.regulation_link_text", number: "9f"), + // regulations_path + "#9f", target: "_blank")) %> + //

+ // + // < + // % if show_qualifications %> + //

+ // < + // %= t("competitions.events.qualification") %>

+ //

+ // < + // %= t("competitions.events.time_limit_information.qualification_html") %> + // <% date_to_events = competition.qualification_date_to_events %> + // <% if (date_to_events.length() > 1) %> + // <% date_to_events.each do |date, events| %> + // <%= t("competitions.events.time_limit_information.qualification_some_events_html", + // date: wca_date_range(date, date), events: events.map{|e| e.event.name}.join(", ")) %> + // <% end %> + // <% else %> + // <%= t("competitions.events.time_limit_information.qualification_all_events_html", + // date: wca_date_range(date_to_events.keys.first, date_to_events.keys.first)) %> + // <% end %> + //

+ // < + // % end %> + //
+ // ) +} From baa7a713e469779a3c2720e0a752edc57f625420 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Fri, 13 Dec 2024 12:27:51 +0100 Subject: [PATCH 14/49] implement setting active index by slug --- .../CompetitionTabs/GeneralInfoTab.jsx | 11 ++++-- .../components/CompetitionTabs/index.jsx | 38 +++++++++++++++++-- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx index d2d67ee089..928c7c8b89 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx @@ -345,13 +345,16 @@ export default function GeneralInfoTab({ showLinksToRegisterPage /> - + { competition['is_probably_over?'] + && ( + + )} ) : ( )} diff --git a/app/webpacker/components/CompetitionTabs/index.jsx b/app/webpacker/components/CompetitionTabs/index.jsx index e9048b93ca..cd5fe66f50 100644 --- a/app/webpacker/components/CompetitionTabs/index.jsx +++ b/app/webpacker/components/CompetitionTabs/index.jsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { Tab, TabPane } from 'semantic-ui-react'; import GeneralInfoTab from './GeneralInfoTab'; import CompetitionTab from './CompetitionTab'; @@ -7,13 +7,33 @@ import Schedule from './Schedule'; import WCAQueryClientProvider from '../../lib/providers/WCAQueryClientProvider'; import './style.css'; +const updatePath = (tabSlug) => { + window.history.replaceState({}, '', `${window.location.pathname}#${tabSlug}`); +}; + +const getSlugFromPath = () => { + if (window.location.hash) { + return window.location.hash.substring(1); + } + return null; +}; + +const tabIndexFromSlug = (panes) => { + const pathSlug = getSlugFromPath(); + if (!pathSlug) { + return 0; + } + return panes.findIndex((p) => p.slug === pathSlug); +}; + export default function Wrapper({ tabs, competition, wcifEvents, wcifSchedule, locale, userInfo, }) { const panes = useMemo(() => { - const p = [{ menuItem: 'General Info', render: () => }]; + const p = [{ slug: 'general-info', menuItem: 'General Info', render: () => }]; if (competition['has_rounds?']) { p.push({ + slug: 'competition-events', menuItem: 'Events', render: () => ( @@ -24,6 +44,7 @@ export default function Wrapper({ } if (competition['has_schedule?']) { p.push({ + slug: 'competition-schedule', menuItem: 'Schedule', render: () => ( @@ -37,12 +58,21 @@ export default function Wrapper({ ), }); } - tabs.map((tab) => p.push({ menuItem: tab.name, render: () => })); + tabs.map((tab) => p.push({ slug: `${tab.id}-${_.kebabCase(tab.name)}`, menuItem: tab.name, render: () => })); return p; }, [competition, locale, tabs, userInfo, wcifEvents, wcifSchedule]); + return ( - + { + const tab = panes[activeIndex]; + updatePath(tab.slug); + }} + /> ); } From 956bed7a4c6e4d911152df26816e0f5a49015b5a Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Fri, 13 Dec 2024 12:53:42 +0100 Subject: [PATCH 15/49] add TimeLimitCutoffInfo.jsx --- app/views/competitions/show.html.erb | 4 +- .../CompetitionTabs/TimeLimitCutoffInfo.jsx | 213 +++++++++++------- .../components/CompetitionTabs/index.jsx | 7 +- app/webpacker/lib/requests/routes.js.erb | 2 + 4 files changed, 138 insertions(+), 88 deletions(-) diff --git a/app/views/competitions/show.html.erb b/app/views/competitions/show.html.erb index 10315d01bb..9ae89d8ffb 100644 --- a/app/views/competitions/show.html.erb +++ b/app/views/competitions/show.html.erb @@ -8,7 +8,8 @@ external_website venue_address latitude_degrees longitude_degrees website has_schedule? country_iso2 events media number_of_bookmarks date_range information on_the_spot_registration? on_the_spot_entry_fee_lowest_denomination guests_per_registration_limit_enabled? guests_per_registration_limit uses_qualification? allow_registration_without_qualification - events_per_registration_limit_enabled? events_per_registration_limit guests_entry_fee_lowest_denomination all_guests_allowed?]} %> + events_per_registration_limit_enabled? events_per_registration_limit guests_entry_fee_lowest_denomination all_guests_allowed? + uses_cumulative? uses_cumulative_across_rounds? uses_cutoff? uses_qualification?]} %> <%= render layout: 'nav' do %> <%= react_component("CompetitionTabs", { tabs: @competition.tabs.as_json, @@ -20,5 +21,4 @@ include: [], }) }) %> - <%= render "time_limit_cutoff_format_info", competition: @competition %> <% end %> diff --git a/app/webpacker/components/CompetitionTabs/TimeLimitCutoffInfo.jsx b/app/webpacker/components/CompetitionTabs/TimeLimitCutoffInfo.jsx index 7317d9085d..81cf520208 100644 --- a/app/webpacker/components/CompetitionTabs/TimeLimitCutoffInfo.jsx +++ b/app/webpacker/components/CompetitionTabs/TimeLimitCutoffInfo.jsx @@ -1,92 +1,135 @@ import React from 'react'; +import { Header } from 'semantic-ui-react'; import I18n from '../../lib/i18n'; import I18nHTMLTranslate from '../I18nHTMLTranslate'; +import { regulationsUrl } from '../../lib/requests/routes.js.erb'; export default function TimeLimitCutoffInfo({ competition }) { - // const showCumulativeOneRound = competition['uses_cumulative?']; - // const showCumulativeAcrossRounds = competition['uses_cumulative_across_rounds?']; - // const showCutoff = competition['uses_cutoff?']; - // const showQualifications = competition['uses_qualification?']; - // - // return ( - //
- //

- // { I18n.t("competitions.events.time_limit") } - //

- //

- // ${I18n.t("competitions.events.time_limit_information.regulation_link_text", { number: "A1a4" })}')}`} - // } - // { showCumulativeOneRound && } - // <% if show_cumulative_one_round %> - //
- // <% cumulative_one_round = content_tag :strong, - // t("competitions.events.time_limit_information.cumulative_time_limit"), id: "cumulative-time-limit" %> - // <%= t("competitions.events.time_limit_information.cumulative_one_round_html", - // cumulative_time_limit: cumulative_one_round, - // regulation_link: link_to(t("competitions.events.time_limit_information.regulation_link_text", number: "A1a2"), - // regulations_path + "#A1a2", target: "_blank")) %> - // <% end %> - // <% if show_cumulative_across_rounds %> - //
- // < - // % cumulative_across_rounds = content_tag :strong, - // t("competitions.events.time_limit_information.cumulative_time_limit"), id: "cumulative-across-rounds-time-limit" - // %> - // <%= t("competitions.events.time_limit_information.cumulative_across_rounds_html", - // cumulative_time_limit: cumulative_across_rounds, - // guideline_link: link_to(t("competitions.events.time_limit_information.guideline_link_text", number: "A1a2++"), - // regulations_path + "/guidelines.html#A1a2++", target: "_blank")) %> - // <% end %> - //

- // - // < - // % if show_cutoff %> - //

- // < - // %= t("competitions.events.cutoff") %>

- //

- // < - // %= t("competitions.events.time_limit_information.cutoff_html", - // regulation_link: link_to(t("competitions.events.time_limit_information.regulation_link_text", number: "9g"), - // regulations_path + "#9g", target: "_blank")) %> - //

- // < - // % end %> - //

- // < - // %= t("competitions.events.format") %>

- //

- // < - // %= t("competitions.events.time_limit_information.format_html", - // link_to_9b: link_to(t("competitions.events.time_limit_information.regulation_link_text", number: "9b"), - // regulations_path + "#9b", target: "_blank"), - // link_to_9f: link_to(t("competitions.events.time_limit_information.regulation_link_text", number: "9f"), - // regulations_path + "#9f", target: "_blank")) %> - //

- // - // < - // % if show_qualifications %> - //

- // < - // %= t("competitions.events.qualification") %>

- //

- // < - // %= t("competitions.events.time_limit_information.qualification_html") %> - // <% date_to_events = competition.qualification_date_to_events %> - // <% if (date_to_events.length() > 1) %> - // <% date_to_events.each do |date, events| %> - // <%= t("competitions.events.time_limit_information.qualification_some_events_html", - // date: wca_date_range(date, date), events: events.map{|e| e.event.name}.join(", ")) %> - // <% end %> - // <% else %> - // <%= t("competitions.events.time_limit_information.qualification_all_events_html", - // date: wca_date_range(date_to_events.keys.first, date_to_events.keys.first)) %> - // <% end %> - //

- // < - // % end %> - //
- // ) + const showCumulativeOneRound = competition['uses_cumulative?']; + const showCumulativeAcrossRounds = competition['uses_cumulative_across_rounds?']; + const showCutoff = competition['uses_cutoff?']; + const showQualifications = competition['uses_qualification?']; + const dateToEvents = competition.qualification_date_to_events; + + return ( +
+
{I18n.t('competitions.events.time_limit')}
+

+ ${I18n.t( + 'competitions.events.time_limit_information.regulation_link_text', + { number: 'A1a4' }, + )}`, + }} + /> + + {showCumulativeOneRound && ( + <> +
+ ${I18n.t( + 'competitions.events.time_limit_information.cumulative_time_limit', + )}`, + regulation_link: `${I18n.t( + 'competitions.events.time_limit_information.regulation_link_text', + { number: 'A1a2' }, + )}`, + }} + /> + + )} + + {showCumulativeAcrossRounds && ( + <> +
+ ${I18n.t( + 'competitions.events.time_limit_information.cumulative_time_limit', + )}`, + guideline_link: `${I18n.t( + 'competitions.events.time_limit_information.guideline_link_text', + { number: 'A1a2++' }, + )}`, + }} + /> + + )} +

+ + {showCutoff && ( + <> +
{I18n.t('competitions.events.cutoff')}
+

+ ${I18n.t( + 'competitions.events.time_limit_information.regulation_link_text', + { number: '9g' }, + )}`, + }} + /> +

+ + )} + +
{I18n.t('competitions.events.format')}
+

+ ${I18n.t( + 'competitions.events.time_limit_information.regulation_link_text', + { number: '9b' }, + )}`, + link_to_9f: `${I18n.t( + 'competitions.events.time_limit_information.regulation_link_text', + { number: '9f' }, + )}`, + }} + /> +

+ + {showQualifications && ( + <> +
{I18n.t('competitions.events.qualification')}
+

+ {I18n.t('competitions.events.time_limit_information.qualification_html')} + {dateToEvents.length > 1 + ? Object.entries(dateToEvents).map(([date, events]) => ( + + e.event.name).join(', '), + }} + /> + + )) + : ( + + )} +

+ + )} +
+ ); } diff --git a/app/webpacker/components/CompetitionTabs/index.jsx b/app/webpacker/components/CompetitionTabs/index.jsx index cd5fe66f50..609ed662d6 100644 --- a/app/webpacker/components/CompetitionTabs/index.jsx +++ b/app/webpacker/components/CompetitionTabs/index.jsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import { Tab, TabPane } from 'semantic-ui-react'; import GeneralInfoTab from './GeneralInfoTab'; import CompetitionTab from './CompetitionTab'; @@ -6,6 +6,7 @@ import EventsTable from './EventsTable'; import Schedule from './Schedule'; import WCAQueryClientProvider from '../../lib/providers/WCAQueryClientProvider'; import './style.css'; +import TimeLimitCutoffInfo from './TimeLimitCutoffInfo'; const updatePath = (tabSlug) => { window.history.replaceState({}, '', `${window.location.pathname}#${tabSlug}`); @@ -38,6 +39,8 @@ export default function Wrapper({ render: () => ( +
+
), }); @@ -54,6 +57,8 @@ export default function Wrapper({ calendarLocale={locale} competitionName={competition.name} /> +
+
), }); diff --git a/app/webpacker/lib/requests/routes.js.erb b/app/webpacker/lib/requests/routes.js.erb index 4ddd5e1232..b897c2bb6f 100644 --- a/app/webpacker/lib/requests/routes.js.erb +++ b/app/webpacker/lib/requests/routes.js.erb @@ -283,3 +283,5 @@ 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 regulationsUrl = (regulationNumber) => `<%= CGI.unescape(Rails.application.routes.url_helpers.regulations_path) %>${regulationNumber}` From ab51c840ea2d99fd053d33edef447eb4f89c79ab Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Fri, 13 Dec 2024 13:26:25 +0100 Subject: [PATCH 16/49] add highlights --- app/models/competition.rb | 4 ++ app/views/competitions/show.html.erb | 6 +- .../CompetitionTabs/GeneralInfoTab.jsx | 60 ++++++++++--------- .../components/CompetitionTabs/index.jsx | 15 ++++- 4 files changed, 54 insertions(+), 31 deletions(-) diff --git a/app/models/competition.rb b/app/models/competition.rb index f3bd7462d1..3dc5321fb6 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -2212,6 +2212,10 @@ def serializable_hash(options = nil) ) end + def competitor_count + competitors.count + end + def to_ics cal = Icalendar::Calendar.new wcif_ids = rounds.to_h { |r| [r.wcif_id, r.to_string_map] } diff --git a/app/views/competitions/show.html.erb b/app/views/competitions/show.html.erb index 9ae89d8ffb..b179fc99b5 100644 --- a/app/views/competitions/show.html.erb +++ b/app/views/competitions/show.html.erb @@ -9,7 +9,7 @@ media number_of_bookmarks date_range information on_the_spot_registration? on_the_spot_entry_fee_lowest_denomination guests_per_registration_limit_enabled? guests_per_registration_limit uses_qualification? allow_registration_without_qualification events_per_registration_limit_enabled? events_per_registration_limit guests_entry_fee_lowest_denomination all_guests_allowed? - uses_cumulative? uses_cumulative_across_rounds? uses_cutoff? uses_qualification?]} %> + uses_cumulative? uses_cumulative_across_rounds? uses_cutoff? uses_qualification? results_posted? competitor_count]} %> <%= render layout: 'nav' do %> <%= react_component("CompetitionTabs", { tabs: @competition.tabs.as_json, @@ -19,6 +19,8 @@ only: %w[wca_id unconfirmed_wca_id], methods: [], include: [], - }) + }), + winners: winners(@competition, @competition.main_event), + records: records(@competition), }) %> <% end %> diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx index 928c7c8b89..9a42af1c53 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx @@ -18,9 +18,11 @@ const linkToGoogleMapsPlace = (latitude, longitude) => `https://www.google.com/m export default function GeneralInfoTab({ competition, userInfo, + records, + winners, }) { const [showRegistrationRequirements, setShowRegistrationRequirements] = useState(!competition['is_probably_over?']); - const [showHighlights, setShowHighlights] = useState(false); + const [showHighlights, setShowHighlights] = useState(true); return ( @@ -243,7 +245,7 @@ export default function GeneralInfoTab({
{I18n.t('competitions.nav.menu.competitors')}
- {competition.competitors.length} + {competition.competitor_count} )} {(competition.media.accepted ?? []).map((mediaType) => ( @@ -360,32 +362,36 @@ export default function GeneralInfoTab({ + {competition['results_posted?'] && (competition.main_event_id || records) && ( + + +
{I18n.t('competitions.competition_info.highlights')}
+
+ +
+ {showHighlights ? ( + <> + +
+ {competition.main_event_id && } +
+ {records && } +
+ + ) : ( + + )} +
+
+
+ )}
- - {competition.userCanViewResults && (competition.main_event_id || records) && ( -
-
{I18n.t('competitions.competition_info.highlights')}
-
-
- {showHighlights ? ( - <> - -
- {competition.main_event_id &&

{winners(competition, competition.mainEvent)}

} - {records &&

{records}

} -
- - ) : ( - - )} -
-
-
- )} diff --git a/app/webpacker/components/CompetitionTabs/index.jsx b/app/webpacker/components/CompetitionTabs/index.jsx index 609ed662d6..6a25edd465 100644 --- a/app/webpacker/components/CompetitionTabs/index.jsx +++ b/app/webpacker/components/CompetitionTabs/index.jsx @@ -28,10 +28,21 @@ const tabIndexFromSlug = (panes) => { }; export default function Wrapper({ - tabs, competition, wcifEvents, wcifSchedule, locale, userInfo, + tabs, competition, wcifEvents, wcifSchedule, locale, userInfo, records, winners, }) { const panes = useMemo(() => { - const p = [{ slug: 'general-info', menuItem: 'General Info', render: () => }]; + const p = [{ + slug: 'general-info', + menuItem: 'General Info', + render: () => ( + + ), + }]; if (competition['has_rounds?']) { p.push({ slug: 'competition-events', From eae6e3de3a3b904f72f7f537f7fcf82c762feed7 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 16 Dec 2024 11:44:16 +0100 Subject: [PATCH 17/49] implement media --- app/views/competitions/show.html.erb | 1 + .../CompetitionTabs/GeneralInfoTab.jsx | 92 +++++++++++-------- .../components/CompetitionTabs/index.jsx | 3 +- 3 files changed, 57 insertions(+), 39 deletions(-) diff --git a/app/views/competitions/show.html.erb b/app/views/competitions/show.html.erb index b179fc99b5..262af25c7c 100644 --- a/app/views/competitions/show.html.erb +++ b/app/views/competitions/show.html.erb @@ -22,5 +22,6 @@ }), winners: winners(@competition, @competition.main_event), records: records(@competition), + media: @competition.media.accepted }) %> <% end %> diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx index 9a42af1c53..45a0051535 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx @@ -1,7 +1,8 @@ import React, { useState } from 'react'; import { - Button, Grid, GridColumn, GridRow, Header, Icon, + Accordion, + Button, Grid, GridColumn, GridRow, Header, Icon, List, } from 'semantic-ui-react'; import { DateTime } from 'luxon'; import I18n from '../../lib/i18n'; @@ -20,9 +21,15 @@ export default function GeneralInfoTab({ userInfo, records, winners, + media = [], }) { const [showRegistrationRequirements, setShowRegistrationRequirements] = useState(!competition['is_probably_over?']); const [showHighlights, setShowHighlights] = useState(true); + const [mediaIndex, setMediaIndex] = useState(-1); + + const handleMediaClick = (index) => { + setMediaIndex(mediaIndex === index ? -1 : index); + }; return ( @@ -239,7 +246,6 @@ export default function GeneralInfoTab({ )} - {competition['results_posted?'] && ( @@ -248,36 +254,46 @@ export default function GeneralInfoTab({ {competition.competitor_count} )} - {(competition.media.accepted ?? []).map((mediaType) => ( -
- -
- -
-
- ))} + { media && ( + + + {['report', 'article', 'multimedia'].map((mediaType, i) => { + const mediaOfType = media.filter((m) => m.type === mediaType); + if (mediaOfType.length > 0) { + return ( + + handleMediaClick(i)}> + {`${_.capitalize(mediaType)} (${mediaOfType.length})`} + + + + {mediaOfType.map((item) => ( + + + {item.text} + + + ))} + + + + ); + } + })} + + + ) } {!competition['results_posted?'] && competition.competitor_limit_enabled && ( @@ -303,8 +319,8 @@ export default function GeneralInfoTab({
- +
{competition.registration_open && competition.registration_close && ( @@ -347,11 +363,11 @@ export default function GeneralInfoTab({ showLinksToRegisterPage /> - { competition['is_probably_over?'] + {competition['is_probably_over?'] && ( - + )} ) : ( @@ -375,7 +391,7 @@ export default function GeneralInfoTab({ {I18n.t('competitions.competition_info.hide_highlights')}
- {competition.main_event_id && } + {competition.main_event_id && }
{records && }
diff --git a/app/webpacker/components/CompetitionTabs/index.jsx b/app/webpacker/components/CompetitionTabs/index.jsx index 6a25edd465..9796808ce6 100644 --- a/app/webpacker/components/CompetitionTabs/index.jsx +++ b/app/webpacker/components/CompetitionTabs/index.jsx @@ -28,7 +28,7 @@ const tabIndexFromSlug = (panes) => { }; export default function Wrapper({ - tabs, competition, wcifEvents, wcifSchedule, locale, userInfo, records, winners, + tabs, competition, wcifEvents, wcifSchedule, locale, userInfo, records, winners, media, }) { const panes = useMemo(() => { const p = [{ @@ -40,6 +40,7 @@ export default function Wrapper({ userInfo={userInfo} records={records} winners={winners} + media={media} /> ), }]; From fb1d4b402ac9e5b0f1115c8c82d191ce2f9718d8 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 16 Dec 2024 15:20:31 +0100 Subject: [PATCH 18/49] add Winners table --- app/views/competitions/show.html.erb | 5 +- .../CompetitionTabs/GeneralInfoTab.jsx | 12 ++- .../CompetitionTabs/WinnerTable.jsx | 81 +++++++++++++++++++ 3 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 app/webpacker/components/CompetitionTabs/WinnerTable.jsx diff --git a/app/views/competitions/show.html.erb b/app/views/competitions/show.html.erb index 262af25c7c..3deab0e24c 100644 --- a/app/views/competitions/show.html.erb +++ b/app/views/competitions/show.html.erb @@ -9,12 +9,13 @@ media number_of_bookmarks date_range information on_the_spot_registration? on_the_spot_entry_fee_lowest_denomination guests_per_registration_limit_enabled? guests_per_registration_limit uses_qualification? allow_registration_without_qualification events_per_registration_limit_enabled? events_per_registration_limit guests_entry_fee_lowest_denomination all_guests_allowed? - uses_cumulative? uses_cumulative_across_rounds? uses_cutoff? uses_qualification? results_posted? competitor_count]} %> + uses_cumulative? uses_cumulative_across_rounds? uses_cutoff? uses_qualification? results_posted? competitor_count winning_results]} %> <%= render layout: 'nav' do %> <%= react_component("CompetitionTabs", { tabs: @competition.tabs.as_json, competition: @competition.as_json(options), - wcifEvents: @competition.events_wcif, wcifSchedule: @competition.schedule_wcif, locale: I18n.locale, + wcifEvents: @competition.events_wcif, + wcifSchedule: @competition.schedule_wcif, locale: I18n.locale, userInfo: current_user.as_json({ only: %w[wca_id unconfirmed_wca_id], methods: [], diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx index 45a0051535..30d8f3b736 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx @@ -2,9 +2,10 @@ import React, { useState } from 'react'; import { Accordion, - Button, Grid, GridColumn, GridRow, Header, Icon, List, + Button, Grid, GridColumn, GridRow, Header, Icon, List, Table, } from 'semantic-ui-react'; import { DateTime } from 'luxon'; +import _ from 'lodash'; import I18n from '../../lib/i18n'; import { countries } from '../../lib/wca-data.js.erb'; import { competitionUrl, personUrl } from '../../lib/requests/routes.js.erb'; @@ -13,6 +14,7 @@ import Markdown from '../Markdown'; import RegistrationRequirements from './Requirements'; import I18nHTMLTranslate from '../I18nHTMLTranslate'; import { getFullDateTimeString } from '../../lib/utils/dates'; +import WinnerTable from './WinnerTable'; const linkToGoogleMapsPlace = (latitude, longitude) => `https://www.google.com/maps/place/${latitude},${longitude}`; @@ -410,6 +412,14 @@ export default function GeneralInfoTab({
+ { competition['results_posted?'] + && ( + + + + + + )} ); } diff --git a/app/webpacker/components/CompetitionTabs/WinnerTable.jsx b/app/webpacker/components/CompetitionTabs/WinnerTable.jsx new file mode 100644 index 0000000000..3f8aeaabab --- /dev/null +++ b/app/webpacker/components/CompetitionTabs/WinnerTable.jsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { Segment, Table } from 'semantic-ui-react'; +import _ from 'lodash'; +import { countries, events } from '../../lib/wca-data.js.erb'; +import I18n from '../../lib/i18n'; +import { formatAttemptResult } from '../../lib/wca-live/attempts'; +import { competitionAllResultsUrl, personUrl } from '../../lib/requests/routes.js.erb'; +import EventIcon from '../wca/EventIcon'; + +export default function WinnerTable({ results, competition }) { + return ( + + + + + + {I18n.t('competitions.results_table.event')} + + + {I18n.t('competitions.results_table.name')} + + + {I18n.t('common.best')} + + + + {I18n.t('common.average')} + + + + {I18n.t('common.user.representing')} + + + {I18n.t('common.solves')} + + + + + + + + + {results.map((r) => { + const attempts = [r.value1, r.value2, r.value3, r.value4, r.value5]; + const bestResult = _.max(attempts); + const worstResult = _.min(attempts); + const bestResultIndex = attempts.findIndex((a) => a === bestResult); + const worstResultIndex = attempts.findIndex((a) => a === worstResult); + return ( + + + + {' '} + + {' '} + {events.byId[r.event.id].name} + + + + {r.personName} + + {formatAttemptResult(r.best, r.event.id)} + {r.regionalSingleRecord} + {formatAttemptResult(r.average, r.event.id)} + {r.regionalAverageRecord} + {countries.byIso2[r.country.iso2].name} + {attempts.map((a, i) => ( + + { r.format.expected_solve_count === 5 + && (i === bestResultIndex || i === worstResultIndex) + ? `(${formatAttemptResult(a, r.event.id)})` : formatAttemptResult(a, r.event.id)} + + ))} + + ); + })} + +
+
+ ); +} From 70b79997118a3932e4b016c03f813a05e19892b4 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 16 Dec 2024 17:28:55 +0100 Subject: [PATCH 19/49] refactor and add mobile view --- .../GeneralInfo/InformationGrid.jsx | 271 +++++++++++ .../GeneralInfo/OneColumnGridEntry.jsx | 26 ++ .../GeneralInfo/TwoColumnGridEntry.jsx | 27 ++ .../CompetitionTabs/GeneralInfo/index.jsx | 0 .../CompetitionTabs/GeneralInfoTab.jsx | 438 +++--------------- 5 files changed, 385 insertions(+), 377 deletions(-) create mode 100644 app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx create mode 100644 app/webpacker/components/CompetitionTabs/GeneralInfo/OneColumnGridEntry.jsx create mode 100644 app/webpacker/components/CompetitionTabs/GeneralInfo/TwoColumnGridEntry.jsx create mode 100644 app/webpacker/components/CompetitionTabs/GeneralInfo/index.jsx diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx new file mode 100644 index 0000000000..768320ec4b --- /dev/null +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx @@ -0,0 +1,271 @@ +import React, { useState } from 'react'; +import { + Accordion, Grid, GridColumn, Icon, List, +} from 'semantic-ui-react'; +import _ from 'lodash'; +import TwoColumnGridEntry from './TwoColumnGridEntry'; +import I18n from '../../../lib/i18n'; +import Markdown from '../../Markdown'; +import { countries } from '../../../lib/wca-data.js.erb'; +import I18nHTMLTranslate from '../../I18nHTMLTranslate'; +import EventIcon from '../../wca/EventIcon'; +import { competitionUrl, personUrl } from '../../../lib/requests/routes.js.erb'; + +const linkToGoogleMapsPlace = (latitude, longitude) => `https://www.google.com/maps/place/${latitude},${longitude}`; + +function LeftColumn({ competition }) { + return ( + + + {competition.date_range} + + + + + + + {competition.city} + {`, ${countries.byIso2[competition.country_iso2].name}`} + + + + + + + + + {competition.venue_address} + + + + {competition.venue_details && ( + + {competition.venue_details} + + )} + + {competition.external_website && ( + + + {`${competition.name} website`} + + + )} + + + + {competition.contact ? ( + + ) : ( + + {I18n.t('competitions.competition_info.organization_team')} + + )} + + + + {competition.organizers.length > 0 && ( + + {competition.organizers.map((user, i) => ( + + {user.wca_id ? ( + {user.name} + ) : ( + user.name + )} + {i !== competition.organizers.length - 1 && ', '} + + ))} + + )} + + + {competition.delegates.map((user, i) => ( + + {user.wca_id ? ( + {user.name} + ) : ( + user.name + )} + {i !== competition.delegates.length - 1 && ', '} + + ))} + + + {competition['has_schedule?'] && ( + + + + ${I18n.t('common.here')} + ` + ), + } + } + /> + + + )} + + ); +} + +function RightColumn({ competition, media }) { + const [mediaIndex, setMediaIndex] = useState(-1); + const handleMediaClick = (index) => { + setMediaIndex(mediaIndex === index ? -1 : index); + }; + return ( + + + + + + + {competition.events.map((event) => ( + + + {' '} + + ))} + + + {competition.main_event_id && ( + + + + )} + + {competition['results_posted?'] && ( + + {competition.competitor_count} + + )} + { media && ( + + + {['report', 'article', 'multimedia'].map((mediaType, i) => { + const mediaOfType = media.filter((m) => m.type === mediaType); + if (mediaOfType.length > 0) { + return ( + + handleMediaClick(i)}> + {`${_.capitalize(mediaType)} (${mediaOfType.length})`} + + + + {mediaOfType.map((item) => ( + + + {item.text} + + + ))} + + + + ); + } + })} + + + ) } + + {!competition['results_posted?'] && competition.competitor_limit_enabled && ( + + {competition.competitor_limit} + + )} + + {!competition['results_posted?'] && ( + + {competition.number_of_bookmarks} + + )} + + ); +} + +export default function InformationGrid({ competition, media = [] }) { + return ( + <> + + + + + + + + + + + + + + ); +} diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/OneColumnGridEntry.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/OneColumnGridEntry.jsx new file mode 100644 index 0000000000..6eb6afc766 --- /dev/null +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/OneColumnGridEntry.jsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Grid, Header } from 'semantic-ui-react'; + +export default function ColumnGridEntry({ + header, children, +}) { + return ( + <> + + +
{header}
+
+ + {children} + +
+ + + {header} +
+ {children} +
+
+ + ); +} diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/TwoColumnGridEntry.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/TwoColumnGridEntry.jsx new file mode 100644 index 0000000000..e11b4acc24 --- /dev/null +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/TwoColumnGridEntry.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Grid, Header, Icon } from 'semantic-ui-react'; + +export default function TwoColumnGridEntry({ + header, children, icon, +}) { + return ( + <> + + + { icon && } +
{header}
+
+ + {children} + +
+ + + {header} +
+ {children} +
+
+ + ); +} diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/index.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/index.jsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx index 30d8f3b736..f9b2b3f177 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx @@ -1,22 +1,16 @@ import React, { useState } from 'react'; import { - Accordion, - Button, Grid, GridColumn, GridRow, Header, Icon, List, Table, + Button, Grid, GridColumn, GridRow, Header, } from 'semantic-ui-react'; import { DateTime } from 'luxon'; -import _ from 'lodash'; import I18n from '../../lib/i18n'; -import { countries } from '../../lib/wca-data.js.erb'; -import { competitionUrl, personUrl } from '../../lib/requests/routes.js.erb'; -import EventIcon from '../wca/EventIcon'; import Markdown from '../Markdown'; import RegistrationRequirements from './Requirements'; -import I18nHTMLTranslate from '../I18nHTMLTranslate'; import { getFullDateTimeString } from '../../lib/utils/dates'; import WinnerTable from './WinnerTable'; - -const linkToGoogleMapsPlace = (latitude, longitude) => `https://www.google.com/maps/place/${latitude},${longitude}`; +import InformationGrid from './GeneralInfo/InformationGrid'; +import OneColumnGridEntry from './GeneralInfo/OneColumnGridEntry'; export default function GeneralInfoTab({ competition, @@ -27,387 +21,77 @@ export default function GeneralInfoTab({ }) { const [showRegistrationRequirements, setShowRegistrationRequirements] = useState(!competition['is_probably_over?']); const [showHighlights, setShowHighlights] = useState(true); - const [mediaIndex, setMediaIndex] = useState(-1); - - const handleMediaClick = (index) => { - setMediaIndex(mediaIndex === index ? -1 : index); - }; return ( - + + + + - - - - -
{I18n.t('competitions.competition_info.date')}
-
- - {competition.date_range} - - - - -
- - - -
{I18n.t('competitions.competition_info.city')}
-
- - {competition.city} - {`, ${countries.byIso2[competition.country_iso2].name}`} - -
- - - -
{I18n.t('competitions.competition_info.venue')}
-
- - - -
- - - -
- {I18n.t('competitions.competition_info.address')} -
-
- - - {competition.venue_address} - - -
- - {competition.venue_details && ( - - -
- {I18n.t('competitions.competition_info.details')} -
-
- {competition.venue_details} -
- )} - - {competition.external_website && ( - - -
- {I18n.t('competitions.competition_info.website')} -
-
- - - {`${competition.name} website`} - - -
- )} - - - -
- {I18n.t('competitions.competition_info.contact')} -
-
- - {competition.contact ? ( - - ) : ( - - {I18n.t('competitions.competition_info.organization_team')} - - )} - -
- - {competition.organizers.length > 0 && ( - - -
- {I18n.t('competitions.competition_info.organizer_plural', { - count: competition.organizers.length, - })} -
-
- - {competition.organizers.map((user, i) => ( - - {user.wca_id ? ( - {user.name} - ) : ( - user.name - )} - {i !== competition.organizers.length - 1 && ', '} - - ))} - -
- )} - - - -
- {I18n.t('competitions.competition_info.delegate', { - count: competition.delegates.length, - })} -
-
- - {competition.delegates.map((user, i) => ( - - {user.wca_id ? ( - {user.name} - ) : ( - user.name - )} - {i !== competition.delegates.length - 1 && ', '} - - ))} - -
- {competition['has_schedule?'] && ( - - -
- -
-
- - - ${I18n.t('common.here')} - ` - ), - } - } - /> - -
- )} -
-
- - - - - -
{I18n.t('competitions.competition_info.information')}
-
- - - -
- - - -
{I18n.t('competitions.competition_info.events')}
-
- - {competition.events.map((event) => ( - - - - ))} - -
- - {competition.main_event_id && ( - - -
{I18n.t('competitions.competition_info.main_event')}
-
- - - -
- )} - {competition['results_posted?'] && ( - - -
{I18n.t('competitions.nav.menu.competitors')}
-
- {competition.competitor_count} -
- )} - { media && ( - - - {['report', 'article', 'multimedia'].map((mediaType, i) => { - const mediaOfType = media.filter((m) => m.type === mediaType); - if (mediaOfType.length > 0) { - return ( - - handleMediaClick(i)}> - {`${_.capitalize(mediaType)} (${mediaOfType.length})`} - - - - {mediaOfType.map((item) => ( - - - {item.text} - - - ))} - - - - ); - } - })} - - - ) } - - {!competition['results_posted?'] && competition.competitor_limit_enabled && ( - - -
{I18n.t('competitions.competition_info.competitor_limit')}
-
- - {competition.competitor_limit} - -
- )} - - {!competition['results_posted?'] && ( - - -
{I18n.t('competitions.competition_info.number_of_bookmarks')}
-
- - {competition.number_of_bookmarks} - -
- )} -
- -
-
- + {competition.registration_open && competition.registration_close && ( - - -
{I18n.t('competitions.competition_info.registration_period.label')}
-
- -

- {competition['registration_not_yet_opened?'] - ? I18n.t('competitions.competition_info.registration_period.range_future_html', { - start_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_open)), - end_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_close)), - }) - : competition['registration_past?'] - ? I18n.t('competitions.competition_info.registration_period.range_past_html', { - start_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_open)), - end_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_close)), - }) - : I18n.t('competitions.competition_info.registration_period.range_ongoing_html', { - start_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_open)), - end_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_close)), - })} -

-
-
+ + {competition['registration_not_yet_opened?'] + ? I18n.t('competitions.competition_info.registration_period.range_future_html', { + start_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_open)), + end_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_close)), + }) + : competition['registration_past?'] + ? I18n.t('competitions.competition_info.registration_period.range_past_html', { + start_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_open)), + end_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_close)), + }) + : I18n.t('competitions.competition_info.registration_period.range_ongoing_html', { + start_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_open)), + end_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_close)), + })} + )} - - - -
{I18n.t('competitions.competition_info.registration_requirements')}
-
- -
- {showRegistrationRequirements ? ( + + {showRegistrationRequirements ? ( + <> +
+ +
+ {competition['is_probably_over?'] + && ( + + )} + + ) : ( + + )} +
+ {competition['results_posted?'] && (competition.main_event_id || records) && ( + + {showHighlights ? ( <> +
- + {competition.main_event_id && } +
+ {records && }
- {competition['is_probably_over?'] - && ( - - )} ) : ( - )} -
-
-
- {competition['results_posted?'] && (competition.main_event_id || records) && ( - - -
{I18n.t('competitions.competition_info.highlights')}
-
- -
- {showHighlights ? ( - <> - -
- {competition.main_event_id && } -
- {records && } -
- - ) : ( - - )} -
-
-
+ )}
From 0b104a50dbff2ff0abbc2da907b519dd6e4cba7f Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 17 Dec 2024 10:52:20 +0100 Subject: [PATCH 20/49] don't render media accordion if no media was supplied --- .../CompetitionTabs/GeneralInfo/InformationGrid.jsx | 12 ++++++------ .../GeneralInfo/TwoColumnGridEntry.jsx | 7 ++++--- .../components/CompetitionTabs/GeneralInfo/index.jsx | 0 app/webpacker/components/CompetitionTabs/index.jsx | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) delete mode 100644 app/webpacker/components/CompetitionTabs/GeneralInfo/index.jsx diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx index 768320ec4b..a71d394479 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx @@ -175,7 +175,7 @@ function RightColumn({ competition, media }) { - + {competition.events.map((event) => ( @@ -185,17 +185,17 @@ function RightColumn({ competition, media }) { {competition.main_event_id && ( - + )} {competition['results_posted?'] && ( - + {competition.competitor_count} )} - { media && ( + { media.length > 0 && ( + {competition.competitor_limit} )} {!competition['results_posted?'] && ( - + {competition.number_of_bookmarks} )} diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/TwoColumnGridEntry.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/TwoColumnGridEntry.jsx index e11b4acc24..5e34f302c3 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/TwoColumnGridEntry.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/TwoColumnGridEntry.jsx @@ -2,11 +2,12 @@ import React from 'react'; import { Grid, Header, Icon } from 'semantic-ui-react'; export default function TwoColumnGridEntry({ - header, children, icon, + header, children, icon, padded = false, }) { + const style = padded ? {} : { padding: '0em' }; return ( <> - + { icon && }
{header}
@@ -15,7 +16,7 @@ export default function TwoColumnGridEntry({ {children}
- + {header}
diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/index.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/index.jsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/app/webpacker/components/CompetitionTabs/index.jsx b/app/webpacker/components/CompetitionTabs/index.jsx index 9796808ce6..f0934ac00d 100644 --- a/app/webpacker/components/CompetitionTabs/index.jsx +++ b/app/webpacker/components/CompetitionTabs/index.jsx @@ -77,7 +77,7 @@ export default function Wrapper({ } tabs.map((tab) => p.push({ slug: `${tab.id}-${_.kebabCase(tab.name)}`, menuItem: tab.name, render: () => })); return p; - }, [competition, locale, tabs, userInfo, wcifEvents, wcifSchedule]); + }, [competition, locale, media, records, tabs, userInfo, wcifEvents, wcifSchedule, winners]); return ( From 6a8ba43cb19235fda3d532ab64c8b119554c2f77 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 17 Dec 2024 11:31:45 +0100 Subject: [PATCH 21/49] make Tab Title orange --- .../GeneralInfo/InformationGrid.jsx | 16 +++++----------- .../GeneralInfo/TwoColumnGridEntry.jsx | 4 ++-- .../CompetitionTabs/GeneralInfoTab.jsx | 2 +- .../components/CompetitionTabs/index.jsx | 7 ++++--- .../components/CompetitionTabs/style.css | 4 ++++ 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx index a71d394479..b478722c05 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx @@ -136,14 +136,9 @@ function LeftColumn({ competition }) { - - - + /> )}
@@ -251,7 +245,7 @@ function RightColumn({ competition, media }) { ); } -export default function InformationGrid({ competition, media = [] }) { +export default function InformationGrid({ competition, media }) { return ( <> diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/TwoColumnGridEntry.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/TwoColumnGridEntry.jsx index 5e34f302c3..f6f8e7104e 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/TwoColumnGridEntry.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/TwoColumnGridEntry.jsx @@ -9,8 +9,8 @@ export default function TwoColumnGridEntry({ <> - { icon && } -
{header}
+ { icon ? + :
{header}
}
{children} diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx index f9b2b3f177..32752370e4 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { - Button, Grid, GridColumn, GridRow, Header, + Button, Grid, GridColumn, GridRow, } from 'semantic-ui-react'; import { DateTime } from 'luxon'; import I18n from '../../lib/i18n'; diff --git a/app/webpacker/components/CompetitionTabs/index.jsx b/app/webpacker/components/CompetitionTabs/index.jsx index f0934ac00d..94272a3db1 100644 --- a/app/webpacker/components/CompetitionTabs/index.jsx +++ b/app/webpacker/components/CompetitionTabs/index.jsx @@ -7,6 +7,7 @@ import Schedule from './Schedule'; import WCAQueryClientProvider from '../../lib/providers/WCAQueryClientProvider'; import './style.css'; import TimeLimitCutoffInfo from './TimeLimitCutoffInfo'; +import I18n from '../../lib/i18n'; const updatePath = (tabSlug) => { window.history.replaceState({}, '', `${window.location.pathname}#${tabSlug}`); @@ -33,7 +34,7 @@ export default function Wrapper({ const panes = useMemo(() => { const p = [{ slug: 'general-info', - menuItem: 'General Info', + menuItem: I18n.t('competitions.show.general_info'), render: () => ( ( @@ -60,7 +61,7 @@ export default function Wrapper({ if (competition['has_schedule?']) { p.push({ slug: 'competition-schedule', - menuItem: 'Schedule', + menuItem: I18n.t('competitions.show.schedule'), render: () => ( Date: Tue, 17 Dec 2024 11:46:35 +0100 Subject: [PATCH 22/49] remove all
s by using List --- .../CompetitionSeriesRequirement.jsx | 13 +- .../Requirements/GuestRequirements.jsx | 2 - .../RegistrationFeeRequirements.jsx | 4 +- .../CompetitionTabs/Requirements/index.jsx | 130 ++++++++++-------- 4 files changed, 81 insertions(+), 68 deletions(-) diff --git a/app/webpacker/components/CompetitionTabs/Requirements/CompetitionSeriesRequirement.jsx b/app/webpacker/components/CompetitionTabs/Requirements/CompetitionSeriesRequirement.jsx index abb8a79abd..09b816a5d2 100644 --- a/app/webpacker/components/CompetitionTabs/Requirements/CompetitionSeriesRequirement.jsx +++ b/app/webpacker/components/CompetitionTabs/Requirements/CompetitionSeriesRequirement.jsx @@ -1,22 +1,23 @@ import React from 'react'; +import { List } from 'semantic-ui-react'; import I18n from '../../../lib/i18n'; export default function CompetitionSeriesRequirement({ competition }) { return ( -
+ <>

{I18n.t('competitions.competition_info.part_of_a_series_list', { name: competition.competition_series.name, })}

-
    + {competition.series_sibling_competitions.map((comp) => ( -
  • + {comp.name} -
  • + ))} -
+

{I18n.t('competitions.competition_info.series_registration_warning_html')}

-
+ ); } diff --git a/app/webpacker/components/CompetitionTabs/Requirements/GuestRequirements.jsx b/app/webpacker/components/CompetitionTabs/Requirements/GuestRequirements.jsx index fc409240f6..0b0acb41ac 100644 --- a/app/webpacker/components/CompetitionTabs/Requirements/GuestRequirements.jsx +++ b/app/webpacker/components/CompetitionTabs/Requirements/GuestRequirements.jsx @@ -13,7 +13,6 @@ export default function GuestRequirements({ competition }) { competition.currency_code, ), })} -
); } @@ -25,7 +24,6 @@ export default function GuestRequirements({ competition }) { ) : competition['some_guests_allowed?'] && ( I18n.t('competitions.competition_info.guests_free.restricted') )} -
); } diff --git a/app/webpacker/components/CompetitionTabs/Requirements/RegistrationFeeRequirements.jsx b/app/webpacker/components/CompetitionTabs/Requirements/RegistrationFeeRequirements.jsx index 823471c383..a0154e3a9f 100644 --- a/app/webpacker/components/CompetitionTabs/Requirements/RegistrationFeeRequirements.jsx +++ b/app/webpacker/components/CompetitionTabs/Requirements/RegistrationFeeRequirements.jsx @@ -4,7 +4,7 @@ import { isoMoneyToHumanReadable } from '../../../lib/helpers/money'; export default function RegistrationFeeRequirements({ competition }) { return ( -
+ <>

{competition.base_entry_fee_lowest_denomination ? I18n.t('competitions.competition_info.entry_fee_is', { @@ -22,6 +22,6 @@ export default function RegistrationFeeRequirements({ competition }) {

{isoMoneyToHumanReadable(event.fee)}
) : null))} - + ); } diff --git a/app/webpacker/components/CompetitionTabs/Requirements/index.jsx b/app/webpacker/components/CompetitionTabs/Requirements/index.jsx index 84e8124bd9..a3aee772c6 100644 --- a/app/webpacker/components/CompetitionTabs/Requirements/index.jsx +++ b/app/webpacker/components/CompetitionTabs/Requirements/index.jsx @@ -1,5 +1,6 @@ import React from 'react'; import { DateTime } from 'luxon'; +import { List } from 'semantic-ui-react'; import I18n from '../../../lib/i18n'; import I18nHTMLTranslate from '../../I18nHTMLTranslate'; import Markdown from '../../Markdown'; @@ -11,11 +12,14 @@ import EventChangeDeadlineRequirements from './EventChangeDeadlineRequirements'; import OnTheSpotRegistrationRequirements from './OnTheSpotRegistrationRequirements'; import GuestRequirements from './GuestRequirements'; -export default function RegistrationRequirements({ competition, userInfo, showLinksToRegisterPage = false }) { +export default function RegistrationRequirements({ + competition, + userInfo, showLinksToRegisterPage = false, +}) { return ( -
+ {competition.use_wca_registration && ( -
+ {showLinksToRegisterPage ? ( {I18n.t('competitions.competition_info.register_below_html')}

)} -
+ )} {competition.external_registration_page && ( -

${I18n.t('common.here')}`, - }), - }} - /> + + ${I18n.t('common.here')}` }} + /> + )} {competition['part_of_competition_series?'] && ( - + + + )} - {I18n.t( - competition.competitor_limit_enabled - ? 'competitions.competition_info.competitor_limit_is' - : 'competitions.competition_info.no_competitor_limit', - { competitor_limit: competition.competitor_limit }, - )} + + {I18n.t( + competition.competitor_limit_enabled + ? 'competitions.competition_info.competitor_limit_is' + : 'competitions.competition_info.no_competitor_limit', + { competitor_limit: competition.competitor_limit }, + )} + - {competition['has_fees?'] && } + + {competition['has_fees?'] && } + {competition['using_payment_integrations?'] && ( - ${I18n.t( - 'common.here', - )}`, - }} - /> + + ${I18n.t( + 'common.here', + )}`, + }} + /> + )} -
- {competition.refund_policy_percent || !competition['has_fees?'] - ? I18n.t('competitions.competition_info.refund_policy_html', { - refund_policy_percent: `${competition.refund_policy_percent}%`, - limit_date_and_time: + + + {competition.refund_policy_percent || !competition['has_fees?'] + ? I18n.t('competitions.competition_info.refund_policy_html', { + refund_policy_percent: `${competition.refund_policy_percent}%`, + limit_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.refund_policy_limit_date)), - }) - : I18n.t('competitions.competition_info.no_refunds')} -
+ }) + : I18n.t('competitions.competition_info.no_refunds')} +
+ {competition.waiting_list_deadline_date && ( - <> + {I18n.t( 'competitions.competition_info.waiting_list_deadline_html', { @@ -84,45 +97,46 @@ export default function RegistrationRequirements({ competition, userInfo, showLi getFullDateTimeString(DateTime.fromISO(competition.waiting_list_deadline_date)), }, )} -
- +
)} {competition.competition_events.length > 1 && competition['has_event_change_deadline_date?'] && ( - + + + )} -
- {competition['on_the_spot_registration?'] ? ( - ) - : I18n.t('competitions.competition_info.no_on_the_spot_registration')} -
- + + {competition['on_the_spot_registration?'] ? ( + ) + : I18n.t('competitions.competition_info.no_on_the_spot_registration')} + + + + + {competition['guests_per_registration_limit_enabled?'] && ( - <> + {I18n.t('competitions.competition_info.guest_limit', { count: competition.guests_per_registration_limit })} -
- +
)} {competition['uses_qualification?'] && !competition.allow_registration_without_qualification && ( - <> + {I18n.t('competitions.competition_info.require_qualification')} -
- +
)} {competition['events_per_registration_limit_enabled?'] && ( - <> + {I18n.t('competitions.competition_info.event_limit', { count: competition.events_per_registration_limit })} ) -
- +
)} -
+ {competition.extra_registration_requirements && ( )} -

+ ); } From 274a5f6d69ca5e89324ac4744c94e3175eb734a0 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 17 Dec 2024 11:59:54 +0100 Subject: [PATCH 23/49] make events hoverable --- .../CompetitionTabs/GeneralInfo/InformationGrid.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx index b478722c05..1ea4427b27 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx @@ -1,12 +1,12 @@ import React, { useState } from 'react'; import { - Accordion, Grid, GridColumn, Icon, List, + Accordion, Grid, GridColumn, Icon, List, Popup, } from 'semantic-ui-react'; import _ from 'lodash'; import TwoColumnGridEntry from './TwoColumnGridEntry'; import I18n from '../../../lib/i18n'; import Markdown from '../../Markdown'; -import { countries } from '../../../lib/wca-data.js.erb'; +import { countries, events } from '../../../lib/wca-data.js.erb'; import I18nHTMLTranslate from '../../I18nHTMLTranslate'; import EventIcon from '../../wca/EventIcon'; import { competitionUrl, personUrl } from '../../../lib/requests/routes.js.erb'; @@ -172,7 +172,7 @@ function RightColumn({ competition, media }) { {competition.events.map((event) => ( - + } content={events.byId[event.id].name} /> {' '} ))} @@ -180,7 +180,7 @@ function RightColumn({ competition, media }) { {competition.main_event_id && ( - + } content={events.byId[competition.main_event_id].name} /> )} From 2143b9b3f7b0e2c5366b8dd332a278dd376952aa Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 17 Dec 2024 12:13:07 +0100 Subject: [PATCH 24/49] fix listing of Sibling Competitions --- app/views/competitions/show.html.erb | 3 +- .../CompetitionSeriesRequirement.jsx | 19 ++++++------ .../RegistrationFeeRequirements.jsx | 30 ++++++------------- 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/app/views/competitions/show.html.erb b/app/views/competitions/show.html.erb index 3deab0e24c..93d07212e7 100644 --- a/app/views/competitions/show.html.erb +++ b/app/views/competitions/show.html.erb @@ -9,7 +9,8 @@ media number_of_bookmarks date_range information on_the_spot_registration? on_the_spot_entry_fee_lowest_denomination guests_per_registration_limit_enabled? guests_per_registration_limit uses_qualification? allow_registration_without_qualification events_per_registration_limit_enabled? events_per_registration_limit guests_entry_fee_lowest_denomination all_guests_allowed? - uses_cumulative? uses_cumulative_across_rounds? uses_cutoff? uses_qualification? results_posted? competitor_count winning_results]} %> + uses_cumulative? uses_cumulative_across_rounds? uses_cutoff? uses_qualification? results_posted? competitor_count winning_results + competition_series]} %> <%= render layout: 'nav' do %> <%= react_component("CompetitionTabs", { tabs: @competition.tabs.as_json, diff --git a/app/webpacker/components/CompetitionTabs/Requirements/CompetitionSeriesRequirement.jsx b/app/webpacker/components/CompetitionTabs/Requirements/CompetitionSeriesRequirement.jsx index 09b816a5d2..140ca812f7 100644 --- a/app/webpacker/components/CompetitionTabs/Requirements/CompetitionSeriesRequirement.jsx +++ b/app/webpacker/components/CompetitionTabs/Requirements/CompetitionSeriesRequirement.jsx @@ -1,23 +1,24 @@ import React from 'react'; import { List } from 'semantic-ui-react'; import I18n from '../../../lib/i18n'; +import { competitionUrl } from '../../../lib/requests/routes.js.erb'; +import I18nHTMLTranslate from '../../I18nHTMLTranslate'; export default function CompetitionSeriesRequirement({ competition }) { return ( <> -

- {I18n.t('competitions.competition_info.part_of_a_series_list', { - name: competition.competition_series.name, - })} -

- + {I18n.t('competitions.competition_info.part_of_a_series_list', { + name: competition.competition_series.name, + })} + {competition.series_sibling_competitions.map((comp) => ( - - {comp.name} + + {comp.name} ))} -

{I18n.t('competitions.competition_info.series_registration_warning_html')}

+
+ ); } diff --git a/app/webpacker/components/CompetitionTabs/Requirements/RegistrationFeeRequirements.jsx b/app/webpacker/components/CompetitionTabs/Requirements/RegistrationFeeRequirements.jsx index a0154e3a9f..65e81b6eeb 100644 --- a/app/webpacker/components/CompetitionTabs/Requirements/RegistrationFeeRequirements.jsx +++ b/app/webpacker/components/CompetitionTabs/Requirements/RegistrationFeeRequirements.jsx @@ -3,25 +3,13 @@ import I18n from '../../../lib/i18n'; import { isoMoneyToHumanReadable } from '../../../lib/helpers/money'; export default function RegistrationFeeRequirements({ competition }) { - return ( - <> -

- {competition.base_entry_fee_lowest_denomination - ? I18n.t('competitions.competition_info.entry_fee_is', { - base_entry_fee: - isoMoneyToHumanReadable( - competition.base_entry_fee_lowest_denomination, - competition.currency_code, - ), - }) - : I18n.t('competitions.competition_info.no_entry_fee')} -

- {competition.competition_events.map((event) => (event['has_fee?'] ? ( -
-
{event.event.name}
-
{isoMoneyToHumanReadable(event.fee)}
-
- ) : null))} - - ); + return competition.base_entry_fee_lowest_denomination + ? I18n.t('competitions.competition_info.entry_fee_is', { + base_entry_fee: + isoMoneyToHumanReadable( + competition.base_entry_fee_lowest_denomination, + competition.currency_code, + ), + }) + : I18n.t('competitions.competition_info.no_entry_fee'); } From a8b0740afbd0ec692eb4c9ca7e2cea3d883e82b0 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 17 Dec 2024 12:22:19 +0100 Subject: [PATCH 25/49] sort organizer and delegated correctly --- .../GeneralInfo/InformationGrid.jsx | 70 +++++++++---------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx index 1ea4427b27..ae80feb1e5 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx @@ -9,7 +9,7 @@ import Markdown from '../../Markdown'; import { countries, events } from '../../../lib/wca-data.js.erb'; import I18nHTMLTranslate from '../../I18nHTMLTranslate'; import EventIcon from '../../wca/EventIcon'; -import { competitionUrl, personUrl } from '../../../lib/requests/routes.js.erb'; +import { competitionUrl, contactCompetitionUrl, personUrl } from '../../../lib/requests/routes.js.erb'; const linkToGoogleMapsPlace = (latitude, longitude) => `https://www.google.com/maps/place/${latitude},${longitude}`; @@ -79,21 +79,15 @@ function LeftColumn({ competition }) { - - {competition.contact ? ( - - ) : ( - - {I18n.t('competitions.competition_info.organization_team')} - - )} - + {competition.contact ? ( + + ) : ( + + {I18n.t('competitions.competition_info.organization_team')} + + )} {competition.organizers.length > 0 && ( @@ -102,16 +96,18 @@ function LeftColumn({ competition }) { count: competition.organizers.length, })} > - {competition.organizers.map((user, i) => ( - - {user.wca_id ? ( - {user.name} - ) : ( - user.name - )} - {i !== competition.organizers.length - 1 && ', '} - - ))} + {competition.organizers + .toSorted((o1, o2) => o1.name.localeCompare(o2.name)) + .map((user, i) => ( + + {user.wca_id ? ( + {user.name} + ) : ( + user.name + )} + {i !== competition.organizers.length - 1 && ', '} + + ))}
)} @@ -120,16 +116,18 @@ function LeftColumn({ competition }) { count: competition.delegates.length, })} > - {competition.delegates.map((user, i) => ( - - {user.wca_id ? ( - {user.name} - ) : ( - user.name - )} - {i !== competition.delegates.length - 1 && ', '} - - ))} + {competition.delegates + .toSorted((d1, d2) => d1.name.localeCompare(d2.name)) + .map((user, i) => ( + + {user.wca_id ? ( + {user.name} + ) : ( + user.name + )} + {i !== competition.delegates.length - 1 && ', '} + + ))} {competition['has_schedule?'] && ( From 2fdac4c44eadd5655a4f60d36f770f3f325bddce Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Tue, 17 Dec 2024 13:49:01 +0100 Subject: [PATCH 26/49] remove another br --- .../{ => GeneralInfo}/GeneralInfoTab.jsx | 27 +++++++++---------- .../CompetitionTabs/{style.css => style.scss} | 7 +++-- 2 files changed, 16 insertions(+), 18 deletions(-) rename app/webpacker/components/CompetitionTabs/{ => GeneralInfo}/GeneralInfoTab.jsx (85%) rename app/webpacker/components/CompetitionTabs/{style.css => style.scss} (55%) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/GeneralInfoTab.jsx similarity index 85% rename from app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx rename to app/webpacker/components/CompetitionTabs/GeneralInfo/GeneralInfoTab.jsx index 32752370e4..b3c3e987bf 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfoTab.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/GeneralInfoTab.jsx @@ -1,16 +1,16 @@ import React, { useState } from 'react'; import { - Button, Grid, GridColumn, GridRow, + Button, Grid, GridColumn, GridRow, List, } from 'semantic-ui-react'; import { DateTime } from 'luxon'; -import I18n from '../../lib/i18n'; -import Markdown from '../Markdown'; -import RegistrationRequirements from './Requirements'; -import { getFullDateTimeString } from '../../lib/utils/dates'; -import WinnerTable from './WinnerTable'; -import InformationGrid from './GeneralInfo/InformationGrid'; -import OneColumnGridEntry from './GeneralInfo/OneColumnGridEntry'; +import I18n from '../../../lib/i18n'; +import Markdown from '../../Markdown'; +import RegistrationRequirements from '../Requirements'; +import { getFullDateTimeString } from '../../../lib/utils/dates'; +import WinnerTable from '../WinnerTable'; +import InformationGrid from './InformationGrid'; +import OneColumnGridEntry from './OneColumnGridEntry'; export default function GeneralInfoTab({ competition, @@ -78,11 +78,10 @@ export default function GeneralInfoTab({ -
- {competition.main_event_id && } -
- {records && } -
+ + {competition.main_event_id && } + {records && } + ) : ( + )} + + ) : ( + + ); +} + +function CompetitionHighlights({ competition, winners, records = null }) { + const [showHighlights, setShowHighlights] = useState(true); + + return showHighlights ? ( + <> + + + {competition.main_event_id && } + {records && } + + + ) : ( + + ); +} export default function GeneralInfoTab({ competition, @@ -19,90 +86,47 @@ export default function GeneralInfoTab({ winners, media = [], }) { - const [showRegistrationRequirements, setShowRegistrationRequirements] = useState(!competition['is_probably_over?']); - const [showHighlights, setShowHighlights] = useState(true); + const bottomItems = useMemo(() => [ + { + // TODO enabled if `competition.registration_open && competition.registration_close` + header: I18n.t('competitions.competition_info.registration_period.label'), + content: (), + }, + { + header: I18n.t('competitions.competition_info.registration_requirements'), + content: (), + }, + { + // TODO enabled if competition['results_posted?'] && (competition.main_event_id || records) + header: I18n.t('competitions.competition_info.highlights'), + content: ( + + ), + }, + ], [competition, records, userInfo, winners]); return ( - + + + + + + + + + + + - - - - {competition.registration_open && competition.registration_close && ( - - {competition['registration_not_yet_opened?'] - ? I18n.t('competitions.competition_info.registration_period.range_future_html', { - start_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_open)), - end_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_close)), - }) - : competition['registration_past?'] - ? I18n.t('competitions.competition_info.registration_period.range_past_html', { - start_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_open)), - end_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_close)), - }) - : I18n.t('competitions.competition_info.registration_period.range_ongoing_html', { - start_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_open)), - end_date_and_time: getFullDateTimeString(DateTime.fromISO(competition.registration_close)), - })} - - )} - - {showRegistrationRequirements ? ( - <> -
- -
- {competition['is_probably_over?'] - && ( - - )} - - ) : ( - - )} -
- {competition['results_posted?'] && (competition.main_event_id || records) && ( - - {showHighlights ? ( - <> - - - {competition.main_event_id && } - {records && } - - - ) : ( - - )} - - )} -
-
-
- { competition.winning_results.length > 0 - && ( + {competition.winning_results.length > 0 && ( - + - + - )} + )}
); } diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx new file mode 100644 index 0000000000..1d9457530f --- /dev/null +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx @@ -0,0 +1,115 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import { + Accordion, List, Popup, +} from 'semantic-ui-react'; +import _ from 'lodash'; +import I18n from '../../../lib/i18n'; +import Markdown from '../../Markdown'; +import { events } from '../../../lib/wca-data.js.erb'; +import EventIcon from '../../wca/EventIcon'; +import InformationList from './InformationList'; + +function EventsIconList({ competition }) { + return competition.events.map((event) => ( + + } content={events.byId[event.id].name} /> + {' '} + + )); +} + +function MediaAccordion({ media }) { + const [mediaIndex, setMediaIndex] = useState(-1); + + const handleMediaClick = useCallback((index) => { + setMediaIndex((oldIdx) => (oldIdx === index ? -1 : index)); + }, [setMediaIndex]); + + return ( + + {['report', 'article', 'multimedia'].map((mediaType, i) => { + const mediaOfType = media.filter((m) => m.type === mediaType); + + if (mediaOfType.length <= 0) { + return null; + } + + return ( + + handleMediaClick(i)}> + {`${_.capitalize(mediaType)} (${mediaOfType.length})`} + + + + {mediaOfType.map((item) => ( + + + {item.text} + + + ))} + + + + ); + })} + + ); +} + +export default function InformationEvents({ competition, media }) { + const infoEntries = useMemo(() => [ + { + header: I18n.t('competitions.competition_info.information'), + content: (), + }, + { + header: I18n.t('competitions.competition_info.events'), + padded: true, + content: (), + }, + { + // TODO only enabled if `competition.main_event_id` + header: I18n.t('competitions.competition_info.main_event'), + padded: true, + content: (} content={events.byId[competition.main_event_id].name} />), + }, + { + // TODO only enabled if `competition['results_posted?']` + header: I18n.t('competitions.nav.menu.competitors'), + padded: true, + content: (competition.competitor_count), + }, + { + // TODO only enabled if `media.length > 0` + content: (), + }, + { + // TODO only enabled if `!competition['results_posted?'] && competition.competitor_limit_enabled` + header: I18n.t('competitions.competition_info.competitor_limit'), + padded: true, + content: (competition.competitor_limit), + }, + { + // TODO only enabled if `!competition['results_posted?']` + header: I18n.t('competitions.competition_info.number_of_bookmarks'), + padded: true, + content: (competition.number_of_bookmarks), + }, + ], [competition, media]); + + return ( + + ); +} diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx deleted file mode 100644 index cce7668727..0000000000 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationGrid.jsx +++ /dev/null @@ -1,258 +0,0 @@ -import React, { useState } from 'react'; -import { - Accordion, Grid, Icon, List, Popup, -} from 'semantic-ui-react'; -import _ from 'lodash'; -import TwoColumnGridEntry from './TwoColumnGridEntry'; -import I18n from '../../../lib/i18n'; -import Markdown from '../../Markdown'; -import { countries, events } from '../../../lib/wca-data.js.erb'; -import I18nHTMLTranslate from '../../I18nHTMLTranslate'; -import EventIcon from '../../wca/EventIcon'; -import { competitionUrl, contactCompetitionUrl, personUrl } from '../../../lib/requests/routes.js.erb'; - -const linkToGoogleMapsPlace = (latitude, longitude) => `https://www.google.com/maps/place/${latitude},${longitude}`; - -function LeftColumn({ competition }) { - return ( - - - {competition.date_range} - - - - - - - {competition.city} - {`, ${countries.byIso2[competition.country_iso2].name}`} - - - - - - - - - {competition.venue_address} - - - - {competition.venue_details && ( - - {competition.venue_details} - - )} - - {competition.external_website && ( - - - {`${competition.name} website`} - - - )} - - - {competition.contact ? ( - - ) : ( - - {I18n.t('competitions.competition_info.organization_team')} - - )} - - - {competition.organizers.length > 0 && ( - - {competition.organizers - .toSorted((o1, o2) => o1.name.localeCompare(o2.name)) - .map((user, i) => ( - - {user.wca_id ? ( - {user.name} - ) : ( - user.name - )} - {i !== competition.organizers.length - 1 && ', '} - - ))} - - )} - - - {competition.delegates - .toSorted((d1, d2) => d1.name.localeCompare(d2.name)) - .map((user, i) => ( - - {user.wca_id ? ( - {user.name} - ) : ( - user.name - )} - {i !== competition.delegates.length - 1 && ', '} - - ))} - - - {competition['has_schedule?'] && ( - - - ${I18n.t('common.here')} - ` - ), - } - } - /> - - )} - - ); -} - -function RightColumn({ competition, media }) { - const [mediaIndex, setMediaIndex] = useState(-1); - const handleMediaClick = (index) => { - setMediaIndex(mediaIndex === index ? -1 : index); - }; - return ( - - - - - - - {competition.events.map((event) => ( - - } content={events.byId[event.id].name} /> - {' '} - - ))} - - - {competition.main_event_id && ( - - } content={events.byId[competition.main_event_id].name} /> - - )} - - {competition['results_posted?'] && ( - - {competition.competitor_count} - - )} - { media.length > 0 && ( - - - {['report', 'article', 'multimedia'].map((mediaType, i) => { - const mediaOfType = media.filter((m) => m.type === mediaType); - if (mediaOfType.length > 0) { - return ( - - handleMediaClick(i)}> - {`${_.capitalize(mediaType)} (${mediaOfType.length})`} - - - - {mediaOfType.map((item) => ( - - - {item.text} - - - ))} - - - - ); - } - })} - - - ) } - - {!competition['results_posted?'] && competition.competitor_limit_enabled && ( - - {competition.competitor_limit} - - )} - - {!competition['results_posted?'] && ( - - {competition.number_of_bookmarks} - - )} - - ); -} - -export default function InformationGrid({ competition, media }) { - return ( - <> - - - - - - - - ); -} diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx new file mode 100644 index 0000000000..6b984b236a --- /dev/null +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Grid } from 'semantic-ui-react'; +import DataListGridEntry from './DataListGridEntry'; + +export default function InformationList({ items, headerBias = 0 }) { + return ( + + {items.map((listItem) => ( + + {listItem.content} + + ))} + + ); +} diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/OneColumnGridEntry.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/OneColumnGridEntry.jsx deleted file mode 100644 index 6eb6afc766..0000000000 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/OneColumnGridEntry.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { Grid, Header } from 'semantic-ui-react'; - -export default function ColumnGridEntry({ - header, children, -}) { - return ( - <> - - -
{header}
-
- - {children} - -
- - - {header} -
- {children} -
-
- - ); -} From 1fea0a91750a432bde0da1d70fb0b9699570ec47 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Sun, 5 Jan 2025 21:02:50 +0900 Subject: [PATCH 40/49] Remove padding hack (and just bear the gaps) --- .../CompetitionTabs/GeneralInfo/DataListGridEntry.jsx | 7 ++----- .../CompetitionTabs/GeneralInfo/InformationEvents.jsx | 5 ----- .../CompetitionTabs/GeneralInfo/InformationList.jsx | 3 +-- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/DataListGridEntry.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/DataListGridEntry.jsx index 6ba17cc3b7..50a786b603 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/DataListGridEntry.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/DataListGridEntry.jsx @@ -5,14 +5,11 @@ export default function DataListGridEntry({ header, children, icon, - padded = false, headerBias = 0, }) { - const style = padded ? {} : { padding: '0em' }; - return ( <> - + { icon ? :
{header}
} @@ -21,7 +18,7 @@ export default function DataListGridEntry({ {children}
- + {header}
diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx index 1d9457530f..146681f72b 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx @@ -76,19 +76,16 @@ export default function InformationEvents({ competition, media }) { }, { header: I18n.t('competitions.competition_info.events'), - padded: true, content: (), }, { // TODO only enabled if `competition.main_event_id` header: I18n.t('competitions.competition_info.main_event'), - padded: true, content: (} content={events.byId[competition.main_event_id].name} />), }, { // TODO only enabled if `competition['results_posted?']` header: I18n.t('competitions.nav.menu.competitors'), - padded: true, content: (competition.competitor_count), }, { @@ -98,13 +95,11 @@ export default function InformationEvents({ competition, media }) { { // TODO only enabled if `!competition['results_posted?'] && competition.competitor_limit_enabled` header: I18n.t('competitions.competition_info.competitor_limit'), - padded: true, content: (competition.competitor_limit), }, { // TODO only enabled if `!competition['results_posted?']` header: I18n.t('competitions.competition_info.number_of_bookmarks'), - padded: true, content: (competition.number_of_bookmarks), }, ], [competition, media]); diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx index 6b984b236a..e844419f1a 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx @@ -4,13 +4,12 @@ import DataListGridEntry from './DataListGridEntry'; export default function InformationList({ items, headerBias = 0 }) { return ( - + {items.map((listItem) => ( {listItem.content} From bbeac57a8c0885a1ee51d2fe1af1535642072dfa Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Sun, 5 Jan 2025 21:04:57 +0900 Subject: [PATCH 41/49] Merge information grid and grid entry file --- .../GeneralInfo/DataListGridEntry.jsx | 30 ------------------ .../GeneralInfo/InformationList.jsx | 31 +++++++++++++++++-- 2 files changed, 29 insertions(+), 32 deletions(-) delete mode 100644 app/webpacker/components/CompetitionTabs/GeneralInfo/DataListGridEntry.jsx diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/DataListGridEntry.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/DataListGridEntry.jsx deleted file mode 100644 index 50a786b603..0000000000 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/DataListGridEntry.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { Grid, Header, Icon } from 'semantic-ui-react'; - -export default function DataListGridEntry({ - header, - children, - icon, - headerBias = 0, -}) { - return ( - <> - - - { icon ? - :
{header}
} -
- - {children} - -
- - - {header} -
- {children} -
-
- - ); -} diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx index e844419f1a..7a5f46204a 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx @@ -1,6 +1,33 @@ import React from 'react'; -import { Grid } from 'semantic-ui-react'; -import DataListGridEntry from './DataListGridEntry'; +import { Grid, Header, Icon } from 'semantic-ui-react'; + +function DataListGridEntry({ + header, + children, + icon, + headerBias = 0, +}) { + return ( + <> + + + { icon ? + :
{header}
} +
+ + {children} + +
+ + + {header} +
+ {children} +
+
+ + ); +} export default function InformationList({ items, headerBias = 0 }) { return ( From 87044204bd9e8d6f8c976873e119bf14833ee85d Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Sun, 5 Jan 2025 21:09:31 +0900 Subject: [PATCH 42/49] Get rid of stylesheet --- app/webpacker/components/CompetitionTabs/index.jsx | 3 +-- app/webpacker/components/CompetitionTabs/style.scss | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 app/webpacker/components/CompetitionTabs/style.scss diff --git a/app/webpacker/components/CompetitionTabs/index.jsx b/app/webpacker/components/CompetitionTabs/index.jsx index b9ecf46286..b9b95bde6f 100644 --- a/app/webpacker/components/CompetitionTabs/index.jsx +++ b/app/webpacker/components/CompetitionTabs/index.jsx @@ -6,7 +6,6 @@ import CompetitionTab from './CompetitionTab'; import EventsTable from './EventsTable'; import Schedule from './Schedule'; import WCAQueryClientProvider from '../../lib/providers/WCAQueryClientProvider'; -import './style.scss'; import TimeLimitCutoffInfo from './TimeLimitCutoffInfo'; import I18n from '../../lib/i18n'; @@ -86,7 +85,7 @@ export default function Wrapper({ { const tab = panes[activeIndex]; updatePath(tab.slug); diff --git a/app/webpacker/components/CompetitionTabs/style.scss b/app/webpacker/components/CompetitionTabs/style.scss deleted file mode 100644 index f8a9dab4b1..0000000000 --- a/app/webpacker/components/CompetitionTabs/style.scss +++ /dev/null @@ -1,8 +0,0 @@ -.tab-wrapped { - display: flex; - flex-direction: row; - flex-wrap: wrap; - .item { - color: #e64503 !important; - } -} From 3ca288ae586be99cbaa9aa648e32e800ea6a74c0 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Sun, 5 Jan 2025 21:23:58 +0900 Subject: [PATCH 43/49] Use SemUI List-based information design --- .../GeneralInfo/GeneralInfoTab.jsx | 2 +- .../GeneralInfo/InformationList.jsx | 35 ++++++------------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/GeneralInfoTab.jsx index c4cd6a6276..21fcc8e29e 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/GeneralInfoTab.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/GeneralInfoTab.jsx @@ -117,7 +117,7 @@ export default function GeneralInfoTab({
- + {competition.winning_results.length > 0 && ( diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx index 7a5f46204a..dc1e0c497b 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationList.jsx @@ -1,47 +1,34 @@ import React from 'react'; -import { Grid, Header, Icon } from 'semantic-ui-react'; +import { List } from 'semantic-ui-react'; function DataListGridEntry({ header, children, icon, - headerBias = 0, }) { return ( - <> - - - { icon ? - :
{header}
} -
- - {children} - -
- - - {header} -
- {children} -
-
- + + {icon && } + + {header} + {children} + + ); } -export default function InformationList({ items, headerBias = 0 }) { +export default function InformationList({ items }) { return ( - + {items.map((listItem) => ( {listItem.content} ))} - + ); } From 37c208e448c9dedec3d530af62ffa84c8a2de2eb Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Sun, 5 Jan 2025 21:36:53 +0900 Subject: [PATCH 44/49] Provide (somewhat arbitrary) icons for DateAddressContact list --- .../CompetitionTabs/GeneralInfo/DateAddressContact.jsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/DateAddressContact.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/DateAddressContact.jsx index 71c63991be..73072c5407 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/DateAddressContact.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/DateAddressContact.jsx @@ -122,6 +122,7 @@ export default function DateAddressContact({ competition }) { { header: I18n.t('competitions.competition_info.date'), content: (), + icon: 'calendar plus', }, { header: I18n.t('competitions.competition_info.city'), @@ -132,28 +133,34 @@ export default function DateAddressContact({ competition }) { {countries.byIso2[competition.country_iso2].name} ), + icon: 'map marker alternate', }, { header: I18n.t('competitions.competition_info.venue'), content: (), + icon: 'building outline', }, { header: I18n.t('competitions.competition_info.address'), content: (), + icon: 'map outline', }, { // TODO only enabled if `competition.venue_details` header: I18n.t('competitions.competition_info.details'), content: (), + icon: 'info circle', }, { // TODO only enabled if `competition.external_website` header: I18n.t('competitions.competition_info.website'), content: (), + icon: 'globe', }, { header: I18n.t('competitions.competition_info.contact'), content: (), + icon: 'comment outline', }, { // TODO only enabled if `competition.organizers.length > 0` @@ -161,12 +168,14 @@ export default function DateAddressContact({ competition }) { count: competition.organizers.length, }), content: (), + icon: 'folder open outline', }, { header: I18n.t('competitions.competition_info.delegate', { count: competition.delegates.length, }), content: (), + icon: 'address card outline', }, { // TODO only enabled if `competition['has_schedule?']` From 75cee7d32752930576b80058fd133bf8a6cc3517 Mon Sep 17 00:00:00 2001 From: Gregor Billing Date: Sun, 5 Jan 2025 21:41:05 +0900 Subject: [PATCH 45/49] Merge main event into events list --- .../GeneralInfo/InformationEvents.jsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx index 146681f72b..d8fc6edc43 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx @@ -9,10 +9,13 @@ import { events } from '../../../lib/wca-data.js.erb'; import EventIcon from '../../wca/EventIcon'; import InformationList from './InformationList'; -function EventsIconList({ competition }) { +function EventsIconList({ competition, mainEventId }) { return competition.events.map((event) => ( - } content={events.byId[event.id].name} /> + } + content={events.byId[event.id].name} + /> {' '} )); @@ -76,12 +79,7 @@ export default function InformationEvents({ competition, media }) { }, { header: I18n.t('competitions.competition_info.events'), - content: (), - }, - { - // TODO only enabled if `competition.main_event_id` - header: I18n.t('competitions.competition_info.main_event'), - content: (} content={events.byId[competition.main_event_id].name} />), + content: (), }, { // TODO only enabled if `competition['results_posted?']` From e54730dbdd45a4739fcd0eab33bbd8dbacc028bc Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 6 Jan 2025 16:17:03 +0100 Subject: [PATCH 46/49] only show certain entries conditionally --- .../GeneralInfo/DateAddressContact.jsx | 135 ++++++++++-------- 1 file changed, 75 insertions(+), 60 deletions(-) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/DateAddressContact.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/DateAddressContact.jsx index 73072c5407..3355399748 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/DateAddressContact.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/DateAddressContact.jsx @@ -118,71 +118,86 @@ function PdfDownloadLink({ competition }) { } export default function DateAddressContact({ competition }) { - const infoEntries = useMemo(() => [ - { - header: I18n.t('competitions.competition_info.date'), - content: (), - icon: 'calendar plus', - }, - { - header: I18n.t('competitions.competition_info.city'), - content: ( - <> - {competition.city} - {', '} - {countries.byIso2[competition.country_iso2].name} - - ), - icon: 'map marker alternate', - }, - { - header: I18n.t('competitions.competition_info.venue'), - content: (), - icon: 'building outline', - }, - { - header: I18n.t('competitions.competition_info.address'), - content: (), - icon: 'map outline', - }, - { - // TODO only enabled if `competition.venue_details` - header: I18n.t('competitions.competition_info.details'), - content: (), - icon: 'info circle', - }, - { - // TODO only enabled if `competition.external_website` - header: I18n.t('competitions.competition_info.website'), - content: (), - icon: 'globe', - }, - { - header: I18n.t('competitions.competition_info.contact'), - content: (), - icon: 'comment outline', - }, - { - // TODO only enabled if `competition.organizers.length > 0` - header: I18n.t('competitions.competition_info.organizer_plural', { - count: competition.organizers.length, - }), - content: (), - icon: 'folder open outline', - }, - { + const infoEntries = useMemo(() => { + const entries = [ + { + header: I18n.t('competitions.competition_info.date'), + content: (), + icon: 'calendar plus', + }, + { + header: I18n.t('competitions.competition_info.city'), + content: ( + <> + {competition.city} + {', '} + {countries.byIso2[competition.country_iso2].name} + + ), + icon: 'map marker alternate', + }, + { + header: I18n.t('competitions.competition_info.venue'), + content: (), + icon: 'building outline', + }, + { + header: I18n.t('competitions.competition_info.address'), + content: (), + icon: 'map outline', + }]; + + if (competition.venue_details) { + entries.push({ + header: I18n.t('competitions.competition_info.details'), + content: (), + icon: 'info circle', + }); + } + + if (competition.external_website) { + entries.push({ + header: I18n.t('competitions.competition_info.website'), + content: (), + icon: 'globe', + }); + } + + entries.push( + { + header: I18n.t('competitions.competition_info.contact'), + content: (), + icon: 'comment outline', + }, + ); + + if (competition.organizers.length > 0) { + entries.push({ + header: I18n.t('competitions.competition_info.organizer_plural', { + count: competition.organizers.length, + }), + content: (), + icon: 'folder open outline', + }); + } + + entries.push({ header: I18n.t('competitions.competition_info.delegate', { count: competition.delegates.length, }), content: (), icon: 'address card outline', - }, - { - // TODO only enabled if `competition['has_schedule?']` - icon: 'print', - content: (), - }, - ], [competition]); + }); + + if (competition['has_schedule?']) { + entries.push({ + icon: 'print', + content: (), + }); + } + + return entries; + }, [competition]); return ( From 9aeeccbeb05a3a9b8bea95aa481a1bd94ce38645 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 6 Jan 2025 16:40:47 +0100 Subject: [PATCH 47/49] only show certain entries conditionally for the other column --- .../GeneralInfo/InformationEvents.jsx | 71 +++++++++++-------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx index d8fc6edc43..96900ed0e1 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx @@ -72,35 +72,48 @@ function MediaAccordion({ media }) { } export default function InformationEvents({ competition, media }) { - const infoEntries = useMemo(() => [ - { - header: I18n.t('competitions.competition_info.information'), - content: (), - }, - { - header: I18n.t('competitions.competition_info.events'), - content: (), - }, - { - // TODO only enabled if `competition['results_posted?']` - header: I18n.t('competitions.nav.menu.competitors'), - content: (competition.competitor_count), - }, - { - // TODO only enabled if `media.length > 0` - content: (), - }, - { - // TODO only enabled if `!competition['results_posted?'] && competition.competitor_limit_enabled` - header: I18n.t('competitions.competition_info.competitor_limit'), - content: (competition.competitor_limit), - }, - { - // TODO only enabled if `!competition['results_posted?']` - header: I18n.t('competitions.competition_info.number_of_bookmarks'), - content: (competition.number_of_bookmarks), - }, - ], [competition, media]); + const infoEntries = useMemo(() => { + const entries = [ + { + header: I18n.t('competitions.competition_info.information'), + content: (), + }, + { + header: I18n.t('competitions.competition_info.events'), + content: (), + }]; + + if (competition['results_posted?']) { + entries.push({ + header: I18n.t('competitions.nav.menu.competitors'), + content: (competition.competitor_count), + }); + } + + if (media.length > 0) { + entries.push( + { + content: (), + }, + ); + } + + if (!competition['results_posted?'] && competition.competitor_limit_enabled) { + entries.push({ + header: I18n.t('competitions.competition_info.competitor_limit'), + content: (competition.competitor_limit), + }); + } + + if (!competition['results_posted?']) { + entries.push({ + header: I18n.t('competitions.competition_info.number_of_bookmarks'), + content: (competition.number_of_bookmarks), + }); + } + + return entries; + }, [competition, media]); return ( From 2d496861385666a359a214080d12c2eb71816589 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 6 Jan 2025 16:55:33 +0100 Subject: [PATCH 48/49] only show certain entries conditionally for the bottom items --- .../GeneralInfo/GeneralInfoTab.jsx | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/GeneralInfoTab.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/GeneralInfoTab.jsx index 21fcc8e29e..105803acd6 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/GeneralInfoTab.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/GeneralInfoTab.jsx @@ -86,24 +86,28 @@ export default function GeneralInfoTab({ winners, media = [], }) { - const bottomItems = useMemo(() => [ - { - // TODO enabled if `competition.registration_open && competition.registration_close` - header: I18n.t('competitions.competition_info.registration_period.label'), - content: (), - }, - { + const bottomItems = useMemo(() => { + const items = []; + if (competition.registration_open && competition.registration_close) { + items.push({ + header: I18n.t('competitions.competition_info.registration_period.label'), + content: (), + }); + } + items.push({ header: I18n.t('competitions.competition_info.registration_requirements'), content: (), - }, - { - // TODO enabled if competition['results_posted?'] && (competition.main_event_id || records) - header: I18n.t('competitions.competition_info.highlights'), - content: ( - - ), - }, - ], [competition, records, userInfo, winners]); + }); + if (competition['results_posted?'] && (competition.main_event_id || records)) { + items.push({ + header: I18n.t('competitions.competition_info.highlights'), + content: ( + + ), + }); + } + return items; + }, [competition, records, userInfo, winners]); return ( From 69dfd4bb79be01922e483ee8795182d31138b699 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 6 Jan 2025 17:17:35 +0100 Subject: [PATCH 49/49] add main Event string to main event icon --- .../CompetitionTabs/GeneralInfo/InformationEvents.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx index 96900ed0e1..5f926c7de8 100644 --- a/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx +++ b/app/webpacker/components/CompetitionTabs/GeneralInfo/InformationEvents.jsx @@ -14,7 +14,7 @@ function EventsIconList({ competition, mainEventId }) { } - content={events.byId[event.id].name} + content={event.id === mainEventId ? `${I18n.t('competitions.competition_info.main_event')}: ${events.byId[event.id].name}` : events.byId[event.id].name} /> {' '}