From 94b6fcb2f363e0afa5959faeb308aa3b3b34c8cc Mon Sep 17 00:00:00 2001 From: testusuke Date: Sun, 19 May 2024 19:11:08 +0900 Subject: [PATCH 1/7] =?UTF-8?q?change:=20=E5=AF=A9=E5=88=A4=E3=83=81?= =?UTF-8?q?=E3=83=BC=E3=83=A0=E3=82=92id=E5=BD=A2=E5=BC=8F=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/models/MatchModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/MatchModel.ts b/src/models/MatchModel.ts index 8071c86..b950ca0 100644 --- a/src/models/MatchModel.ts +++ b/src/models/MatchModel.ts @@ -13,7 +13,7 @@ export type Match = { result: MatchResult, status: MatchStatus, note: string | null, - judgeTeamId: string | null, + judgeTeamId: number | null, parents: number[], children: number[], createdAt: string, From 212453c2b45bbec81ec8d39ce8502fc0cbd2986d Mon Sep 17 00:00:00 2001 From: testusuke Date: Sun, 19 May 2024 19:11:22 +0900 Subject: [PATCH 2/7] =?UTF-8?q?refactor:=20=E3=83=9E=E3=83=83=E3=83=81?= =?UTF-8?q?=E7=B7=A8=E9=9B=86=E3=81=AE=E6=9C=80=E9=81=A9=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/match/matchEditor.tsx | 744 ++++++++++++++++--------------- 1 file changed, 387 insertions(+), 357 deletions(-) diff --git a/components/match/matchEditor.tsx b/components/match/matchEditor.tsx index daa9197..34a490e 100644 --- a/components/match/matchEditor.tsx +++ b/components/match/matchEditor.tsx @@ -1,7 +1,7 @@ 'use client' -import React, {useRef} from "react"; -import {useAsync} from "react-use"; -import {useRouter} from "next/navigation"; +import React, {useRef, useState} from "react" +import {useAsync} from "react-use" +import {useRouter} from "next/navigation" import { Accordion, AccordionDetails, @@ -19,17 +19,17 @@ import { Typography, ToggleButton, ToggleButtonGroup, Chip, SvgIcon, Avatar -} from "@mui/material"; -import CardBackground from "@/components/layout/cardBackground"; -import {HiCheck, HiChevronDown, HiArrowPath, HiFlag, HiMapPin, HiClock} from "react-icons/hi2"; - -import {Sport} from "@/src/models/SportModel"; -import {Game} from "@/src/models/GameModel"; -import {Match, matchFactory, MatchResult, MatchStatus} from "@/src/models/MatchModel"; -import {Team, teamFactory} from "@/src/models/TeamModel"; -import {Location, locationFactory} from "@/src/models/LocationModel"; -import Loading from "@/app/(authenticated)/loading"; -import Link from "next/link"; +} from "@mui/material" +import CardBackground from "@/components/layout/cardBackground" +import {HiCheck, HiChevronDown, HiArrowPath, HiFlag, HiMapPin, HiClock, HiMiniNoSymbol} from "react-icons/hi2" + +import {Sport} from "@/src/models/SportModel" +import {Game} from "@/src/models/GameModel" +import {Match, matchFactory, MatchResult, MatchStatus} from "@/src/models/MatchModel" +import {Team, teamFactory} from "@/src/models/TeamModel" +import {Location, locationFactory} from "@/src/models/LocationModel" +import Loading from "@/app/(authenticated)/loading" +import Link from "next/link" export type MatchEditorProps = { sport: Sport @@ -39,98 +39,92 @@ export type MatchEditorProps = { export default function MatchEditor(props: MatchEditorProps) { const router = useRouter() - const useState = React.useState; - - const teamUndefined = { - id: 0, - name: "取得中", - description: "取得中", - classId: 0, - teamTagId: 0, - userIds: [], - enteredGameIds: [], - createdAt: "0", - updatedAt: "0" - }; - - // state + // loading state const [isFetching, setIsFetching] = useState(true) - const [leftTeam, setLeftTeam] = useState(teamUndefined); - const [rightTeam, setRightTeam] = useState(teamUndefined); - const [judgeTeam, setJudgeTeam] = useState(teamUndefined); - const [matchResult, setMatchResult] = useState(props.match.result); - const [matchStatus, setMatchStatus] = useState(props.match.status); - const [locations, setLocations] = useState([]); - const [locationId, setLocationId] = useState(props.match.locationId); - const [teams, setTeams] = useState([]); - const [judgeTeamId, setJudgeTeamId] = useState(props.match.judgeTeamId); - const [updateSnackOpen, setUpdateSnackOpen] = React.useState(false) - const [cancelSnackOpen, setCancelSnackOpen] = React.useState(false) - const [scoreError, setScoreError] = useState(false); - const leftRef = useRef(null) - const rightRef = useRef(null) + // index + const [teams, setTeams] = useState([]) + const [locations, setLocations] = useState([]) + // match result state + const [matchResult, setMatchResult] = useState(props.match.result) + const [matchStatus, setMatchStatus] = useState(props.match.status) + // location and team state + const [locationId, setLocationId] = useState(props.match.locationId) + const [judgeTeamId, setJudgeTeamId] = useState(props.match.judgeTeamId) + // UX state + const [updateSnackOpen, setUpdateSnackOpen] = useState(false) + const [cancelSnackOpen, setCancelSnackOpen] = useState(false) + const [scoreError, setScoreError] = useState(false) + const [matchStateError, setMatchStateError] = useState(false) + const leftScoreRef = useRef(null) + const rightScoreRef = useRef(null) const noteRef = useRef(null) // fetch data useAsync(async () => { - const fetchMatchEditor = async () => { - const left = await teamFactory().show(Number(props.match.leftTeamId)); - const right = await teamFactory().show(Number(props.match.rightTeamId)); - const judge = await teamFactory().show(Number(props.match.judgeTeamId)); - const location = await locationFactory().index(); - const teams = await teamFactory().index(); - setLeftTeam(left); - setRightTeam(right); - setJudgeTeam(judge); - setLocations(location); - setTeams(teams); - setIsFetching(false); - }; - - return fetchMatchEditor(); + const fetchedLocations = await locationFactory().index() + setLocations(fetchedLocations) + + const fetchedTeams = await teamFactory().index() + // filter by game id + const filteredTeams = fetchedTeams.filter(team => team.enteredGameIds.includes(props.game.id)) + setTeams(filteredTeams) + + // finish loading + setIsFetching(false) }) // reformat data - const locationName = locations.find(location => location.id === props.match.locationId)?.name; - const date = new Date(props.match.startAt); - const formattedDate = `${date.getMonth() + 1}月${date.getDate()}日 ${date.getHours()}時${date.getMinutes() < 10 ? '0' : ''}${date.getMinutes()}分`; + const locationName = locations.find(location => location.id === props.match.locationId)?.name + const date = new Date(props.match.startAt) + const formattedDate = `${date.getMonth() + 1}月${date.getDate()}日 ${date.getHours()}時${date.getMinutes() < 10 ? '0' : ''}${date.getMinutes()}分` - // handler + // snack bar close handler const handleUpdateSnackClose = () => { setUpdateSnackOpen(false) } + // snack bar close handler const handleCancelSnackClose = () => { setCancelSnackOpen(false) } + + // match result change handler const handleResultChange = ( - event: React.MouseEvent, + _: React.MouseEvent, newResult: string, ) => { - setMatchResult(newResult as MatchResult); - }; + setMatchResult(newResult as MatchResult) + } + + // match status change handler const handleStatusChange = ( - event: React.MouseEvent, + _: React.MouseEvent, newStatus: string, ) => { - setMatchStatus(newStatus as MatchStatus); - }; + setMatchStatus(newStatus as MatchStatus) + } + + const handleMatchStateErrorClose = () => { + setMatchStateError(false) + } + + // auto compare const handleCompare = () => { - const leftScore = Number(leftRef.current?.value); - const rightScore = Number(rightRef.current?.value); + const leftScore = Number(leftScoreRef.current?.value) + const rightScore = Number(rightScoreRef.current?.value) if (leftScore !== 0 || rightScore !== 0) { - setMatchStatus('finished'); + setMatchStatus('finished') } else { - setMatchStatus('standby'); + setMatchStatus('standby') } if (leftScore > rightScore) { - setMatchResult('left_win'); + setMatchResult('left_win') } else if (leftScore < rightScore) { - setMatchResult('right_win'); + setMatchResult('right_win') } else { - setMatchResult('draw'); + setMatchResult('draw') } setScoreError(false) } @@ -142,23 +136,28 @@ export default function MatchEditor(props: MatchEditorProps) { setLocationId(props.match.locationId) setJudgeTeamId(props.match.judgeTeamId) noteRef.current!.value = props.match.note - leftRef.current!.value = props.match.leftScore - rightRef.current!.value = props.match.rightScore + leftScoreRef.current!.value = props.match.leftScore + rightScoreRef.current!.value = props.match.rightScore setCancelSnackOpen(true) } // update data const handleUpdate = async () => { - const leftScoreValue = leftRef.current?.value; - const rightScoreValue = rightRef.current?.value; + const leftScoreValue = leftScoreRef.current?.value + const rightScoreValue = rightScoreRef.current?.value if (!leftScoreValue || !rightScoreValue || isNaN(Number(leftScoreValue)) || isNaN(Number(rightScoreValue || Number(leftScoreValue) < 0 || Number(rightScoreValue) < 0))) { - setScoreError(true); - return; + setScoreError(true) + return } - const leftScore = Number(leftScoreValue); - const rightScore = Number(rightScoreValue); + const leftScore = Number(leftScoreValue) + const rightScore = Number(rightScoreValue) + + if (!matchResult || !matchStatus) { + setMatchStateError(true) + return + } await matchFactory().update(props.match.id, { locationId: locationId, @@ -178,322 +177,353 @@ export default function MatchEditor(props: MatchEditorProps) { router.refresh() setUpdateSnackOpen(true) setScoreError(false) + setMatchStateError(false) } if (isFetching) { return ( ) - } else { - return ( - - - - - - - } color={"secondary"} - /> - } color={"secondary"} + } + + const leftTeamName = teams.find(team => team.id === props.match.leftTeamId)?.name ?? "未登録" + const rightTeamName = teams.find(team => team.id === props.match.rightTeamId)?.name ?? "未登録" + const judgeTeamName = teams.find(team => team.id === props.match.judgeTeamId)?.name ?? "未登録" + + return ( + + + + + + + } color={"secondary"} + /> + } color={"secondary"} + /> + } color={"secondary"} + /> + + + + + + + {leftTeamName}のスコア + - } color={"secondary"} + + + VS + + + {rightTeamName}のスコア + + - + - - + + 勝ったのは + - {leftTeam.name}のスコア - - - - VS - - {leftTeamName} + 引き分け + {rightTeamName} + + + + 試合の状態 + - {rightTeam.name}のスコア - - + 中止 + スタンバイ + 進行中 + 完了 + + - - - - - 勝ったのは - - {leftTeam.name} - 引き分け - {rightTeam.name} - - - - 試合の状態 - - 中止 - スタンバイ - 進行中 - 完了 - - - + + + + + + + + + + + } + aria-controls="panel-content" + id="panel-header" + > + 編集する + + + + + 補足 + + + 審判 + + 審判 + + + + 試合の場所 + + 場所 + + - - - - - - } - aria-controls="panel-content" - id="panel-header" - > - 編集する - - - + + + - 補足 - - - 審判 - - 審判 - - - - 試合の場所 - - 場所 - - - - - - - - - - - - + - - - - - - - 変更が保存されました - - - - - - + + + + + 変更が保存されました + + + + + + {/*Cancel Snack*/} + + - - - - - - - 変更を元に戻しました - - - - - - - ) - } -} \ No newline at end of file + + + + + + 変更を元に戻しました + + + + + + {/*Match State Error*/} + + + + + + + + 勝者、状態を選択してください。 + + + + + + ) +} From e60fd57332815645038cc26f3ab46f41081808d2 Mon Sep 17 00:00:00 2001 From: testusuke Date: Sun, 19 May 2024 21:16:05 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=E8=A9=A6=E5=90=88=E4=B8=80?= =?UTF-8?q?=E8=A6=A7=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=99=E3=82=8B=E3=82=B3?= =?UTF-8?q?=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cardList.tsx => match/matchCard.tsx} | 71 +++++++++++-------- components/match/matchList.tsx | 24 +++++++ 2 files changed, 66 insertions(+), 29 deletions(-) rename components/{layout/cardList.tsx => match/matchCard.tsx} (56%) create mode 100644 components/match/matchList.tsx diff --git a/components/layout/cardList.tsx b/components/match/matchCard.tsx similarity index 56% rename from components/layout/cardList.tsx rename to components/match/matchCard.tsx index 746b289..c7ab33e 100644 --- a/components/layout/cardList.tsx +++ b/components/match/matchCard.tsx @@ -1,22 +1,37 @@ import {Grid, Button, Avatar, Chip, Stack, Tooltip, Divider} from "@mui/material"; -import React, {ReactNode} from 'react'; import {HiClock, HiFlag, HiMapPin, HiTableCells, HiUserGroup} from "react-icons/hi2"; +import {Match} from "@/src/models/MatchModel"; +import {sportFactory} from "@/src/models/SportModel"; +import {gameFactory} from "@/src/models/GameModel"; +import {locationFactory} from "@/src/models/LocationModel"; +import {teamFactory} from "@/src/models/TeamModel"; -type CardProps = { - link?: string; - sport: string; - league: string; - judge: string; - left: string; - right: string; - time: string; - location: string; +type MatchCardProps = { + match: Match } -const CardList: React.FC = ({link, location, sport, league, judge, left, right, time}) => { +export default async function MatchCard(props: MatchCardProps) { + const sport = await sportFactory().show(props.match.sportId) + const game = await gameFactory().show(props.match.gameId) + const location = props.match.locationId == null ? undefined : await locationFactory().show(props.match.locationId) + const leftTeam = props.match.leftTeamId == null ? undefined : await teamFactory().show(props.match.leftTeamId) + const rightTeam = props.match.rightTeamId == null ? undefined : await teamFactory().show(props.match.rightTeamId) + const judgeTeam = props.match.judgeTeamId == null ? undefined : await teamFactory().show(props.match.judgeTeamId) + + const date = new Date(props.match.startAt) + const formattedDate = `${date.getHours()}時${date.getMinutes() < 10 ? '0' : ''}${date.getMinutes()}分` + return ( -