hexlet-codebattle/codebattle

View on GitHub
services/app/apps/codebattle/assets/js/widgets/pages/event/EventRatingPanel.jsx

Summary

Maintainability
F
4 days
Test Coverage
import React, { useState, useEffect, useCallback } from 'react';

import cn from 'classnames';
import i18next from 'i18next';
import groupBy from 'lodash/groupBy';
import { useDispatch } from 'react-redux';

import { actions } from '../../slices';

import LeaderboardPagination from './LeaderboardPagination';

const getCustomEventTrClassNamePersonal = (type, muted, isUser) => cn('text-dark font-weight-bold cb-custom-event-tr', {
    'cb-custom-event-bg-success': type === 'clan' && !muted,
    'cb-custom-event-bg-muted-success': type === 'clan' && muted,
    'cb-custom-event-bg-purple': type === 'user' && !muted,
    'cb-custom-event-bg-muted-purple': type === 'user' && muted,
    'cb-custom-event-tr-brown-border': isUser,
  });

const tableDataCellClassNamePersonal = hideSeparator => cn(
    'p-1 pl-4 my-2 align-middle text-nowrap position-relative cb-custom-event-td border-0',
    {
      'hide-separator': hideSeparator,
    },
  );

const tableDataCellClassName = cn(
  'p-1 pl-4 my-2 align-middle text-nowrap position-relative cb-custom-event-td border-0',
);

const navTabsClassName = cn(
  'nab nab-tabs d-flex flex-nowrap cb-overflow-x-auto cb-overlfow-y-hidden',
  'rounded-top',
);

const getTabLinkClassName = isActive => cn(
    'nav-item nav-link cb-custom-event-nav-item position-relative',
    'text-nowrap text-white rounded-0 p-2 px-3 border-0 w-100 bg-gray',
    {
      active: isActive,
      'cb-custom-event-common-leaderboard-bg text-dark font-weight-bold':
        isActive,
    },
  );

const getCustomEventTrClassName = (item, selectedId) => cn(
    'text-dark font-weight-bold cb-custom-event-tr',
    {
      'cb-gold-place-bg': item?.place === 1,
      'cb-silver-place-bg': item?.place === 2,
      'cb-bronze-place-bg': item?.place === 3,
      'bg-white': !item?.place || item.place > 3,
    },
    {
      'cb-custom-event-tr-brown-border': item.userId
        ? item.userId === selectedId
        : item.clanId === selectedId,
    },
  );

const commonRatingTypes = {
  personal: 'personal',
  clan: 'clan',
  player: 'player',
  playerClan: 'player_clan',
};

