--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Modal, Row } from 'react-bootstrap';
+import { withTranslation } from 'react-i18next';
+
+import Box from '../users/Box';
+import { getTime } from '../../helpers/Result';
+import { findResult } from '../../helpers/Participant';
+import { maySeeResults } from '../../helpers/permissions';
+import { withUser } from '../../helpers/UserContext';
+import i18n from '../../i18n';
+
+const getPlacement = result =>
+ `${result.placement}. (${i18n.t('results.points', { count: result.score })})`;
+
+const DetailDialog = ({
+ onHide,
+ participant,
+ round,
+ show,
+ tournament,
+ user,
+}) => {
+ const result = findResult(participant, round);
+ const maySee = maySeeResults(user, tournament, round);
+ return <Modal className="result-dialog" onHide={onHide} show={show}>
+ <Modal.Header closeButton>
+ <Modal.Title>
+ {i18n.t('results.details')}
+ </Modal.Title>
+ </Modal.Header>
+ <Modal.Body>
+ <Row>
+ <Form.Group as={Col} sm={6}>
+ <Form.Label>{i18n.t('results.round')}</Form.Label>
+ <div>
+ #{round.number || '?'}
+ {' '}
+ {i18n.t('rounds.date', { date: new Date(round.created_at) })}
+ </div>
+ </Form.Group>
+ <Form.Group as={Col} sm={6}>
+ <Form.Label>{i18n.t('results.runner')}</Form.Label>
+ <div><Box user={participant.user} /></div>
+ </Form.Group>
+ <Form.Group as={Col} sm={6}>
+ <Form.Label>{i18n.t('results.result')}</Form.Label>
+ <div>
+ {maySee && result && result.has_finished
+ ? getTime(result, maySee)
+ : i18n.t('results.pending')}
+ </div>
+ </Form.Group>
+ <Form.Group as={Col} sm={6}>
+ <Form.Label>{i18n.t('results.placement')}</Form.Label>
+ <div>
+ {maySee && result && result.placement
+ ? getPlacement(result)
+ : i18n.t('results.pending')}
+ </div>
+ </Form.Group>
+ {maySee && result && result.comment ?
+ <Form.Group as={Col} sm={12}>
+ <Form.Label>{i18n.t('results.comment')}</Form.Label>
+ <div>{result.comment}</div>
+ </Form.Group>
+ : null}
+ </Row>
+ </Modal.Body>
+ <Modal.Footer>
+ <Button onClick={onHide} variant="secondary">
+ {i18n.t('button.close')}
+ </Button>
+ </Modal.Footer>
+ </Modal>;
+};
+
+DetailDialog.propTypes = {
+ onHide: PropTypes.func,
+ participant: PropTypes.shape({
+ user: PropTypes.shape({
+ }),
+ }),
+ round: PropTypes.shape({
+ created_at: PropTypes.string,
+ number: PropTypes.number,
+ }),
+ show: PropTypes.bool,
+ tournament: PropTypes.shape({
+ }),
+ user: PropTypes.shape({
+ }),
+};
+
+export default withTranslation()(withUser(DetailDialog));
import PropTypes from 'prop-types';
-import React from 'react';
-import { withTranslation } from 'react-i18next';
+import React, { useState } from 'react';
+import { Button } from 'react-bootstrap';
-import Icon from '../common/Icon';
+import DetailDialog from './DetailDialog';
import Box from '../users/Box';
-import { formatTime } from '../../helpers/Result';
+import { getIcon, getTime } from '../../helpers/Result';
import { findResult } from '../../helpers/Participant';
import { maySeeResults } from '../../helpers/permissions';
import { withUser } from '../../helpers/UserContext';
-const getIcon = (result, maySee) => {
- if (!result || !result.has_finished) {
- return <Icon.PENDING className="text-muted" size="lg" />;
- }
- if (result.forfeit && maySee) {
- return <Icon.FORFEIT className="text-danger" size="lg" />;
- }
- if (result.placement === 1 && maySee) {
- return <Icon.FIRST_PLACE className="text-gold" size="lg" />;
- }
- if (result.placement === 2 && maySee) {
- return <Icon.SECOND_PLACE className="text-silver" size="lg" />;
- }
- if (result.placement === 3 && maySee) {
- return <Icon.THIRD_PLACE className="text-bronze" size="lg" />;
- }
- return <Icon.FINISHED className="text-success" size="lg" />;
-};
-
-const getTime = (result, maySee) => {
- if (!result || !maySee) {
- return null;
- }
- if (result.time) {
- return formatTime(result);
- }
- if (result.forfeit) {
- return 'DNF';
- }
- return '?';
-};
-
const getClassName = result => {
const classNames = ['status'];
if (result && result.has_finished) {
tournament,
user,
}) => {
+ const [showDialog, setShowDialog] = useState(false);
const result = findResult(participant, round);
const maySee = maySeeResults(user, tournament, round);
return <div className="result">
<Box user={participant.user} />
- <div
+ <Button
className={getClassName(result)}
+ onClick={() => setShowDialog(true)}
title={maySee && result && result.comment ? result.comment : null}
>
<span className="time">
{getTime(result, maySee)}
</span>
{getIcon(result, maySee)}
- </div>
+ </Button>
+ <DetailDialog
+ onHide={() => setShowDialog(false)}
+ participant={participant}
+ round={round}
+ show={showDialog}
+ tournament={tournament}
+ />
</div>;
};
}),
};
-export default withTranslation()(withUser(Item));
+export default withUser(Item);
+import React from 'react';
+import Icon from '../components/common/Icon';
+
export const formatTime = result => {
const hours = `${Math.floor(result.time / 60 / 60)}`;
let minutes = `${Math.floor((result.time / 60) % 60)}`;
return `${hours}:${minutes}:${seconds}`;
};
+export const getIcon = (result, maySee) => {
+ if (!result || !result.has_finished) {
+ return <Icon.PENDING className="text-muted" size="lg" />;
+ }
+ if (result.forfeit && maySee) {
+ return <Icon.FORFEIT className="text-danger" size="lg" />;
+ }
+ if (result.placement === 1 && maySee) {
+ return <Icon.FIRST_PLACE className="text-gold" size="lg" />;
+ }
+ if (result.placement === 2 && maySee) {
+ return <Icon.SECOND_PLACE className="text-silver" size="lg" />;
+ }
+ if (result.placement === 3 && maySee) {
+ return <Icon.THIRD_PLACE className="text-bronze" size="lg" />;
+ }
+ return <Icon.FINISHED className="text-success" size="lg" />;
+};
+
+export const getTime = (result, maySee) => {
+ if (!result || !maySee) {
+ return null;
+ }
+ if (result.time) {
+ return formatTime(result);
+ }
+ if (result.forfeit) {
+ return 'DNF';
+ }
+ return '?';
+};
+
export const parseTime = str => {
if (!str) return null;
return `${str}`.split(/[-.: ]+/).reduce((acc,time) => (60 * acc) + +time, 0);
export default {
formatTime,
+ getIcon,
+ getTime,
parseTime,
};
results: {
addComment: 'Kommentieren',
comment: 'Kommentar',
+ details: 'Details',
edit: 'Ergebnis ändern',
editComment: 'Kommentar ändern',
forfeit: 'Aufgegeben',
+ pending: 'Ausstehend',
+ placement: 'Platzierung',
+ points_one: '{{ count }} Punkt',
+ points_other: '{{ count }} Punkte',
report: 'Ergebnis eintragen',
reportError: 'Fehler beim Eintragen :(',
reportPreview: 'Wird als {{ time }} festgehalten',
reportSuccess: 'Festgehalten',
reportTime: 'Zeit',
+ result: 'Ergebnis',
+ round: 'Runde',
+ runner: 'Runner',
time: 'Zeit: {{ time }}',
},
rounds: {
results: {
addComment: 'Comment',
comment: 'Comment',
+ details: 'Details',
edit: 'Change result',
editComment: 'Edit comment',
forfeit: 'Forfeit',
+ pending: 'Pending',
+ placement: 'Placement',
+ points_one: '{{ count }} point',
+ points_other: '{{ count }} points',
report: 'Report result',
reportError: 'Error saving :(',
reportPreview: 'Will be recorded as {{ time }}',
reportSuccess: 'Stored, thanks :)',
reportTime: 'Time',
+ result: 'Result',
+ round: 'Round',
+ runner: 'Runner',
time: 'Time: {{ time }}',
},
rounds: {
.status {
display: flex;
+ position: relative;
align-items: center;
justify-content: space-between;
margin-top: 1ex;
padding: 0.5em;
+ width: 100%;
min-width: 15ex;
+ border: none;
border-radius: 1ex;
background: $dark;
color: $light;
+ box-shadow: none;
+ transition: top 0.15s ease-in-out;
&.has-comment {
box-shadow: 0 0.5ex 0 $info;
}
+ &:active {
+ box-shadow: none;
+ top: 0.5ex;
+ }
.time {
min-width: 9ex;