Skip to content

Commit

Permalink
Merge branch 'main' into fix/506-rule-goal-point
Browse files Browse the repository at this point in the history
  • Loading branch information
tufusa authored Oct 18, 2024
2 parents 8183a25 + 9af370a commit a70dc6b
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 176 deletions.
4 changes: 2 additions & 2 deletions packages/kcmsf/src/components/match/PointControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const PointControl = (props: Props) => (
<>
{props.rule.type === "single" && (
<PointSingle
initial={props.rule.initial}
value={props.team.point.state[props.rule.name]}
color={props.color}
onChange={(active) => {
if (props.rule.type !== "single") return; // type narrowing
Expand All @@ -35,7 +35,7 @@ export const PointControl = (props: Props) => (
)}
{props.rule.type === "countable" && (
<PointCountable
initial={props.rule.initial}
value={props.team.point.state[props.rule.name]}
color={props.color}
validate={props.rule.validate}
onChange={(count) => {
Expand Down
23 changes: 10 additions & 13 deletions packages/kcmsf/src/components/match/PointCountable.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ActionIcon, Group, MantineColor, Text } from "@mantine/core";
import { IconMinus, IconPlus } from "@tabler/icons-react";
import { useCallback, useState } from "react";
import { useCallback } from "react";

interface Props {
initial: number;
value: number;
color: MantineColor;
onChange: (count: number) => void;
validate: (count: number) => boolean;
Expand All @@ -12,25 +12,22 @@ interface Props {
}

export const PointCountable = (props: Props) => {
const [count, setCount] = useState(props.initial);
const decrementable = props.validate(count - 1);
const incrementable = props.validate(count + 1);
const { onChange } = props;
const { value, onChange } = props;
const decrementable = props.validate(value - 1);
const incrementable = props.validate(value + 1);

const decrement = useCallback(() => {
if (!decrementable) return; // これ以上減らせない

const nextCount = count - 1;
setCount(nextCount);
const nextCount = value - 1;
onChange(nextCount);
}, [count, decrementable, onChange]);
}, [value, decrementable, onChange]);
const increment = useCallback(() => {
if (!incrementable) return; // これ以上増やせない

const nextCount = count + 1;
setCount(nextCount);
const nextCount = value + 1;
onChange(nextCount);
}, [count, incrementable, onChange]);
}, [value, incrementable, onChange]);

return (
<Group>
Expand All @@ -48,7 +45,7 @@ export const PointCountable = (props: Props) => {
<IconMinus style={{ width: "100%", height: "100%" }} />
</ActionIcon>
<Text w="auto" size="xl" style={{ flexGrow: 1 }}>
{count}
{value}
</Text>
<ActionIcon
size="xl"
Expand Down
38 changes: 15 additions & 23 deletions packages/kcmsf/src/components/match/PointSingle.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
import { Button, MantineColor, Text } from "@mantine/core";
import { useState } from "react";

interface Props {
initial: boolean;
color: MantineColor;
onChange: (active: boolean) => void;
disabled: boolean;
children: React.ReactNode;
value: boolean;
}

export const PointSingle = (props: Props) => {
const [active, setActive] = useState(props.initial);

return (
<Button
w="auto"
h="auto"
px="lg"
py="xs"
variant={active ? "filled" : "outline"}
color={props.color}
onClick={() => {
props.onChange(!active);
setActive(() => !active);
}}
disabled={props.disabled}
>
<Text size="1.2rem">{props.children}</Text>
</Button>
);
};
export const PointSingle = (props: Props) => (
<Button
w="auto"
h="auto"
px="lg"
py="xs"
variant={props.value ? "filled" : "outline"}
color={props.color}
onClick={() => props.onChange(!props.value)}
disabled={props.disabled}
>
<Text size="1.2rem">{props.children}</Text>
</Button>
);
116 changes: 59 additions & 57 deletions packages/kcmsf/src/pages/match.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,24 @@
import { Button, Divider, Flex, Paper, Text } from "@mantine/core";
import {
config,
DepartmentType,
MatchInfo,
MatchType,
RobotType,
} from "config";
import { useEffect, useState } from "react";
import { IconRotate } from "@tabler/icons-react";
import { config, MatchInfo, MatchType } from "config";
import { Side } from "config/src/types/matchInfo";
import { useCallback, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { useTimer } from "react-timer-hook";
import { MatchSubmit } from "../components/match/matchSubmit";
import { PointControls } from "../components/match/PointControls";
import { useForceReload } from "../hooks/useForceReload";
import { GetMatchResponse } from "../types/api/match";
import { GetTeamResponse } from "../types/api/team";
import { MainMatch, PreMatch, Match as TMatch } from "../types/match";
import { Judge } from "../utils/match/judge";
import { expiryTimestamp, parseSeconds } from "../utils/time";

type TimerState = "initial" | "counting" | "finished";
type GetTeamResponse = {
id: string;
name: string;
entryCode: string;
members: string[];
clubName: string;
robotType: RobotType;
departmentType: DepartmentType;
isEntered: boolean;
};

type GetMatchResponseBase = {
id: string;
matchCode: string;
// TODO: RunResultの扱い
};

type BriefTeam = { id: string; teamName: string };

type GetPreMatchResponse = GetMatchResponseBase & {
leftTeam?: BriefTeam;
rightTeam?: BriefTeam;
};

type GetMainMatchResponse = GetMatchResponseBase & {
team1: BriefTeam;
team2: BriefTeam;
};
type GetMatchResponse = GetPreMatchResponse | GetMainMatchResponse;

type DiscriminatedGetMatchResponse =
| (GetPreMatchResponse & { matchType: "pre" })
| (GetMainMatchResponse & { matchType: "main" });
type DiscriminatedMatch =
| (PreMatch & { matchType: "pre" })
| (MainMatch & { matchType: "main" });

export const Match = () => {
const { id, matchType } = useParams<{ id: string; matchType: MatchType }>();
Expand All @@ -63,8 +33,8 @@ export const Match = () => {

const isMainMatch = (
matchType: MatchType,
_matchResponse: GetPreMatchResponse | GetMainMatchResponse
): _matchResponse is GetMainMatchResponse => matchType === "main";
_matchResponse: TMatch
): _matchResponse is MainMatch => matchType === "main";

const getTeam = async (teamID: string): Promise<GetTeamResponse> => {
const res = await fetch(
Expand All @@ -82,10 +52,7 @@ export const Match = () => {
if (!res.ok) return;

const matchData = (await res.json()) as GetMatchResponse;
const match: DiscriminatedGetMatchResponse = isMainMatch(
matchType,
matchData
)
const match: DiscriminatedMatch = isMainMatch(matchType, matchData)
? { ...matchData, matchType: "main" }
: { ...matchData, matchType: "pre" };

Expand Down Expand Up @@ -149,9 +116,32 @@ export const Match = () => {
}
};

const resetPointState = useCallback(
(side: Side) => {
matchJudge.team(side).reset();
forceReload();
},
[matchJudge, forceReload]
);

return (
<Flex h="100%" direction="column" gap="md" align="center" justify="center">
<Text size="2rem">{matchCode}</Text>
{matchInfo && (
<Paper w="100%" p="xs" withBorder>
<Flex direction="row" align="center" justify="center">
<Text size="2rem" c="blue" flex={1}>
{matchInfo?.teams.left?.teamName}
</Text>
<Flex direction="column" align="center" justify="center" c="dark">
{config.match[matchInfo?.matchType].name}
<Text size="2rem">#{matchCode}</Text>
</Flex>
<Text size="2rem" c="red" flex={1}>
{matchInfo?.teams.right?.teamName}
</Text>
</Flex>
</Paper>
)}
<Button
w="100%"
h="auto"
Expand All @@ -164,11 +154,17 @@ export const Match = () => {
</Button>
<Paper w="100%" withBorder>
<Flex align="center" justify="center">
{!isExhibition && matchInfo && (
<Text pl="md" size="2rem" c="blue" style={{ flex: 1 }}>
{matchInfo.teams.left?.teamName}
</Text>
)}
<Button
flex={1}
variant="transparent"
c="blue"
leftSection={<IconRotate />}
size="xl"
fw="normal"
onClick={() => resetPointState("left")}
>
リセット
</Button>
<Flex pb="sm" gap="sm">
<Text size="4rem" c="blue">
{isExhibition || matchInfo?.teams.left
Expand All @@ -182,11 +178,17 @@ export const Match = () => {
: 0}
</Text>
</Flex>
{!isExhibition && matchInfo && (
<Text pr="md" size="2rem" c="red" style={{ flex: 1 }}>
{matchInfo.teams.right?.teamName}
</Text>
)}
<Button
flex={1}
variant="transparent"
c="red"
leftSection={<IconRotate />}
size="xl"
fw="normal"
onClick={() => resetPointState("right")}
>
リセット
</Button>
</Flex>
</Paper>
<Divider w="100%" />
Expand Down
11 changes: 2 additions & 9 deletions packages/kcmsf/src/pages/register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@ import { Box, Button, Group, SegmentedControl, TextInput } from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { config, DepartmentType, RobotType } from "config";
import { useState } from "react";

interface CreateTeamRequestBody {
name: string;
members: string[];
clubName: string;
robotType: RobotType;
departmentType: DepartmentType;
}
import { CreateTeamArgs } from "../types/team";

export const Register = () => {
const [teamName, setTeamName] = useState("");
Expand All @@ -26,7 +19,7 @@ export const Register = () => {
event.preventDefault();
// メンバーは、オープン部門または小学生部門かつメンバーが1人の場合は配列の要素数を1つにする
// 2024/1/5 仕様変更に伴いメンバーは入力せずに登録できるようにする
const data: CreateTeamRequestBody = {
const data: CreateTeamArgs = {
name: teamName,
members: member.filter((v) => v !== ""),
clubName: clubName,
Expand Down
Loading

0 comments on commit a70dc6b

Please sign in to comment.