const EventRatingPanel = ({
  commonLeaderboard: {
 items, pageNumber, pageSize, totalEntries,
} = {
    items: [],
    pageNumber: 1,
    pageSize: 10,
    totalEntries: 0,
  },
  currentUserClanId,
  currentUserId,
  personalTournamentId,
  eventId,
}) => {
  const dispatch = useDispatch();

  const [type, setType] = useState(
    personalTournamentId ? commonRatingTypes.personal : commonRatingTypes.clan,
  );
  const selectedId = type === commonRatingTypes.clan ? currentUserClanId : currentUserId;

  const handleClick = useCallback(
    e => {
      const {
        currentTarget: { dataset },
      } = e;
      setType(dataset.tabName);
    },
    [setType],
  );

  const setPage = useCallback(
    page => {
      (async () => {
        try {
          await dispatch(
            actions.fetchCommonLeaderboard({
              type,
              eventId,
              pageNumber: page,
              pageSize,
            }),
          );
        } catch (e) {
          throw new Error(e.message);
        }
      })();
    },
    [type, eventId, pageSize, dispatch],
  );

  useEffect(() => {
    (async () => {
      try {
        await dispatch(
          actions.fetchCommonLeaderboard({
            type,
            eventId,
          }),
        );
      } catch (e) {
        throw new Error(e.message);
      }
    })();
    /* eslint-disable-next-line */
  }, [type]);

  if (personalTournamentId) {
    const groupedItems = Object.values(groupBy(items, item => item.clanRank));

    return (
      <div className="my-2 px-1 mt-lg-0 rounded-lg position-relative cb-overflow-x-auto cb-overflow-y-auto">
        <table className="table table-striped cb-custom-event-table">
          <thead className="text-muted">
            <tr>
              <th className="p-1 pl-4 font-weight-light border-0">{}</th>
              <th className="p-1 pl-4 font-weight-light border-0">
                {i18next.t('Clan')}
              </th>
              <th className="p-1 pl-4 font-weight-light border-0">
                {i18next.t('Score')}
              </th>
              <th className="p-1 pl-4 font-weight-light border-0">
                {i18next.t('Wins count')}
              </th>
              <th className="p-1 pl-4 font-weight-light border-0">
                {i18next.t('Total time for solving task')}
              </th>
            </tr>
          </thead>
          <tbody>
            {groupedItems?.map((users, index) => (
              <React.Fragment key={`${type}-clan-${users[0].clanId}`}>
                <tr className="cb-custom-event-empty-space-tr" />
                <tr className={getCustomEventTrClassNamePersonal('clan', index > 3)}>
                  <td className={tableDataCellClassNamePersonal(true)}>
                    {users[0].clanRank}
                  </td>
                  <td
                    title={users[0].clanLongName}
                    className={tableDataCellClassNamePersonal()}
                  >
                    <div className="cb-custom-event-name mr-1">
                      {users[0].clanName}
                    </div>
                  </td>
                  <td className={tableDataCellClassNamePersonal()}>
                    {users.reduce((acc, user) => acc + user.totalScore, 0) || 0}
                  </td>
                  <td className={tableDataCellClassNamePersonal()}>
                    {users.reduce((acc, user) => acc + user.winsCount, 0) || 0}
                  </td>
                  <td className={tableDataCellClassNamePersonal()}>
                    {users.reduce(
                      (acc, user) => acc + user.totalDurationSec,
                      0,
                    ) || 0}
                  </td>
                </tr>
                {users.map(user => (
                  <React.Fragment key={`${type}-user-${user.userId}`}>
                    <tr className="cb-custom-event-empty-space-tr" />
                    <tr
                      className={getCustomEventTrClassNamePersonal('user', index > 3, user.userId === currentUserId)}
                    >
                      <td className={tableDataCellClassNamePersonal(true)} />
                      <td className={tableDataCellClassNamePersonal()}>
                        <div style={{ maxWidth: 200 }} className="cb-custom-event-name mr-1">
                          {user.userName}
                        </div>
                      </td>
                      <td className={tableDataCellClassNamePersonal()}>
                        {user.totalScore || 0}
                      </td>
                      <td className={tableDataCellClassNamePersonal()}>
                        {user.winsCount || 0}
                      </td>
                      <td className={tableDataCellClassNamePersonal()}>
                        {user.totalDurationSec || 0}
                      </td>
                    </tr>
                  </React.Fragment>
                ))}
              </React.Fragment>
            ))}
          </tbody>
        </table>
      </div>
    );
  }
  return (
    <>
      <div className="d-flex flex-column">
        <div className="d-flex w-100 justify-content-starts border-bottom border-dark pb-2">
          <span className="font-weight-bold">{i18next.t('Event rating')}</span>
        </div>
        <div className="d-flex flex-column w-100 mt-3 cb-custom-event-common-leaderboard-bg rounded-lg">
          <nav className="pb-2">
            <div id="nav-tab" className={navTabsClassName} role="tablist">
              <button
                type="button"
                id="clan-tab"
                className={getTabLinkClassName(type === commonRatingTypes.clan)}
                role="tab"
                data-tab-name="clan"
                onClick={handleClick}
              >
                {i18next.t('Clans rating')}
              </button>
              <button
                type="button"
                id="player-tab"
                className={getTabLinkClassName(
                  type === commonRatingTypes.player,
                )}
                role="tab"
                data-tab-name="player"
                onClick={handleClick}
              >
                {i18next.t('Players rating')}
              </button>
              <button
                type="button"
                id="clan-player-tab"
                className={getTabLinkClassName(
                  type === commonRatingTypes.playerClan,
                )}
                role="tab"
                data-tab-name="player_clan"
                onClick={handleClick}
              >
                {i18next.t('Clan players rating')}
              </button>
            </div>
          </nav>
          <div className="px-3 cb-overflow-x-auto">
            <table className="table table-striped cb-custom-event-table mt-3">
              <thead className="text-muted">
                <tr>
                  <th className="p-1 pl-4 font-weight-light border-0">
                    {i18next.t('Place')}
                  </th>
                  <th className="p-1 pl-4 font-weight-light border-0">
                    {i18next.t('Score')}
                  </th>
                  {type === commonRatingTypes.clan && (
                    <th className="p-1 pl-4 font-weight-light border-0">
                      {i18next.t('Clan players_count/registrations')}
                    </th>
                  )}
                  <th className="p-1 pl-4 font-weight-light border-0">
                    {i18next.t('Clan')}
                  </th>
                  {type !== commonRatingTypes.clan && (
                    <th className="p-1 pl-4 font-weight-light border-0">
                      {i18next.t('Login')}
                    </th>
                  )}
                </tr>
              </thead>
              <tbody>
                {items.map(item => (
                  <React.Fragment key={`${type}${item.clanId}${item.userId}`}>
                    <tr className="cb-custom-event-empty-space-tr" />
                    <tr className={getCustomEventTrClassName(item, selectedId)}>
                      <td width="110" className={tableDataCellClassName}>
                        {item.place || '-'}
                      </td>
                      <td width="120" className={tableDataCellClassName}>
                        {item.score || '-'}
                      </td>
                      {item.eventPlayersCount !== undefined && (
                        <td className={tableDataCellClassName}>
                          {item.eventPlayersCount !== null
                            ? `${item.eventPlayersCount}/${item.clansPlayersCount}`
                            : item.clansPlayersCount}
                        </td>
                      )}
                      <td
                        title={item.clanLongName}
                        className={tableDataCellClassName}
                      >
                        <div
                          className="cb-custom-event-name"
                          style={{ maxWidth: 220 }}
                        >
                          {item.clanName}
                        </div>
                      </td>
                      {item.userName && (
                        <td
                          title={item.userName}
                          className={tableDataCellClassName}
                        >
                          <div
                            className="cb-custom-event-name"
                            style={{ maxWidth: 220 }}
                          >
                            {item.userName}
                          </div>
                        </td>
                      )}
                    </tr>
                  </React.Fragment>
                ))}
              </tbody>
            </table>
          </div>
          <div className="d-flex justify-content-between mr-1 px-2">
            <div className="pl-2">
              <span>
                {i18next.t('Total entries: %{totalEntries}', { totalEntries })}
              </span>
            </div>
            <LeaderboardPagination
              pageInfo={{ pageNumber, pageSize, totalEntries }}
              setPage={setPage}
            />
          </div>
        </div>
      </div>
    </>
  );
};

export default EventRatingPanel;