Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate the My Competition Table to React #10370

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/controllers/competitions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ def my_competitions
registrations = current_user.registrations.includes(:competition).accepted.reject { |r| r.competition.results_posted? }
registrations.concat(current_user.registrations.includes(:competition).pending.select { |r| r.competition.upcoming? })
@registered_for_by_competition_id = registrations.uniq.to_h do |r|
[r.competition.id, r]
[r.competition.id, r.as_json({ only: %w[competing_status], include: %w[], methods: %w[] })]
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
end
competition_ids.concat(@registered_for_by_competition_id.keys)
if current_user.person
Expand Down
8 changes: 8 additions & 0 deletions app/models/competition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,14 @@ def main_event
Event.c_find(main_event_id)
end

def report_posted?
delegate_report.posted?
end

def visible?
self.showAtAll
end
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved

def events_held?(desired_event_ids)
# rubocop:disable Style/BitwisePredicate
# We have to shut up Rubocop here because otherwise it thinks that
Expand Down
82 changes: 16 additions & 66 deletions app/views/competitions/my_competitions.html.erb
Original file line number Diff line number Diff line change
@@ -1,70 +1,20 @@
<% provide(:title, t('.title')) %>

<div class="container">
<h3><%= yield(:title) %></h3>

<%= t '.disclaimer' %>

<%= render "my_competitions_table", competitions: @not_past_competitions, registrations_by_competition_id: @registered_for_by_competition_id, past: false %>

<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<a data-toggle="collapse" href="#past-competitions" class="collapsed">
<span><%= t '.past_competitions' %> (<%= @past_competitions.length %>)</span>
<span class="collapse-indicator"></span>
</a>
</h3>
</div>

<div id="past-competitions" class="panel-collapse collapse">
<div class="panel-body">
<%= render "my_competitions_table", competitions: @past_competitions, registrations_by_competition_id: @registered_for_by_competition_id, past: true %>
</div>
</div>
</div>
<script>
(function() {
// Reflow table when its made visible. See:
// https://github.com/thewca/worldcubeassociation.org/issues/343
// changed to .trigger, see https://github.com/thewca/worldcubeassociation.org/issues/343#issuecomment-182916594
$('#past-competitions').on('show.bs.collapse', function(e) {
$('#past-competitions table:not(.floatThead-table)').trigger('reflow');
});
})();
</script>

<% if current_user.wca_id %>
<p>
<%= link_to t('layouts.navigation.my_results'), person_path(current_user.wca_id) %>
</p>
<% if current_user.senior_delegate? %>
<p><%= link_to "Upcoming competitions for your subordinate Delegates", competitions_for_senior_path %></p>
<% end %>
<% end %>

<h3>
<%= ui_icon('bookmark') %>
<%= t '.bookmarked_title' %>
</h3>

<%= t '.bookmarked_explanation' %>
<%= form_tag(my_comps_path, method: :get, class: "competition-select form-inline #{@display}", id: "bookmarked-competitions-query-form") do %>
<div id="registration-status" class="form-group registration-status-selector">
<%= check_box_tag(:show_registration_status, params[:show_registration_status], params[:show_registration_status] == "on") %>
<%= label_tag(name = :show_registration_status, content_or_options = t('competitions.index.show_registration_status')) %>
</div>
<% end %>
<%= render "my_competitions_table", competitions: @bookmarked_competitions, registrations_by_competition_id: @registered_for_by_competition_id, past: false, bookmarked: true %>
<% options = {
only: %w[id name website start_date end_date],
methods: %w[url city country_iso2 registration_not_yet_opened? registration_full? registration_past? results_posted? visible? confirmed? cancelled? report_posted? date_range short_display_name],
include: %w[],
} %>

<div class="container">
<%= react_component("MyCompetitions", {
userInfo: @current_user.as_json,
permissions: @current_user.permissions.as_json,
competitions: {
pastCompetitions: @past_competitions.as_json(options),
futureCompetitions: @not_past_competitions.as_json(options),
bookmarkedCompetitions: @bookmarked_competitions.as_json(options),
registrationStatuses: @registered_for_by_competition_id,
},
}) %>
</div>

<script>
$(document).ready(function() {
var $form = $('#bookmarked-competitions-query-form');
function submitForm() {
$form.trigger('submit.rails');
}
$('#show_registration_status').on('change', submitForm);
});
</script>
63 changes: 63 additions & 0 deletions app/webpacker/components/MyCompetitions/PastCompetitionTable.jsx
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
Icon, Popup, Table, TableBody, TableHeader,
} from 'semantic-ui-react';
import React from 'react';
import I18n from '../../lib/i18n';
import ReportTableCell from './ReportTableCell';
import { countries } from '../../lib/wca-data.js.erb';

