Skip to content
Open
45 changes: 45 additions & 0 deletions src/components/RoundActionButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { hasDistributedAttempts, parseActivityCode } from '../lib/domain/activities';
import { type ActivityWithParent } from '../lib/domain/types';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import { type Person } from '@wca/helpers';

Expand All @@ -9,11 +11,14 @@ interface RoundActionButtonsProps {
activityCode: string;
onConfigureAssignments: () => void;
onGenerateAssignments: () => void;
onAssignToRoundAttempt: () => void;
onResetAttemptAssignments: () => void;
onConfigureStationNumbers: (activityCode: string) => void;
onConfigureGroups: () => void;
onResetAll: () => void;
onResetNonScrambling: () => void;
onConfigureGroupCounts: () => void;
isDistributedAttemptRoundLevel: boolean;
}

export const RoundActionButtons = ({
Expand All @@ -23,12 +28,52 @@ export const RoundActionButtons = ({
activityCode,
onConfigureAssignments,
onGenerateAssignments,
onAssignToRoundAttempt,
onResetAttemptAssignments,
onConfigureStationNumbers,
onConfigureGroups,
onResetAll,
onResetNonScrambling,
onConfigureGroupCounts,
isDistributedAttemptRoundLevel,
}: RoundActionButtonsProps) => {
const { attemptNumber } = parseActivityCode(activityCode);
const isAttemptActivity = hasDistributedAttempts(activityCode) && attemptNumber !== undefined;

if (isDistributedAttemptRoundLevel) {
return (
<>
<Button onClick={onConfigureAssignments}>Configure Round Attempt Assignments</Button>
<Button onClick={onAssignToRoundAttempt}>Assign All</Button>
<Box sx={{ display: 'flex', flex: 1 }} />
<Button color="error" onClick={onResetAttemptAssignments}>
Clear Round Attempt Assignments
</Button>
</>
);
}

if (groups.length === 0 && isAttemptActivity) {
if (personsAssignedToCompete.length > 0) {
return (
<>
<Button onClick={onAssignToRoundAttempt}>Assign to Round Attempt</Button>
<Box sx={{ display: 'flex', flex: 1 }} />
<Button color="error" onClick={onResetAttemptAssignments}>
Reset Attempt Assignments
</Button>
</>
);
}

return (
<>
<Button onClick={onAssignToRoundAttempt}>Assign to Round Attempt</Button>
<Button onClick={onConfigureGroupCounts}>Configure Group Counts</Button>
</>
);
}

if (groups.length === 0) {
return (
<>
Expand Down
10 changes: 9 additions & 1 deletion src/components/RoundSelector/RoundListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,17 @@ interface RoundListItemProps {
activityCode: string;
round: Round;
selected: boolean;
nestingLevel?: number;
in?: boolean;
}

function RoundListItem({ activityCode, round, selected, ...props }: RoundListItemProps) {
function RoundListItem({
activityCode,
round,
selected,
nestingLevel = 0,
...props
}: RoundListItemProps) {
const ref = useRef<HTMLAnchorElement>(null);
const wcif = useAppSelector((state) => state.wcif);
const realGroups = wcif ? findGroupActivitiesByRound(wcif, activityCode) : [];
Expand Down Expand Up @@ -75,6 +82,7 @@ function RoundListItem({ activityCode, round, selected, ...props }: RoundListIte
component={RouterLink}
to={`/competitions/${wcif?.id}/events/${activityCode}`}
selected={selected}
sx={{ pl: 2 + nestingLevel * 4 }}
ref={ref}>
<ListItemAvatar>
<span className={`cubing-icon event-${eventId}`} />
Expand Down
44 changes: 42 additions & 2 deletions src/components/RoundSelector/_tests_/RoundSelector.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
}));

vi.mock('../../../lib/domain/activities', async () => {
const actual = await vi.importActual<typeof import('../../../lib/domain/activities')>(

Check warning on line 18 in src/components/RoundSelector/_tests_/RoundSelector.test.tsx

View workflow job for this annotation

GitHub Actions / lint

`import()` type annotations are forbidden
'../../../lib/domain/activities'
);

Expand All @@ -26,8 +26,21 @@
});

vi.mock('../RoundListItem', () => ({
default: ({ activityCode, selected }: { activityCode: string; selected: boolean }) => (
<div data-testid="round-item" data-selected={selected} data-code={activityCode} />
default: ({
activityCode,
selected,
nestingLevel,
}: {
activityCode: string;
selected: boolean;
nestingLevel?: number;
}) => (
<div
data-testid="round-item"
data-selected={selected}
data-code={activityCode}
data-nesting={nestingLevel ?? 0}
/>
),
}));

Expand Down Expand Up @@ -85,4 +98,31 @@

expect(toggle).toBeChecked();
});

it('renders round-level entry plus attempts for distributed-attempt events', () => {
const wcif = {
id: 'TestComp',
events: [
{
id: '333fm',
rounds: [{ id: '333fm-r1', format: '3', results: [], timeLimit: null, cutoff: null }],
},
],
};

const state = { wcif } as unknown as AppState;
useAppSelector.mockImplementation((selector: (state: AppState) => unknown) => selector(state));

const { getAllByTestId } = renderWithProviders(
<RoundSelector competitionId="TestComp" onSelected={() => undefined} />
);

const activityCodes = getAllByTestId('round-item').map((item) => item.getAttribute('data-code'));
const nestingLevels = getAllByTestId('round-item').map((item) =>
item.getAttribute('data-nesting')
);

expect(activityCodes).toEqual(['333fm-r1', '333fm-r1-a1', '333fm-r1-a2', '333fm-r1-a3']);
expect(nestingLevels).toEqual(['0', '1', '1', '1']);
});
});
57 changes: 39 additions & 18 deletions src/components/RoundSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const RoundSelector = ({ onSelected }: RoundSelectorProps) => {
const [showAllRounds, setShowAllRounds] = useState(false);
const [selectedId, setSelectedId] = useState(wcif?.events[0]?.rounds[0]?.id || null);

const attemptCountForRound = (round: Round) => (round.format === 'm' ? 3 : +round.format);

const shouldShowRound = (round: Round) => {
if (!wcif) return false;

Expand Down Expand Up @@ -54,13 +56,18 @@ const RoundSelector = ({ onSelected }: RoundSelectorProps) => {
.filter(shouldShowRound)
: [];

const roundIds = rounds.flatMap((r) =>
hasDistributedAttempts(r.id)
? new Array(r.format === 'm' ? 3 : +r.format)
.fill(0)
.map((_, index) => `${r.id}-a${index + 1}`)
: r.id
);
const roundIds = rounds.flatMap((r) => {
if (!hasDistributedAttempts(r.id)) {
return r.id;
}

return [
r.id,
...new Array(attemptCountForRound(r))
.fill(0)
.map((_, index) => `${r.id}-a${index + 1}`),
];
});

const handleKeyDown = (e: KeyboardEvent) => {
if (commandPromptOpen) {
Expand Down Expand Up @@ -124,31 +131,45 @@ const RoundSelector = ({ onSelected }: RoundSelectorProps) => {
<TransitionGroup>
{!hasDistributedAttempts(event.id)
? roundsForEvent.map((round) => (
<RoundListItem
key={round.id}
activityCode={round.id}
round={round}
selected={round.id === selectedId}
in
/>
<RoundListItem
key={round.id}
activityCode={round.id}
round={round}
nestingLevel={0}
selected={round.id === selectedId}
in
/>
))
: roundsForEvent.flatMap((round) => {
const attempts = new Array(round.format === 'm' ? 3 : +round.format) // TODO: create helper function to calculate attempts
.fill(0);

return attempts.map((_, index) => {
const attempts = new Array(attemptCountForRound(round)).fill(0);

const roundListItem = (
<RoundListItem
key={round.id}
activityCode={round.id}
round={round}
nestingLevel={0}
selected={round.id === selectedId}
in
/>
);

const attemptListItems = attempts.map((_, index) => {
const attemptActivityCode = `${round.id}-a${index + 1}`;

return (
<RoundListItem
key={attemptActivityCode}
activityCode={attemptActivityCode}
round={round}
nestingLevel={1}
selected={attemptActivityCode === selectedId}
in
/>
);
});

return [roundListItem, ...attemptListItems];
})}
</TransitionGroup>
</React.Fragment>
Expand Down
32 changes: 32 additions & 0 deletions src/components/_tests_/RoundActionButtons.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { RoundActionButtons } from '../RoundActionButtons';
import { renderWithProviders } from '../../test-utils';
import { describe, expect, it, vi } from 'vitest';

const baseProps = {
groups: [],
personsAssignedToCompete: [],
personsShouldBeInRound: [],
activityCode: '333fm-r1',
onConfigureAssignments: vi.fn(),
onGenerateAssignments: vi.fn(),
onAssignToRoundAttempt: vi.fn(),
onResetAttemptAssignments: vi.fn(),
onConfigureStationNumbers: vi.fn(),
onConfigureGroups: vi.fn(),
onResetAll: vi.fn(),
onResetNonScrambling: vi.fn(),
onConfigureGroupCounts: vi.fn(),
};

describe('RoundActionButtons', () => {
it('shows distributed round-level attempt actions', () => {
const { getByText, queryByText } = renderWithProviders(
<RoundActionButtons {...baseProps} isDistributedAttemptRoundLevel />
);

expect(getByText('Configure Round Attempt Assignments')).toBeInTheDocument();
expect(getByText('Assign All')).toBeInTheDocument();
expect(getByText('Clear Round Attempt Assignments')).toBeInTheDocument();
expect(queryByText('Configure Group Counts')).not.toBeInTheDocument();
});
});
Loading
Loading