diff --git a/components/admin/tournament/TournamentEdit.tsx b/components/admin/tournament/TournamentEdit.tsx index 136ee855b..9e3808ced 100644 --- a/components/admin/tournament/TournamentEdit.tsx +++ b/components/admin/tournament/TournamentEdit.tsx @@ -1,5 +1,5 @@ import dynamic from 'next/dynamic'; -import { useEffect, useState } from 'react'; +import { ChangeEvent, useEffect } from 'react'; import { useSetRecoilState } from 'recoil'; import { Paper, @@ -9,10 +9,11 @@ import { TableContainer, TableRow, } from '@mui/material'; +import { ITournamentEditInfo } from 'types/admin/adminTournamentTypes'; import { QUILL_EDIT_MODULES, QUILL_FORMATS } from 'types/quillTypes'; +import { instanceInManage } from 'utils/axios'; import { toastState } from 'utils/recoil/toast'; -import { useUser } from 'hooks/Layout/useUser'; -import styles from 'styles/admin/announcement/AnnounceEdit.module.scss'; +import styles from 'styles/admin/tournament/TournamentEdit.module.scss'; import 'react-quill/dist/quill.snow.css'; import 'react-quill/dist/quill.bubble.css'; import { AdminTableHead } from '../common/AdminTable'; @@ -23,113 +24,102 @@ const Quill = dynamic(() => import('react-quill'), { }); const tableTitle: { [key: string]: string } = { - tournamentName: '토너먼트 이름', + title: '토너먼트 이름', startTime: '시작 시간', endTime: '종료 시간', - tournamentType: '토너먼트 유형', + type: '유형', }; -export default function TournamentEdit() { - const user = useUser(); - const setSnackbar = useSetRecoilState(toastState); - const [content, setContent] = useState(''); - const announceCreateResponse: { [key: string]: string } = { - SUCCESS: '공지사항이 성공적으로 등록되었습니다.', - AN300: - '이미 활성화된 공지사항이 있습니다. 새로운 공지사항을 등록하시려면 활성화된 공지사항을 삭제해 주세요.', +interface TournamentEditProps { + tournamentEditInfo: ITournamentEditInfo; + inputChangeHandler: (event: ChangeEvent) => void; + selectChangehandler: (event: ChangeEvent) => void; + quillChangeHandler: ( + value: string, + delta: any, + source: string, + editor: any + ) => void; + resetHandler: () => Promise; +} + +export default function TournamentEdit({ + tournamentEditInfo, + inputChangeHandler, + selectChangehandler, + quillChangeHandler, + resetHandler, +}: TournamentEditProps) { + const tournamentPatchResponse: { [key: string]: string } = { + SUCCESS: '토너먼트가 성공적으로 수정되었습니다.', }; - const announceDeleteResponse: { [key: string]: string } = { - SUCCESS: '공지사항이 성공적으로 삭제되었습니다.', - AN100: '삭제 할 활성화된 공지사항이 없습니다.', + const tournamentCreateResponse: { [key: string]: string } = { + SUCCESS: '토너먼트가 성공적으로 등록되었습니다.', }; + const setSnackbar = useSetRecoilState(toastState); + useEffect(() => { resetHandler(); }, []); - const resetHandler = async () => { + const patchHandler = async () => { try { - //const res = await instance.get('/pingpong/announcement'); - setContent('가장 최근 토너먼트 내용'); //setContent(res?.data.content); - } catch (e) { - alert(e); + await instanceInManage.patch( + `/tournament/${tournamentEditInfo.tournamentId}`, + { + title: tournamentEditInfo.title, + contents: tournamentEditInfo.contents, + startTime: tournamentEditInfo.startTime, + endTime: tournamentEditInfo.endTime, + type: tournamentEditInfo.type, + } + ); + setSnackbar({ + toastName: `patch request`, + severity: 'success', + message: `🔥 ${tournamentPatchResponse.SUCCESS} 🔥`, + clicked: true, + }); + } catch (e: any) { + setSnackbar({ + toastName: `bad request`, + severity: 'error', + // message: `🔥 ${announceCreateResponse[e.response.data.code]} 🔥`, + clicked: true, + }); } }; - if (!user) return null; - - const currentUserId = user.intraId; - const postHandler = async () => { - // try { - // await instanceInManage.post(`/announcement`, { - // content, - // creatorIntraId: currentUserId, - // }); - // setSnackbar({ - // toastName: `post request`, - // severity: 'success', - // message: `🔥 ${announceCreateResponse.SUCCESS} 🔥`, - // clicked: true, - // }); - // } catch (e: any) { - // setSnackbar({ - // toastName: `bad request`, - // severity: 'error', - // message: `🔥 ${announceCreateResponse[e.response.data.code]} 🔥`, - // clicked: true, - // }); - // } - }; - - const deleteHandler = async () => { - // try { - // await instanceInManage.delete(`/announcement/${currentUserId}`); - // setSnackbar({ - // toastName: `delete request`, - // severity: 'success', - // message: `🔥 ${announceDeleteResponse.SUCCESS} 🔥`, - // clicked: true, - // }); - // } catch (e: any) { - // setSnackbar({ - // toastName: `bad request`, - // severity: 'error', - // message: `🔥 ${announceDeleteResponse[e.response.data.code]} 🔥`, - // clicked: true, - // }); - // } + try { + await instanceInManage.post(`/tournament`, { + title: tournamentEditInfo.title, + contents: tournamentEditInfo.contents, + startTime: tournamentEditInfo.startTime, + endTime: tournamentEditInfo.endTime, + type: tournamentEditInfo.type, + }); + setSnackbar({ + toastName: `post request`, + severity: 'success', + message: `🔥 ${tournamentCreateResponse.SUCCESS} 🔥`, + clicked: true, + }); + } catch (e: any) { + setSnackbar({ + toastName: `bad request`, + severity: 'error', + // message: `🔥 ${announceCreateResponse[e.response.data.code]} 🔥`, + clicked: true, + }); + } }; return (

추후 토너먼트 페이지 모달 완성시 추가 예정

- {/* {content ? ( -
-
Notice!
- -
- - -
-
-
- -
-
-
- ) : ( -
활성화된 공지사항이 없습니다 !
- )} */}
@@ -140,34 +130,46 @@ export default function TournamentEdit() { @@ -179,13 +181,20 @@ export default function TournamentEdit() { modules={QUILL_EDIT_MODULES} formats={QUILL_FORMATS} theme='snow' - value={content} - onChange={(content) => setContent(content)} + value={tournamentEditInfo.contents} + onChange={quillChangeHandler} />
- - + {tournamentEditInfo.tournamentId ? ( + + ) : ( + + )}
diff --git a/components/admin/tournament/TournamentList.tsx b/components/admin/tournament/TournamentList.tsx index 1dd56a6fc..ea4f654a8 100644 --- a/components/admin/tournament/TournamentList.tsx +++ b/components/admin/tournament/TournamentList.tsx @@ -1,5 +1,10 @@ -import dynamic from 'next/dynamic'; -import { useCallback, useEffect, useState } from 'react'; +import { + Dispatch, + SetStateAction, + useCallback, + useEffect, + useState, +} from 'react'; import { useSetRecoilState } from 'recoil'; import { Paper, @@ -11,36 +16,45 @@ import { } from '@mui/material'; import { ITournament, + ITournamentEditInfo, ITournamentTable, } from 'types/admin/adminTournamentTypes'; -import { TournamentInfo } from 'types/tournamentTypes'; +import { instanceInManage } from 'utils/axios'; +import { dateToString } from 'utils/handleTime'; import { mockInstance } from 'utils/mockAxios'; import { modalState } from 'utils/recoil/modal'; +import { toastState } from 'utils/recoil/toast'; import { tableFormat } from 'constants/admin/table'; import { AdminEmptyItem, AdminTableHead, } from 'components/admin/common/AdminTable'; import PageNation from 'components/Pagination'; -import styles from 'styles/admin/announcement/AnnounceList.module.scss'; -import 'react-quill/dist/quill.snow.css'; -import 'react-quill/dist/quill.bubble.css'; - -const Quill = dynamic(() => import('react-quill'), { - ssr: false, - loading: () =>

Loading ...

, -}); +import styles from 'styles/admin/tournament/TournamentList.module.scss'; const tableTitle: { [key: string]: string } = { title: '토너먼트 이름', contents: '토너먼트 내용', startTime: '시작 시간', endTime: '종료 시간', - type: '토너먼트 타입', + type: '유형', edit: '수정하기', }; -export default function TournamentList() { +interface TournamentListProps { + tournamentEditInfo: ITournamentEditInfo; + setTournamentEditInfo: Dispatch>; +} + +export default function TournamentList({ + tournamentEditInfo, + setTournamentEditInfo, +}: TournamentListProps) { + const tournamentDeleteResponse: { [key: string]: string } = { + SUCCESS: '토너먼트가 성공적으로 삭제되었습니다.', + }; + + const setSnackbar = useSetRecoilState(toastState); const setModal = useSetRecoilState(modalState); const [tournamentInfo, setTournamentInfo] = useState({ tournamentList: [], @@ -67,6 +81,25 @@ export default function TournamentList() { fetchTournaments(); }, [fetchTournaments]); + const deleteHandler = async (tournamentId: number) => { + try { + await instanceInManage.delete(`/tournament/${tournamentId}`); + setSnackbar({ + toastName: `delete request`, + severity: 'success', + message: `🔥 ${tournamentDeleteResponse.SUCCESS} 🔥`, + clicked: true, + }); + } catch (e: any) { + setSnackbar({ + toastName: `bad request`, + severity: 'error', + // message: `🔥 ${announceDeleteResponse[e.response.data.code]} 🔥`, + clicked: true, + }); + } + }; + return (
@@ -79,7 +112,15 @@ export default function TournamentList() { {tournamentInfo.tournamentList.length > 0 ? ( tournamentInfo.tournamentList.map( (tournament: ITournament, index: number) => ( - + {tableFormat['tournament'].columns.map( (columnName: string, index: number) => { return ( @@ -89,23 +130,74 @@ export default function TournamentList() { > {columnName === 'startTime' || columnName === 'endTime' ? ( - tournament[ - columnName as keyof ITournament - ]?.toLocaleString() + dateToString( + new Date( + tournament[ + columnName as keyof ITournament + ] as Date + ) + ) ) : columnName === 'edit' ? ( -
- -
+ tournament.status === 'BEFORE' ? ( +
+ + + +
+ ) : ( +
+ +
+ ) ) : ( tournament[ columnName as keyof ITournament diff --git a/components/modal/admin/AdminEditTournamentBraket.tsx b/components/modal/admin/AdminEditTournamentBraket.tsx index 1a86d325a..3a98af015 100644 --- a/components/modal/admin/AdminEditTournamentBraket.tsx +++ b/components/modal/admin/AdminEditTournamentBraket.tsx @@ -7,11 +7,22 @@ import { mockInstance } from 'utils/mockAxios'; import TournamentBraket from 'components/tournament/TournamentBraket'; import styles from 'styles/admin/modal/AdminEditTournamentBraket.module.scss'; +const tournamentId = 1; + export default function AdminEditTournamentBraket({ tournamentId, }: ITournament) { const [bracketMatchs, setBracketMatchs] = useState([]); + // const putHandler = async () => { + // await instanceInManage.put( + // `/pingpong/admin/tournaments/${tournamentId}}/games`, + // { + // games: tournament + // } + // ); + // }; + const fetchTournamentGames = useCallback(async () => { console.log('Fetching more data...'); try { @@ -32,6 +43,7 @@ export default function AdminEditTournamentBraket({ return (
+
- ); + ); //*onClick={putHandler} } diff --git a/constants/admin/table.ts b/constants/admin/table.ts index 05dbcd2e4..ca6d8a607 100644 --- a/constants/admin/table.ts +++ b/constants/admin/table.ts @@ -156,6 +156,6 @@ export const tableFormat: TableFormat = { }, tournamentCreate: { name: '토너먼트 생성', - columns: ['tournamentName', 'startTime', 'endTime', 'tournamentType'], + columns: ['title', 'startTime', 'endTime', 'type'], }, }; diff --git a/hooks/tournament/useTournamentEditInfo.ts b/hooks/tournament/useTournamentEditInfo.ts new file mode 100644 index 000000000..e91605e1f --- /dev/null +++ b/hooks/tournament/useTournamentEditInfo.ts @@ -0,0 +1,51 @@ +import { useState, useCallback } from 'react'; +import { ITournamentEditInfo } from 'types/admin/adminTournamentTypes'; + +const emptyTournamentEditInfo: ITournamentEditInfo = { + tournamentId: null, + title: '', + contents: '', + type: 'CUSTOM', + startTime: new Date(), + endTime: new Date(), +}; + +function useTournamentEditInfo() { + const [tournamentEditInfo, setTournamentEditInfo] = + useState(emptyTournamentEditInfo); + + // change + const inputChangeHandler = (event: React.ChangeEvent) => { + const { name, value } = event.target; + if (name === 'startTime' || name === 'endTime') + setTournamentEditInfo((prev) => ({ ...prev, [name]: new Date(value) })); + else setTournamentEditInfo((prev) => ({ ...prev, [name]: value })); + }; + + const selectChangeHandler = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setTournamentEditInfo((prev) => ({ ...prev, [name]: value })); + }; + + const quillChangeHandler = (value: string) => { + setTournamentEditInfo((prev) => ({ ...prev, contents: value })); + }; + + const resetHandler = async () => { + try { + setTournamentEditInfo(emptyTournamentEditInfo); + } catch (e) { + alert(e); + } + }; + return { + tournamentEditInfo, + setTournamentEditInfo, + inputChangeHandler, + selectChangeHandler, + quillChangeHandler, + resetHandler, + }; +} + +export default useTournamentEditInfo; diff --git a/pages/admin/tournament.tsx b/pages/admin/tournament.tsx index 9bc472b01..fc0a8f770 100644 --- a/pages/admin/tournament.tsx +++ b/pages/admin/tournament.tsx @@ -1,12 +1,32 @@ +import { useState } from 'react'; import TournamentEdit from 'components/admin/tournament/TournamentEdit'; import TournamentList from 'components/admin/tournament/TournamentList'; -import styles from 'styles/admin/announcement/Announcement.module.scss'; +import useTournamentEditInfo from 'hooks/tournament/useTournamentEditInfo'; +import styles from 'styles/admin/tournament/Tournament.module.scss'; export default function Tournament() { + const { + tournamentEditInfo, + setTournamentEditInfo, + inputChangeHandler, + selectChangeHandler, + quillChangeHandler, + resetHandler, + } = useTournamentEditInfo(); + return (
- - + +
); } diff --git a/pages/api/pingpong/tournament/dummyTournamentData.ts b/pages/api/pingpong/tournament/dummyTournamentData.ts index e96a4443b..6b0adb8c2 100644 --- a/pages/api/pingpong/tournament/dummyTournamentData.ts +++ b/pages/api/pingpong/tournament/dummyTournamentData.ts @@ -25,11 +25,13 @@ const dummyTournaments: TournamentInfo[] = []; for (let i = 28; i >= 1; i--) { let status; if (i === 28) { - status = '예정'; + status = 'BEFORE'; } else if (i === 27) { - status = '진행중'; + status = 'READY'; + } else if (i === 26) { + status = 'LIVE'; } else { - status = '종료'; + status = 'END'; } const rookieTournament: TournamentInfo = { diff --git a/styles/admin/modal/AdminEditTournamentBraket.module.scss b/styles/admin/modal/AdminEditTournamentBraket.module.scss index 364003780..7666e939c 100644 --- a/styles/admin/modal/AdminEditTournamentBraket.module.scss +++ b/styles/admin/modal/AdminEditTournamentBraket.module.scss @@ -5,6 +5,24 @@ width: 510px; height: 569px; flex-direction: column; - background: rgb(38, 38, 38); + background: gray; border-radius: 9px; + align-items: center; +} + +.editBtn { + all: unset; + width: 5rem; + height: 2rem; + font-size: $small-font; + color: white; + text-align: center; + cursor: pointer; + background: gray; + background-color: $btn-blue; + border-radius: 0.5rem; + &:hover { + background-color: darken($btn-blue, 10%); + transition: 0.3s; + } } diff --git a/styles/admin/tournament/Tournament.module.scss b/styles/admin/tournament/Tournament.module.scss new file mode 100644 index 000000000..5dafda454 --- /dev/null +++ b/styles/admin/tournament/Tournament.module.scss @@ -0,0 +1,6 @@ +.container { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; +} diff --git a/styles/admin/tournament/TournamentEdit.module.scss b/styles/admin/tournament/TournamentEdit.module.scss new file mode 100644 index 000000000..7ea5ad634 --- /dev/null +++ b/styles/admin/tournament/TournamentEdit.module.scss @@ -0,0 +1,147 @@ +@import 'styles/common.scss'; + +.container { + display: flex; + width: 100%; + height: 28rem; + flex-direction: row; + padding: 1.5rem; + gap: 1.5rem; +} + +.announceModal { + display: flex; + min-width: 16rem; + height: min-content; + max-height: 31rem; + flex-direction: column; + font-size: $medium-font; + color: white; + background: linear-gradient( + 180deg, + #301451 23.82%, + #481570 58.85%, + #300e4b 98.44% + ); + border-radius: $small-radius; + justify-content: space-between; + align-items: center; +} + +.announceModalContainer { + @include modalContainer('SKYPINK'); + margin: 1.6rem 1.5rem; +} + +.modalTitle { + @include modalTitle; + font-size: 2rem; + color: #121725; +} + +.quillViewer { + width: 100%; + min-width: 10rem; + max-width: 15rem; + height: 100%; + max-height: 19rem; + margin: 1rem 0; + overflow-y: scroll; + color: black; +} + +.quillViewer::-webkit-scrollbar { + display: inherit; + width: 5px; + height: 1rem; +} + +.quillViewer::-webkit-scrollbar-thumb { + background: white; + border-radius: 10px; +} + +.checkBox { + margin-bottom: 0.5rem; + + input { + position: relative; + top: 2px; + display: inline-block; + width: 1rem; + height: 1rem; + margin: 0 0.5rem 0 0; + } + + label { + display: inline-block; + } +} + +.buttons { + @include select-buttons; +} + +.editorBtnContainer { + z-index: 0; + display: flex; + flex-direction: row; + gap: 1rem; + + button { + all: unset; + width: 5rem; + height: 2rem; + font-size: $small-font; + color: white; + text-align: center; + cursor: pointer; + background: gray; + border-radius: 0.5rem; + } + + .edit { + background-color: $btn-blue; + &:hover { + background-color: darken($btn-blue, 10%); + transition: 0.3s; + } + } + + .create { + background-color: #10b981; + &:hover { + background-color: darken(#10b981, 10%); + transition: 0.3s; + } + } + + :nth-child(3) { + background: red; + } +} + +.noActive { + @include modalContainer('SKYPINK'); + margin: 1.5rem; + font-size: $medium-font; + color: black; +} + +.editorContainer { + display: flex; + width: 100%; + max-width: 40rem; + height: 30rem; + flex-direction: column; + padding: 1rem; + border: 1px solid $gray; + border-radius: $small-radius; + justify-content: space-between; + align-items: flex-end; +} + +.quillEditor { + width: 100%; + height: 22rem; +} diff --git a/styles/admin/tournament/TournamentList.module.scss b/styles/admin/tournament/TournamentList.module.scss new file mode 100644 index 000000000..d7df305f6 --- /dev/null +++ b/styles/admin/tournament/TournamentList.module.scss @@ -0,0 +1,107 @@ +@import 'styles/common.scss'; +@import 'styles/admin/common/Pagination.module.scss'; + +.container { + display: flex; + width: 95%; + max-width: 61.5rem; + height: 100%; + flex-direction: column; + justify-content: center; + align-items: center; + margin-top: 5rem; +} + +.header { + display: flex; + width: 100%; + padding: 0.5rem 1rem; +} + +.title { + font-size: $big-font; + font-weight: 700; + line-height: 150%; +} + +.tableContainer { + .table { + .tableBody { + .tableRow { + &:nth-child(odd) { + background-color: #f9fafb; + } + &:nth-child(even) { + background-color: #fff; + } + } + .selectedTableRow { + background-color: #6b7280; + } + .tableBodyItem { + padding: 0.5rem; + font-size: $small-font; + line-height: 100%; + text-align: center; + } + } + } +} + +.listBtnContainer { + z-index: 0; + display: flex; + flex-direction: row; + gap: 0.2rem; + justify-content: center; + + %button { + padding: 4px 12px; + font-size: $small-font; + white-space: nowrap; + cursor: pointer; + border: none; + border-radius: $medium-radius; + } + + .edit { + @extend %button; + color: #6b7280; + background-color: #e5e7eb; + &:hover { + background-color: darken(#e5e7eb, 10%); + transition: 0.3s; + } + } + .editParticipants { + @extend %button; + color: white; + background-color: #10b981; + &:hover { + background-color: darken(#10b981, 10%); + transition: 0.3s; + } + } + + .delete { + @extend %button; + color: white; + background-color: $pp-red; + &:hover { + background-color: darken($pp-red, 10%); + transition: 0.3s; + } + } + + .editBracket { + @extend %button; + color: white; + background-color: $rank-purple; + &:hover { + background-color: darken($rank-purple, 10%); + transition: 0.3s; + } + } +} + +@include pagination; diff --git a/types/admin/adminTournamentTypes.ts b/types/admin/adminTournamentTypes.ts index 57683c7cd..0e8b524f9 100644 --- a/types/admin/adminTournamentTypes.ts +++ b/types/admin/adminTournamentTypes.ts @@ -2,7 +2,7 @@ export interface ITournament { tournamentId: number; title: string; contents: string; - status: string; // 'NO_SHOW' | 'WALK_OVER' | 'NO_PARTY' | 'DONE' | 'SCORE_DONE' + status: 'BEFORE' | 'READY' | 'LIVE' | 'END'; type: 'CUSTOM' | 'MASTER' | 'ROOKIE'; winnerIntraId: string; winnerImageUrl: string; @@ -16,3 +16,12 @@ export interface ITournamentTable { totalPage: number; currentPage: number; } + +export interface ITournamentEditInfo { + tournamentId: number | null; + title: string; + contents: string; + type: 'CUSTOM' | 'MASTER' | 'ROOKIE' | null; + startTime: Date; + endTime: Date; +}