export default function PastCompetitionsTable({ competitions, permissions }) {
return (
<Table striped>
<TableHeader>
<Table.Row>
<Table.HeaderCell>
{I18n.t('competitions.adjacent_competitions.name')}
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
</Table.HeaderCell>
<Table.HeaderCell>
{I18n.t('competitions.adjacent_competitions.location')}
</Table.HeaderCell>
<Table.HeaderCell>
{I18n.t('competitions.adjacent_competitions.date')}
</Table.HeaderCell>
<Table.HeaderCell />
<Table.HeaderCell />
<Table.HeaderCell />
</Table.Row>
</TableHeader>

<TableBody>
{competitions.map((competition) => (
<Table.Row key={competition.id}>
<Table.Cell>
<a href={competition.url}>{competition.short_display_name}</a>
</Table.Cell>
<Table.Cell>
{countries.byIso2[competition.country_iso2].name}
{`, ${competition.city}`}
</Table.Cell>
<Table.Cell>
{competition.date_range}
</Table.Cell>
<Table.Cell>
{!competition['results_posted?'] && (
<Icon name="calendar check" />
)}
</Table.Cell>
<Table.Cell>
{competition['results_posted?'] && (
<Popup
content={I18n.t('competitions.my_competitions_table.results_up')}
trigger={(
<Icon name="check circle" />
)}
/>
)}
</Table.Cell>
<ReportTableCell competitionId={competition.id} permissions={permissions} />
</Table.Row>
))}
</TableBody>
</Table>
);
}
40 changes: 40 additions & 0 deletions app/webpacker/components/MyCompetitions/ReportTableCell.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Icon, Popup, Table } from 'semantic-ui-react';
import React from 'react';
import I18n from '../../lib/i18n';
import { competitionReportUrl, competitionReportEditUrl } from '../../lib/requests/routes.js.erb';

export default function ReportTableCell({ permissions, competitionId, isReportPosted }) {
return (
<Table.Cell>
{(permissions.can_administer_competitions.scope === '*' || permissions.can_administer_competitions.scope.includes(competitionId)) && (
<>
<Popup
content={I18n.t('competitions.my_competitions_table.report')}
trigger={(
<a href={competitionReportUrl(competitionId)}>
<Icon name="file alternate" />
</a>
)}
/>
<Popup
content={I18n.t('competitions.my_competitions_table.edit_report')}
trigger={(
<a href={competitionReportEditUrl(competitionId)}>
<Icon name="edit" />
</a>
)}
/>
{ !isReportPosted
&& permissions.can_administer_competitions.scope.includes(competitionId) && (
<Popup
content={I18n.t('competitions.my_competitions_table.missing_report')}
trigger={(
<Icon name="warning" />
)}
/>
)}
</>
)}
</Table.Cell>
);
}
109 changes: 109 additions & 0 deletions app/webpacker/components/MyCompetitions/UpcomingCompetitionTable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
Icon,
Popup, Table, TableBody, TableHeader,
} from 'semantic-ui-react';
import React from 'react';
import I18n from '../../lib/i18n';
import { competitionStatusText } from '../../lib/utils/competition-table';
import { competitionRegistrationsUrl, editCompetitionsUrl } from '../../lib/requests/routes.js.erb';
import ReportTableCell from './ReportTableCell';
import { countries } from '../../lib/wca-data.js.erb';

const registrationStatusIcon = (registrationStatus) => {
switch (registrationStatus?.competing_status) {
case 'pending': return <Icon name="hourglass" />;
case 'waiting_list': return <Icon name="hourglass" />;
case 'accepted': return <Icon name="check circle" />;
case 'cancelled': return <Icon name="trash" />;
case 'rejected': return <Icon name="trash" />;
default: return <Icon />;
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
}
};

const competitionStatusIcon = (competition) => {
if (competition['registration_not_yet_opened?']) {
return <Icon name="clock" color="blue" />;
}
if (competition['registration_past?']) {
return <Icon name="user times" color="red" />;
}
if (competition['registration_full?']) {
return <Icon name="user clock" color="orange" />;
}
return <Icon name="user plus" color="green" />;
};

