From dfde845e01ad7424f12bfac8b9d7b615f4dacc96 Mon Sep 17 00:00:00 2001 From: pipisebastian Date: Sat, 8 Jun 2024 02:25:26 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20team=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Team : 기본 베이스 타입 - TeamDetail : 팀 조회 타입 #247 --- src/types.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 2437e0bf..efc7d89d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -60,9 +60,15 @@ export interface EditStudyDto { status: string; } -export interface EditTeamDto { +export interface Team { + readonly id: number; name: string; description: string; + imageUrl: string; +} + +export interface TeamDetail extends Team { + attendanceRate: number; } export interface Curriculum { From 62a87c40d3af88588d12a7eab6bfe7e2e01a6b8b Mon Sep 17 00:00:00 2001 From: pipisebastian Date: Sat, 8 Jun 2024 02:26:21 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=ED=8C=80=20=EC=88=98=EC=A0=95=20ap?= =?UTF-8?q?i=20Team=20=ED=83=80=EC=9E=85=20=EC=B6=95=EC=86=8C=ED=95=B4?= =?UTF-8?q?=EC=84=9C=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20#24?= =?UTF-8?q?7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/team.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/api/team.ts b/src/app/api/team.ts index d522201d..a3247bcd 100644 --- a/src/app/api/team.ts +++ b/src/app/api/team.ts @@ -1,4 +1,4 @@ -import { EditTeamDto } from '@/types'; +import { Team } from '@/types'; import { fetcher } from './fetcher'; @@ -10,10 +10,10 @@ const postCreateTeam = (team: FormData) => body: team, }); -const putEditTeam = (teamId: number, team: EditTeamDto) => +const putEditTeam = (teamId: number, teamInfo: Pick) => teamFetcher(`/teams/${teamId}`, { method: 'PUT', - body: team, + body: teamInfo, }); const patchEditTeamImage = (teamId: number, file: FormData) => From cbb8a9b3b13327043feb2fa5142550d4593b82d7 Mon Sep 17 00:00:00 2001 From: pipisebastian Date: Sat, 8 Jun 2024 02:31:40 +0900 Subject: [PATCH 3/7] =?UTF-8?q?refactor:=20=ED=83=80=EC=9D=B4=ED=8B=80=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20Team=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=20=EC=82=AC=EC=9A=A9=20-=20teamImg=20->=20imageUrl?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD(Team=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=82=AC=EC=9A=A9)=20-=20=EB=85=BC?= =?UTF-8?q?=EB=A6=AC=ED=95=A9=EC=97=B0=EC=82=B0=EC=9E=90=20=EB=8C=80?= =?UTF-8?q?=EC=8B=A0=20=20null=EB=B3=91=ED=95=A9=20=EC=97=B0=EC=82=B0?= =?UTF-8?q?=EC=9E=90=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #247 --- src/components/Title/index.tsx | 4 ++-- src/components/Title/types.ts | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/Title/index.tsx b/src/components/Title/index.tsx index 9997fb28..81dc7342 100644 --- a/src/components/Title/index.tsx +++ b/src/components/Title/index.tsx @@ -2,7 +2,7 @@ import { Text, Flex, Avatar, Box } from '@chakra-ui/react'; import { TitleProps } from './types'; -const Title = ({ isTeam = false, teamImg, name, description }: TitleProps) => { +const Title = ({ isTeam = false, name, description, imageUrl }: TitleProps) => { return ( {isTeam && ( @@ -11,7 +11,7 @@ const Title = ({ isTeam = false, teamImg, name, description }: TitleProps) => { borderColor="gray.100" shadow="none" size="md" - src={teamImg || '/images/doore_logo.png'} + src={imageUrl ?? '/images/doore_logo.png'} /> )} {name} diff --git a/src/components/Title/types.ts b/src/components/Title/types.ts index 131738ba..4a75d88d 100644 --- a/src/components/Title/types.ts +++ b/src/components/Title/types.ts @@ -1,6 +1,5 @@ -export interface TitleProps { +import { Team } from '@/types'; + +export interface TitleProps extends Omit { isTeam?: boolean; - teamImg?: string; - name: string; - description: string; } From 1bf2eab5ffb97f70736bc3c81b4ce43939ddbb8b Mon Sep 17 00:00:00 2001 From: pipisebastian Date: Sat, 8 Jun 2024 02:32:22 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20=ED=8C=80=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=ED=8C=A8=EB=84=90=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?#247?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team/TeamControlPanel/index.tsx | 57 +++++++++++++++++++ src/containers/team/TeamControlPanel/types.ts | 5 ++ 2 files changed, 62 insertions(+) create mode 100644 src/containers/team/TeamControlPanel/index.tsx create mode 100644 src/containers/team/TeamControlPanel/types.ts diff --git a/src/containers/team/TeamControlPanel/index.tsx b/src/containers/team/TeamControlPanel/index.tsx new file mode 100644 index 00000000..8cdc4ca5 --- /dev/null +++ b/src/containers/team/TeamControlPanel/index.tsx @@ -0,0 +1,57 @@ +import { Button, Flex } from '@chakra-ui/react'; +import { useState } from 'react'; + +import { TeamControlPanelProps } from './types'; +import DeleteTeamModal from '../DeleteTeamModal'; +import TeamModal from '../TeamModal'; + +const TeamControlPanel = ({ teamInfo }: TeamControlPanelProps) => { + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + + return ( + + + + {isEditModalOpen && ( + setIsEditModalOpen(false)} /> + )} + {isDeleteModalOpen && ( + setIsDeleteModalOpen(false)} + /> + )} + + ); +}; + +export default TeamControlPanel; diff --git a/src/containers/team/TeamControlPanel/types.ts b/src/containers/team/TeamControlPanel/types.ts new file mode 100644 index 00000000..13f73659 --- /dev/null +++ b/src/containers/team/TeamControlPanel/types.ts @@ -0,0 +1,5 @@ +import { TeamDetail } from '@/types'; + +export interface TeamControlPanelProps { + teamInfo: TeamDetail; +} From 56cdc711515fdadaf7c88f8ef54f833aabe9dd2c Mon Sep 17 00:00:00 2001 From: pipisebastian Date: Sat, 8 Jun 2024 02:35:57 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20=ED=8C=80=20=EC=A1=B0=ED=9A=8C=20Ap?= =?UTF-8?q?i=20mocks=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=97=B0=EA=B2=B0?= =?UTF-8?q?=20#247?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/team/[teamId]/page.tsx | 12 +++++++++--- src/mocks/teamInfo.ts | 10 ++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 src/mocks/teamInfo.ts diff --git a/src/app/team/[teamId]/page.tsx b/src/app/team/[teamId]/page.tsx index 2fa774e7..3b767d69 100644 --- a/src/app/team/[teamId]/page.tsx +++ b/src/app/team/[teamId]/page.tsx @@ -16,12 +16,17 @@ import AssetGridView from '@/containers/team/AssetGridView'; import AttendanceRate from '@/containers/team/AttendanceRate'; import NavigationButton from '@/containers/team/NavigationButton'; import StudyGridView from '@/containers/team/StudyGridView'; +import TeamControlPanel from '@/containers/team/TeamControlPanel'; import TeamMember from '@/containers/team/teamMember'; import { gardenInfos1 } from '@/mocks/Garden3D'; import studyAssetCardData from '@/mocks/studyAssetCard'; import studyCardData from '@/mocks/studyCard'; +import teamInfoData from '@/mocks/teamInfo'; const Page = () => { + // TODO 팀 조회 연결 + const teamInfo = teamInfoData; + const [category, setCategory] = useState(TEAM_CATEGORY_INFOS[0].name); const [cardIdx, setCardIdx] = useState(0); @@ -78,9 +83,9 @@ const Page = () => { }; return ( - + - + <Title isTeam imageUrl={teamInfo.imageUrl} name={teamInfo.name} description={teamInfo.description} /> {/* TODO 팀원 목록, 초대링크 버튼 */} <Flex align="center" gap={{ base: '2', lg: '8' }}> <TeamMember /> @@ -89,6 +94,7 @@ const Page = () => { </Button> </Flex> </Flex> + <TeamControlPanel teamInfo={teamInfo} /> <Flex pos="relative" align="center" flex="1" gap="8"> {/* TODO 잔디 */} @@ -105,7 +111,7 @@ const Page = () => { </Box> {/* TODO 진행도 */} - <AttendanceRate attendanceRate={75} /> + <AttendanceRate attendanceRate={teamInfo.attendanceRate} /> </Flex> <Flex direction="column" flex="1" gap="4"> diff --git a/src/mocks/teamInfo.ts b/src/mocks/teamInfo.ts new file mode 100644 index 00000000..1ffd7dd4 --- /dev/null +++ b/src/mocks/teamInfo.ts @@ -0,0 +1,10 @@ +import { TeamDetail } from '@/types'; + +const teamInfoData: TeamDetail = { + id: 99, + name: '열사모', + description: '팀 설명이옵니다', + imageUrl: 'https://doo-re-dev-bucket.s3.ap-northeast-2.amazonaws.com/images/005bd2bf-b000-47e2-b092-f5210416ecbc.png', + attendanceRate: 50, +}; +export default teamInfoData; From fc6e1277bdf0acc272e13a9be98efe35b603a3e5 Mon Sep 17 00:00:00 2001 From: pipisebastian <yrt7998@pusan.ac.kr> Date: Sat, 8 Jun 2024 02:36:19 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20=ED=8C=80=20=EC=82=AD=EC=A0=9C=20ap?= =?UTF-8?q?i=20=EC=97=B0=EA=B2=B0=20#247?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/containers/team/DeleteTeamModal/index.tsx | 32 +++++++++++++++++++ src/containers/team/DeleteTeamModal/type.ts | 6 ++++ 2 files changed, 38 insertions(+) create mode 100644 src/containers/team/DeleteTeamModal/index.tsx create mode 100644 src/containers/team/DeleteTeamModal/type.ts diff --git a/src/containers/team/DeleteTeamModal/index.tsx b/src/containers/team/DeleteTeamModal/index.tsx new file mode 100644 index 00000000..e1fb75ea --- /dev/null +++ b/src/containers/team/DeleteTeamModal/index.tsx @@ -0,0 +1,32 @@ +import { Text } from '@chakra-ui/react'; + +import { deleteTeam } from '@/app/api/team'; +import ConfirmModal from '@/components/Modal/ConfirmModal'; + +import { DeleteTeamModalProps } from './type'; + +const DeleteTeamModal = ({ id, name, isOpen, onClose }: DeleteTeamModalProps) => { + const handleDeleteTeamButtonClick = () => { + deleteTeam(id).then(() => { + onClose(); + }); + }; + + return ( + <ConfirmModal + isOpen={isOpen} + onClose={onClose} + title="팀 삭제" + confirmButtonText="삭제" + onConfirmButtonClick={() => handleDeleteTeamButtonClick()} + > + <Text align="center"> + 삭제된 팀은 되돌릴 수 없습니다. + <br /> + {name} 팀을 삭제하시겠습니까? + </Text> + </ConfirmModal> + ); +}; + +export default DeleteTeamModal; diff --git a/src/containers/team/DeleteTeamModal/type.ts b/src/containers/team/DeleteTeamModal/type.ts new file mode 100644 index 00000000..47cef8fa --- /dev/null +++ b/src/containers/team/DeleteTeamModal/type.ts @@ -0,0 +1,6 @@ +import { Team } from '@/types'; + +export interface DeleteTeamModalProps extends Pick<Team, 'id' | 'name'> { + isOpen: boolean; + onClose: () => void; +} From dcd8522a7a6e67d27eb333cd54cf3336adb0b8bd Mon Sep 17 00:00:00 2001 From: pipisebastian <yrt7998@pusan.ac.kr> Date: Sat, 8 Jun 2024 02:36:41 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=ED=8C=80=20=EC=88=98=EC=A0=95=20ap?= =?UTF-8?q?i=20=EC=97=B0=EA=B2=B0=20#247?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sidebar/SidebarContent/index.tsx | 2 +- src/containers/team/TeamModal/index.tsx | 93 ++++++++++++++----- src/containers/team/TeamModal/type.ts | 5 +- 3 files changed, 74 insertions(+), 26 deletions(-) diff --git a/src/components/Sidebar/SidebarContent/index.tsx b/src/components/Sidebar/SidebarContent/index.tsx index 15199afe..b0dc8994 100644 --- a/src/components/Sidebar/SidebarContent/index.tsx +++ b/src/components/Sidebar/SidebarContent/index.tsx @@ -90,7 +90,7 @@ const SidebarContent = ({ isOpen, setIsOpen }: SidebarContentProps) => { </> )} </Flex> - <TeamModal isOpen={isTeamModalOpen} setIsOpen={setIsTeamModalOpen} /> + <TeamModal isOpen={isTeamModalOpen} onClose={() => setIsTeamModalOpen(false)} /> </> ); }; diff --git a/src/containers/team/TeamModal/index.tsx b/src/containers/team/TeamModal/index.tsx index 5a802fce..34c2cb85 100644 --- a/src/containers/team/TeamModal/index.tsx +++ b/src/containers/team/TeamModal/index.tsx @@ -1,10 +1,10 @@ 'use client'; import { Flex, Text, Textarea, Image } from '@chakra-ui/react'; -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { BiEdit, BiFile } from 'react-icons/bi'; -import { postCreateTeam } from '@/app/api/team'; +import { patchEditTeamImage, postCreateTeam, putEditTeam } from '@/app/api/team'; import IconBox from '@/components/IconBox'; import ActionModal from '@/components/Modal/ActionModal'; @@ -18,52 +18,92 @@ const AlertContent = ({ message }: { message: string }) => { ); }; -const TeamModal = ({ isOpen, setIsOpen }: TeamModalProps) => { +const TeamModal = ({ teamInfo, isOpen, onClose }: TeamModalProps) => { const inputFileRef = useRef<HTMLInputElement>(null); const [name, setName] = useState<string>(''); const [description, setDescription] = useState<string>(''); + const [thumbnailPath, setThumbnailPath] = useState<string>(''); const [thumbnail, setThumbnail] = useState<File | null>(); const [alertName, setAlertName] = useState<boolean>(false); const [alertDescription, setAlertDescription] = useState<boolean>(false); - const onClose = () => { + const resetState = () => { setName(''); setDescription(''); + setThumbnailPath(''); setThumbnail(null); setAlertName(false); setAlertDescription(false); - setIsOpen(false); }; - const onSave = () => { - if (name === '') setAlertName(true); - else if (description === '') setAlertDescription(true); - else { - const teamForm = new FormData(); - const request = { + const resetAndCloseModal = () => { + resetState(); + onClose(); + }; + + const isTeamInfoValid = () => { + const isValidName = name.trim() !== ''; + const isValidDescription = description.trim() !== ''; + setAlertName(!isValidName); + setAlertDescription(!isValidDescription); + + return isValidName && isValidDescription; + }; + + const handleEditTeamButtonClick = () => { + if (!isTeamInfoValid()) return; + + if (teamInfo) { + putEditTeam(teamInfo.id, { name, description, - }; - const requestBlob = new Blob([JSON.stringify(request)], { type: 'application/json' }); - - teamForm.append('request', requestBlob); - teamForm.append('file', thumbnail as Blob); + }).then(() => { + if (thumbnail) { + const teamForm = new FormData(); + teamForm.append('file', thumbnail as Blob); - postCreateTeam(teamForm).then(() => { - onClose(); + patchEditTeamImage(teamInfo.id, teamForm).then(() => {}); + } + resetAndCloseModal(); }); } }; + const handleAddTeamButtonClick = () => { + if (!isTeamInfoValid) return; + + const teamForm = new FormData(); + const request = { + name, + description, + }; + const requestBlob = new Blob([JSON.stringify(request)], { type: 'application/json' }); + + teamForm.append('request', requestBlob); + teamForm.append('file', thumbnail as Blob); + + postCreateTeam(teamForm).then(() => { + resetAndCloseModal(); + }); + }; + + useEffect(() => { + if (teamInfo) { + setName(teamInfo.name); + setDescription(teamInfo.description); + setThumbnailPath(teamInfo.imageUrl ?? ''); + } + }, [teamInfo]); + return ( <ActionModal isOpen={isOpen} - onClose={onClose} - title="팀 생성" + onClose={resetAndCloseModal} + title={`팀 ${teamInfo ? '수정' : '생성'}`} subButtonText="취소" - onSubButtonClick={onClose} - mainButtonText="저장" - onMainButtonClick={onSave} + onSubButtonClick={resetAndCloseModal} + mainButtonText={teamInfo ? '수정' : '생성'} + onMainButtonClick={teamInfo ? handleEditTeamButtonClick : handleAddTeamButtonClick} > <Flex direction="column" gap="10" w="100%"> <Flex direction="column"> @@ -112,6 +152,7 @@ const TeamModal = ({ isOpen, setIsOpen }: TeamModalProps) => { onChange={(e) => { if (e.target.files && e.target.files[0]) { setThumbnail(e.target.files[0]); + setThumbnailPath(''); } }} /> @@ -121,7 +162,11 @@ const TeamModal = ({ isOpen, setIsOpen }: TeamModalProps) => { content={thumbnail ? thumbnail.name : '파일을 추가해주세요.'} handleClick={() => inputFileRef.current?.click()} /> - {thumbnail && <Image w="40" alt="thumbnail" src={URL.createObjectURL(thumbnail)} />} + {thumbnailPath ? ( + <Image w="40" alt="thumbnail" src={thumbnailPath} /> + ) : ( + thumbnail && <Image w="40" alt="thumbnail" src={URL.createObjectURL(thumbnail)} /> + )} </Flex> </Flex> </ActionModal> diff --git a/src/containers/team/TeamModal/type.ts b/src/containers/team/TeamModal/type.ts index 32a60ecc..528229eb 100644 --- a/src/containers/team/TeamModal/type.ts +++ b/src/containers/team/TeamModal/type.ts @@ -1,4 +1,7 @@ +import { Team } from '@/types'; + export interface TeamModalProps { + teamInfo?: Team; isOpen: boolean; - setIsOpen: React.Dispatch<React.SetStateAction<boolean>>; + onClose: () => void; }