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 14 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
4 changes: 2 additions & 2 deletions app/models/registration.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

class Registration < ApplicationRecord
scope :pending, -> { where(accepted_at: nil, deleted_at: nil, is_competing: true) }
scope :accepted, -> { where.not(accepted_at: nil).where(deleted_at: nil) }
scope :pending, -> { where(competing_status: 'pending') }
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
scope :accepted, -> { where(competing_status: 'accepted') }
scope :deleted, -> { where.not(deleted_at: nil) }
scope :cancelled, -> { where(competing_status: 'cancelled') }
scope :rejected, -> { where(competing_status: 'rejected') }
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, registration_open],
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,
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
permissions: current_user.permissions.as_json,
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
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>
57 changes: 57 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,57 @@
import {
Icon, Popup, Table, TableBody, TableHeader,
} from 'semantic-ui-react';
import React from 'react';
import I18n from '../../lib/i18n';
import {
DateTableCell, LocationTableCell, NameTableCell, ReportTableCell,
} from './TableCells';

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}>
<NameTableCell competition={competition} />
<LocationTableCell competition={competition} />
<DateTableCell competition={competition} />
<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>
);
}
74 changes: 74 additions & 0 deletions app/webpacker/components/MyCompetitions/TableCells.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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';
import { countries } from '../../lib/wca-data.js.erb';

export function NameTableCell({ competition }) {
return (
<Table.Cell>
<a href={competition.url}>{competition.short_display_name}</a>
</Table.Cell>
);
}

export function LocationTableCell({ competition }) {
return (
<Table.Cell>
{competition.city}
{`, ${countries.byIso2[competition.country_iso2].name}`}
</Table.Cell>
);
}

export function DateTableCell({ competition }) {
return (
<Table.Cell>
{competition.date_range}
</Table.Cell>
);
}

export function ReportTableCell({
permissions, competitionId, isReportPosted, canAdminCompetitions,
}) {
if (permissions.can_administer_competitions.scope === '*' || permissions.can_administer_competitions.scope.includes(competitionId)) {
return (
<Table.Cell>
<>
<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>
);
}

// A user might be able to see only certain reports in the list, so we return an empty cell

if (canAdminCompetitions) {
return <Table.Cell />;
}
}
130 changes: 130 additions & 0 deletions app/webpacker/components/MyCompetitions/UpcomingCompetitionTable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import {
Icon,
Popup, Table, TableBody, TableHeader,
} from 'semantic-ui-react';
import React from 'react';
import { DateTime } from 'luxon';
import I18n from '../../lib/i18n';
import { competitionStatusText } from '../../lib/utils/competition-table';
import { competitionRegistrationsUrl, editCompetitionsUrl } from '../../lib/requests/routes.js.erb';
import {
DateTableCell, LocationTableCell, NameTableCell, ReportTableCell,
} from './TableCells';

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 competitionStatusIconText = (competition) => {
if (competition['registration_not_yet_opened?']) {
return I18n.t('competitions.index.tooltips.registration.opens_in', { duration: DateTime.fromISO(competition.registration_open).toRelative() });
}
if (competition['registration_past?']) {
return I18n.t('competitions.index.tooltips.registration.closed', { days: DateTime.fromISO(competition.start_date).toRelative() });
}
if (competition['registration_full?']) {
return I18n.t('competitions.index.tooltips.registration.full');
}
return I18n.t('competitions.index.tooltips.registration.open');
};

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,
}) {
const canAdminCompetitions = permissions.can_administer_competitions.scope === '*' || competitions.some((c) => permissions.can_administer_competitions.scope.includes(c.id));

return (
<Table>
<TableHeader>
<Table.Row>
{ shouldShowRegistrationStatus && <Table.HeaderCell /> }
<Table.HeaderCell>
{I18n.t('competitions.competition_info.name')}
</Table.HeaderCell>
<Table.HeaderCell>
{I18n.t('competitions.competition_info.location')}
</Table.HeaderCell>
<Table.HeaderCell>
{I18n.t('competitions.competition_info.date')}
</Table.HeaderCell>
<Table.HeaderCell />
{canAdminCompetitions
&& (
<>
<Table.HeaderCell />
<Table.HeaderCell />
<Table.HeaderCell />
</>
)}

</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?']} negative={!competition['visible?']}>
{ shouldShowRegistrationStatus && (
<Popup
position="top left"
content={competitionStatusIconText(competition)}
trigger={(
<Table.Cell>
{competitionStatusIcon(competition)}
</Table.Cell>
)}
/>
)}
<NameTableCell competition={competition} />
<LocationTableCell competition={competition} />
<DateTableCell competition={competition} />
<Table.Cell>
{registrationStatusIcon(registrationStatuses[competition.id])}
</Table.Cell>
{ (permissions.can_organize_competitions.scope === '*' || permissions.can_organize_competitions.scope.includes(competition.id)) && (
<Table.Cell>
<a href={editCompetitionsUrl(competition.id)}>
{ I18n.t('competitions.my_competitions_table.edit') }
</a>
</Table.Cell>
)}
{ (permissions.can_organize_competitions.scope === '*' || permissions.can_organize_competitions.scope.includes(competition.id)) && (
<Table.Cell>
<a href={competitionRegistrationsUrl(competition.id)}>
{ I18n.t('competitions.my_competitions_table.registrations') }
</a>
</Table.Cell>
)}
<ReportTableCell competitionId={competition.id} permissions={permissions} canAdminCompetitions={canAdminCompetitions} />
</Table.Row>
)}
/>
))}
</TableBody>
</Table>
);
}
Loading
Loading