export default function UpcomingCompetitionTable({
competitions, permissions, registrationStatuses, shouldShowRegistrationStatus = false,
}) {
return (
<Table>
<TableHeader>
<Table.Row>
{ shouldShowRegistrationStatus && <Table.HeaderCell /> }
<Table.HeaderCell>
{I18n.t('competitions.adjacent_competitions.name')}
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
</Table.HeaderCell>
<Table.HeaderCell>
{I18n.t('competitions.adjacent_competitions.location')}
</Table.HeaderCell>
<Table.HeaderCell>
{I18n.t('competitions.adjacent_competitions.date')}
</Table.HeaderCell>
<Table.HeaderCell />
<Table.HeaderCell />
<Table.HeaderCell />
<Table.HeaderCell />
<Table.HeaderCell />
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
</Table.Row>
</TableHeader>

<TableBody>
{competitions.map((competition) => (
<Popup
key={competition.id}
position="top center"
content={competitionStatusText(competition, registrationStatuses[competition.id])}
trigger={(
<Table.Row positive={competition['confirmed?'] && !competition['cancelled?']}>
{ shouldShowRegistrationStatus && (
<Table.Cell>
{competitionStatusIcon(competition)}
</Table.Cell>
)}
<Table.Cell>
<a href={competition.url}>{competition.short_display_name}</a>
</Table.Cell>
<Table.Cell>
{countries.byIso2[competition.country_iso2].name}
{`, ${competition.city}`}
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
</Table.Cell>
<Table.Cell>
{competition.date_range}
</Table.Cell>
<Table.Cell>
{registrationStatusIcon(registrationStatuses[competition.id])}
</Table.Cell>
<Table.Cell>
{ (permissions.can_organize_competitions.scope === '*' || permissions.can_organize_competitions.scope.includes(competition.id)) && (
<a href={editCompetitionsUrl(competition.id)}>
{ I18n.t('competitions.my_competitions_table.edit') }
</a>
)}
</Table.Cell>
<Table.Cell>
{ (permissions.can_organize_competitions.scope === '*' || permissions.can_organize_competitions.scope.includes(competition.id)) && (
<a href={competitionRegistrationsUrl(competition.id)}>
{ I18n.t('competitions.my_competitions_table.registrations') }
</a>
)}
</Table.Cell>
<ReportTableCell competitionId={competition.id} permissions={permissions} />
</Table.Row>
)}
/>
))}
</TableBody>
</Table>
);
}
73 changes: 73 additions & 0 deletions app/webpacker/components/MyCompetitions/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, { useState } from 'react';
import {
Accordion,
Header,
Icon,
Checkbox, Segment,
} from 'semantic-ui-react';
import I18n from '../../lib/i18n';
import {
personUrl,
} from '../../lib/requests/routes.js.erb';
import UpcomingCompetitionTable from './UpcomingCompetitionTable';
import PastCompetitionsTable from './PastCompetitionTable';

const defaultPermissions = {
can_attend_competitions: { scope: [] },
can_organize_competitions: { scope: [] },
can_administer_competitions: { scope: [] },
};

export default function MyCompetitions({ permissions, competitions, userInfo }) {
const [isAccordionOpen, setIsAccordionOpen] = useState(false);
const [shouldShowRegistrationStatus, setShouldShowRegistrationStatus] = useState(false);
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved

return (
<>
<Header>
{I18n.t('competitions.my_competitions.title')}
</Header>
<p>
{I18n.t('competitions.my_competitions.disclaimer')}
</p>
<UpcomingCompetitionTable
competitions={competitions.futureCompetitions ?? []}
permissions={permissions ?? defaultPermissions}
registrationStatuses={competitions.registrationStatuses ?? {}}
/>
<Accordion fluid styled>
<Accordion.Title
active={isAccordionOpen}
onClick={() => setIsAccordionOpen(!isAccordionOpen)}
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
>
{`${I18n.t('competitions.my_competitions.past_competitions')} (${competitions.pastCompetitions?.length ?? 0})`}
</Accordion.Title>
<Accordion.Content active={isAccordionOpen}>
<PastCompetitionsTable
permissions={permissions}
competitions={competitions.pastCompetitions ?? []}
/>
</Accordion.Content>
</Accordion>
<Segment>
<a href={personUrl(userInfo.wca_id)}>{I18n.t('layouts.navigation.my_results')}</a>
</Segment>
<Header>
<Icon name="bookmark" />
{I18n.t('competitions.my_competitions.bookmarked_title')}
</Header>
<p>{I18n.t('competitions.my_competitions.bookmarked_explanation')}</p>
<Checkbox
checked={shouldShowRegistrationStatus}
label={I18n.t('competitions.index.show_registration_status')}
onChange={() => setShouldShowRegistrationStatus(!shouldShowRegistrationStatus)}
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
/>
<UpcomingCompetitionTable
competitions={competitions.bookmarkedCompetitions ?? []}
registrationStatuses={competitions.registrationStatuses ?? {}}
shouldShowRegistrationStatus={shouldShowRegistrationStatus}
permissions={permissions}
/>
</>
);
}
Loading
Loading