diff --git a/__mocks__/constants/matchRoundMock.ts b/__mocks__/constants/matchRoundMock.ts index 9778862..f151511 100644 --- a/__mocks__/constants/matchRoundMock.ts +++ b/__mocks__/constants/matchRoundMock.ts @@ -1,11 +1,11 @@ import { BracketContents } from '@type/bracket'; interface mockRoundData { - [key: string]: BracketContents; + [key: number]: BracketContents; } export const matchRoundMock: mockRoundData = { - '1': { + 1: { myGameId: 'NO_DATA', matchInfoDtoList: [ { @@ -439,7 +439,7 @@ export const matchRoundMock: mockRoundData = { }, ], }, - '2': { + 2: { myGameId: 'NO_DATA', matchInfoDtoList: [ { @@ -658,7 +658,7 @@ export const matchRoundMock: mockRoundData = { }, ], }, - '3': { + 3: { myGameId: 'NO_DATA', matchInfoDtoList: [ { @@ -773,7 +773,7 @@ export const matchRoundMock: mockRoundData = { }, ], }, - '4': { + 4: { myGameId: 'NO_DATA', matchInfoDtoList: [ { diff --git a/__mocks__/handlers/bracketHandlers.ts b/__mocks__/handlers/bracketHandlers.ts index 167ca02..0d06ce8 100644 --- a/__mocks__/handlers/bracketHandlers.ts +++ b/__mocks__/handlers/bracketHandlers.ts @@ -1,7 +1,13 @@ import { SERVER_URL } from '@config/index'; import { matchRoundMock } from '@mocks/constants/matchRoundMock'; +import { MatchCountList } from '@type/channelConfig'; import { rest } from 'msw'; +const channelRoundList: MatchCountList[] = [ + { + roundCountList: [2, 3, 4, 5], + }, +]; const bracketHandlers = [ rest.get(SERVER_URL + '/api/match/:channelLink', (req, res, ctx) => { const { channelLink } = req.params; @@ -9,12 +15,18 @@ const bracketHandlers = [ return res(ctx.json({ roundList: [1, 2, 3, 4], liveRound: 1 })); }), - rest.get(SERVER_URL + '/api/match/:channelLink/:matchRound', (req, res, ctx) => { + rest.get(SERVER_URL + '/api/match/:channelLink/:matchRound(\\d+)', (req, res, ctx) => { const { channelLink, matchRound } = req.params; - if (typeof matchRound === 'string') { - return res(ctx.json(matchRoundMock[matchRound])); - } + return res(ctx.json(matchRoundMock[Number(matchRound)])); + }), + + rest.get(SERVER_URL + '/api/match/:channelLink/count', (req, res, ctx) => { + return res(ctx.json(channelRoundList[0])); + }), + + rest.post(SERVER_URL + '/api/match/:channelLink/count', (req, res, ctx) => { + return res(ctx.json({ message: '채널 재설정 완료' })); }), ]; diff --git a/src/@types/channelConfig.ts b/src/@types/channelConfig.ts new file mode 100644 index 0000000..a9eb886 --- /dev/null +++ b/src/@types/channelConfig.ts @@ -0,0 +1,3 @@ +export interface MatchCountList { + roundCountList: number[]; +} diff --git a/src/@types/icon.ts b/src/@types/icon.ts index f072fee..4f8e383 100644 --- a/src/@types/icon.ts +++ b/src/@types/icon.ts @@ -10,4 +10,6 @@ export type IconKind = | 'clock' | 'refresh' | 'sendEmail' - | 'modify'; + | 'modify' + | 'setting' + | 'cancel'; diff --git a/src/components/Bracket/BracketContents.tsx b/src/components/Bracket/BracketContents.tsx index d8ec4f1..f25aac9 100644 --- a/src/components/Bracket/BracketContents.tsx +++ b/src/components/Bracket/BracketContents.tsx @@ -39,29 +39,33 @@ const BracketContents = (props: Props) => { return ( - R{match.matchRoundCount} + M{match.matchRoundCount} {match.matchName} 자세히 - {match.matchPlayerInfoList.map((user) => { - return ( - - - {user.profileSrc ? ( - - ) : ( - - {user.gameId.substring(0, 2)} - - )} - - - {user.gameId} - {user.score} - - ); - })} + {match.matchPlayerInfoList.length === 0 ? ( + 아직 대진표가 생성되지 않았어요 :( + ) : ( + match.matchPlayerInfoList.map((user) => { + return ( + + + {user.profileSrc ? ( + + ) : ( + + {user.gameId.substring(0, 2)} + + )} + + + {user.gameId} + {user.score} + + ); + }) + )} ); @@ -78,6 +82,8 @@ const Container = styled.div``; const Header = styled.div``; const Content = styled.div` + position: relative; + display: grid; max-width: 100rem; @@ -170,6 +176,19 @@ const MatchContent = styled.div` flex-direction: column; `; +const MatchNoGame = styled.div` + height: 35rem; + width: 21rem; + display: flex; + align-items: center; + justify-content: center; + background-color: #b4b4b3; + opacity: 0.6; + + font-size: 1.8 rem; + color: black; +`; + const UserContainer = styled.div<{ myself: boolean }>` width: 20rem; display: grid; diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx index f211ffa..7aabb28 100644 --- a/src/components/Icon/index.tsx +++ b/src/components/Icon/index.tsx @@ -13,6 +13,8 @@ import { MdRefresh, MdSend, MdModeEdit, + MdSettings, + MdCancel, } from 'react-icons/md'; import { MouseEventHandler } from 'react'; @@ -29,6 +31,8 @@ const ICON: { [key in IconKind]: IconType } = { refresh: MdRefresh, sendEmail: MdSend, modify: MdModeEdit, + setting: MdSettings, + cancel: MdCancel, }; interface IconProps { diff --git a/src/components/Modal/ModifyChannel/ModifyChannel.tsx b/src/components/Modal/ModifyChannel/ModifyChannel.tsx index 358dbf0..432faf7 100644 --- a/src/components/Modal/ModifyChannel/ModifyChannel.tsx +++ b/src/components/Modal/ModifyChannel/ModifyChannel.tsx @@ -1,8 +1,8 @@ -import authAPI from '@apis/authAPI'; -import { css } from '@emotion/react'; +import Icon from '@components/Icon'; +import BasicInfoChannel from '@components/ModifyChannel/BasicInfoChannel'; +import BracketInfoChannel from '@components/ModifyChannel/BracketInfoChannel'; import styled from '@emotion/styled'; -import { useRef } from 'react'; - +import { useState } from 'react'; interface ModifyChannelProps { channelLink: string; leagueTitle: string; @@ -10,68 +10,38 @@ interface ModifyChannelProps { onClose: (leagueTitle?: string, maxPlayer?: number) => void; } +type MenuList = 'basicInfo' | 'bracketInfo'; + const ModifyChannel = ({ channelLink, leagueTitle, maxPlayer, onClose }: ModifyChannelProps) => { - const leagueTitleRef = useRef(null); - const maxPlayerRef = useRef(null); + const [selectedMenu, setSelectedMenu] = useState('basicInfo'); - const onClickSubmit = async () => { - console.log(leagueTitleRef.current?.value, maxPlayerRef.current?.value); - if (!leagueTitleRef.current || !maxPlayerRef.current) return; - const updatedLeagueTitle = leagueTitleRef.current.value; - const updatedMaxPlayer = parseInt(maxPlayerRef.current.value, 10); - if (isNaN(updatedMaxPlayer)) { - alert('최대인원수를 숫자로 입력해주세요'); - return; - } - if (!confirm('리그를 수정하시겠습니까?')) return; - const res = await authAPI({ - method: 'post', - url: `/api/channel/${channelLink}`, - data: { - title: updatedLeagueTitle, - maxPlayer: updatedMaxPlayer, - }, - }); - if (res.status !== 200) return; - alert('정보가 수정되었습니다'); - onClose(updatedLeagueTitle, updatedMaxPlayer); + const handleSelectedMenu = (menu: MenuList) => { + setSelectedMenu(menu); }; return ( - -

리그 수정하기

-
- - 리그 제목 - - + handleSelectedMenu('basicInfo')}> + 대회 기본 정보 수정 + + handleSelectedMenu('bracketInfo')}> + 대진표 수정 + + + + {selectedMenu === 'basicInfo' && ( + - - - - 최대 참여자 인원 - - - - - - onClose()}>취소 - 수정하기 - + )} + {selectedMenu === 'bracketInfo' && } + + + onClose()} /> +
); }; @@ -79,58 +49,42 @@ const ModifyChannel = ({ channelLink, leagueTitle, maxPlayer, onClose }: ModifyC export default ModifyChannel; const Container = styled.div` - color: black; - padding: 5%; -`; + width: 100vw; + height: 100vh; + background-color: white; -const Wrapper = styled.div` display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - padding: 1rem; - font-size: 1.7rem; - font-weight: bold; - min-height: 7rem; `; -const FlexWrapper = styled.div` - flex: 1; - justify-content: flex-start; +const Sidebar = styled.div` + width: 30rem; + background-color: #141c24; `; -const ButtonWrapper = styled.div` +const SidebarContent = styled.div` + width: 80%; + height: 5rem; + margin: 0 auto; + color: white; + + font-size: 2rem; + font-weight: 900; + display: flex; - flex-direction: row; - justify-content: center; align-items: center; - padding: 1rem; - font-size: 1.7rem; - font-weight: bold; - min-height: 7rem; + + cursor: pointer; `; -const Input = styled.input` - width: 80%; - height: 4rem; - border: none; - border-radius: 0.6rem; - padding: 0.6rem; +const MainContent = styled.div` + width: calc(100vw - 30rem); + + background-color: #202b37; `; -const SubmitButton = styled.button` - width: 10rem; - height: 6rem; - background-color: #344051; - border: none; - border-radius: 0.5rem; - color: white; - margin: 0 6rem 0 6rem; - &:hover { - cursor: pointer; - } - &:disabled { - background-color: #d3d3d3; - cursor: not-allowed; - } +const CloseButtonContainer = styled.div` + position: absolute; + top: 1rem; + right: 1rem; + cursor: pointer; `; diff --git a/src/components/ModifyChannel/BasicInfoChannel.tsx b/src/components/ModifyChannel/BasicInfoChannel.tsx new file mode 100644 index 0000000..b693f30 --- /dev/null +++ b/src/components/ModifyChannel/BasicInfoChannel.tsx @@ -0,0 +1,134 @@ +import styled from '@emotion/styled'; +import { css } from '@emotion/react'; +import { useRef } from 'react'; +import authAPI from '@apis/authAPI'; + +interface BasicInfoChannelProps { + channelLink: string; + leagueTitle: string; + maxPlayer: number; +} + +const BasicInfoChannel = ({ channelLink, leagueTitle, maxPlayer }: BasicInfoChannelProps) => { + const leagueTitleRef = useRef(null); + const maxPlayerRef = useRef(null); + + const onClickSubmit = async () => { + console.log(leagueTitleRef.current?.value, maxPlayerRef.current?.value); + if (!leagueTitleRef.current || !maxPlayerRef.current) return; + const updatedLeagueTitle = leagueTitleRef.current.value; + const updatedMaxPlayer = parseInt(maxPlayerRef.current.value, 10); + if (isNaN(updatedMaxPlayer)) { + alert('최대인원수를 숫자로 입력해주세요'); + return; + } + if (!confirm('리그를 수정하시겠습니까?')) return; + const res = await authAPI({ + method: 'post', + url: `/api/channel/${channelLink}`, + data: { + title: updatedLeagueTitle, + maxPlayer: updatedMaxPlayer, + }, + }); + if (res.status !== 200) return; + alert('정보가 수정되었습니다'); + }; + + return ( + +
리그 수정하기
+ + + 리그 제목 + + + + + + 최대 인원 + + + + + + + + 수정하기 + +
+ ); +}; + +export default BasicInfoChannel; + +const Container = styled.div` + margin: 2rem 4rem; +`; +const Header = styled.div` + text-align: left; + + font-size: 3rem; + font-weight: 900; + color: white; +`; + +const Content = styled.div``; + +const Wrapper = styled.div` + display: flex; + flex-direction: row; + align-items: center; + padding: 1rem; + font-size: 1.7rem; + font-weight: bold; + min-height: 7rem; +`; + +const FlexWrapper = styled.div` + color: white; +`; + +const ButtonWrapper = styled.div` + display: flex; + align-items: center; + justify-content: flex-end; + + font-weight: bold; + font-size: 1.8rem; +`; + +const Input = styled.input` + width: 80%; + height: 4rem; + border: none; + border-radius: 0.6rem; + padding: 0.6rem; +`; + +const SubmitButton = styled.button` + width: 10rem; + height: 6rem; + background-color: #344051; + border: none; + border-radius: 0.5rem; + color: white; + margin: 0 6rem 0 6rem; + &:hover { + cursor: pointer; + } + &:disabled { + background-color: #d3d3d3; + cursor: not-allowed; + } +`; diff --git a/src/components/ModifyChannel/BracketInfoChannel.tsx b/src/components/ModifyChannel/BracketInfoChannel.tsx new file mode 100644 index 0000000..fb06db5 --- /dev/null +++ b/src/components/ModifyChannel/BracketInfoChannel.tsx @@ -0,0 +1,133 @@ +import authAPI from '@apis/authAPI'; +import styled from '@emotion/styled'; +import { useQuery } from '@tanstack/react-query'; +import { MatchCountList } from '@type/channelConfig'; +import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; + +const fetchInitialMatchCount = async (channelLink: string) => { + const res = await authAPI({ + method: 'get', + url: `/api/match/${channelLink}/count`, + }); + return res.data; +}; + +const BracketInfoChannel = () => { + const router = useRouter(); + + const [roundInfo, setRoundInfo] = useState(); + + const { data, isLoading, isError, isSuccess } = useQuery({ + queryKey: ['matchCount', router.query.channelLink as string], + queryFn: () => fetchInitialMatchCount(router.query.channelLink as string), + }); + + const handleRoundInfo = (e: React.ChangeEvent, roundIndex: number) => { + const changeRoundInfoArr = roundInfo?.map((ele, index) => + index === roundIndex ? Number(e.target.value) : ele, + ); + + setRoundInfo(changeRoundInfoArr); + }; + + const updateRoundMatchCount = async () => { + try { + const res = await authAPI({ + method: 'post', + url: `/api/match/${router.query.channelLink as string}/count`, + data: { + roundCountList: roundInfo?.reverse(), + }, + }); + + console.log(res); + + alert('수정이 완료되었습니다!'); + } catch (error) { + console.log(error); + } + }; + + useEffect(() => { + if (isSuccess) { + setRoundInfo(data?.roundCountList.reverse()); + } + }, [data]); + + return ( + +
라운드 수정
+ + {roundInfo?.map((currentMatchCount, index) => { + return ( + + Round {index + 1} + + + ); + })} + + 수정완료 + + +
+ ); +}; + +export default BracketInfoChannel; + +const Container = styled.div` + margin: 2rem 4rem; +`; + +const Header = styled.div` + text-align: left; + + font-size: 3rem; + font-weight: 900; + color: white; +`; + +const Content = styled.div``; + +const RoundInfo = styled.div` + height: 7rem; + display: flex; + align-items: center; + gap: 2rem; +`; + +const RoundInfoHeader = styled.div` + font-size: 2rem; + color: white; +`; + +const ButtonWrapper = styled.div` + display: flex; + align-items: center; + justify-content: flex-end; +`; + +const ModifyButton = styled.button` + width: 10rem; + height: 6rem; + background-color: #344051; + border: none; + border-radius: 0.5rem; + color: white; + margin: 0 6rem 0 6rem; + + cursor: pointer; + + font-size: 1.8rem; +`;