-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/#247 팀 rud api 연결 #248
The head ref may contain hidden characters: "feature/#247-\uD300_RUD_API_\uC5F0\uACB0"
Changes from 7 commits
dfde845
62a87c4
cbb8a9b
1bf2eab
56cdc71
fc6e127
dcd8522
42543e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. teamInfoData는 현재 mocks데이터입니다! api 연결 필요합니다 |
||
|
||
const [category, setCategory] = useState<string>(TEAM_CATEGORY_INFOS[0].name); | ||
const [cardIdx, setCardIdx] = useState<number>(0); | ||
|
||
|
@@ -78,9 +83,9 @@ const Page = () => { | |
}; | ||
|
||
return ( | ||
<Flex direction="column" gap="8" w="100%" p="8"> | ||
<Flex direction="column" gap="2" w="100%" p="8"> | ||
<Flex justify="space-between"> | ||
<Title isTeam name="열사모" description="팀입니다" /> | ||
<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"> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,7 +90,7 @@ const SidebarContent = ({ isOpen, setIsOpen }: SidebarContentProps) => { | |
</> | ||
)} | ||
</Flex> | ||
<TeamModal isOpen={isTeamModalOpen} setIsOpen={setIsTeamModalOpen} /> | ||
<TeamModal isOpen={isTeamModalOpen} onClose={() => setIsTeamModalOpen(false)} /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분 제맘대로 슬쩍 바꿨는데요! 사실 모달창에서 모달을 닫는 작업만 필요하기 때문에, setter로 넘겨줘야하나 싶었습니다! 그래서 모달창 닫는 함수로 넘겨줬습니다! |
||
</> | ||
); | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 ( | ||
<Flex pos="relative" align="center" gap="3"> | ||
{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'} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기는 null 병합 연산자( 그런데 백엔드에 여쭤보니까, 이미지 아무것도 안넣으면, 백엔드에서 기본 디폴트 이미지 url을 던져준다고 하더라구요! 그래서 사실 그런데 현재는 이미지 아무것도 안넣었을때, null을 던져준다고 합니다!(추후 디폴트 url로 바꿀 예정이래요) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 혹시 api 연결 오류로 undefined 일 수 있으니 이후에도 계속 유지해도 될듯 합니다! |
||
/> | ||
)} | ||
<Text textStyle="bold_3xl">{name}</Text> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
export interface TitleProps { | ||
import { Team } from '@/types'; | ||
|
||
export interface TitleProps extends Omit<Team, 'id'> { | ||
isTeam?: boolean; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제가 헷갈려서 그런데, 스터디 페이지에선 이미지를 안 쓰지 않나요? 그리고 Title은 공통 컴포넌트라서 꼭 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 음음 그렇네용..! 아예 타입을 따로 만드는게 더 좋을까요?? 원래대로 도르마무!! export interface TitleProps {
isTeam?: boolean;
name: string;
description: string;
imageUrl?: string;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 캬햐 사실 방금 디벨롭 풀받으면서 충돌 해결하다가,,, 이 코드로 푸시를 해버려씁니다..! |
||
teamImg?: string; | ||
name: string; | ||
description: string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Team } from '@/types'; | ||
|
||
export interface DeleteTeamModalProps extends Pick<Team, 'id' | 'name'> { | ||
isOpen: boolean; | ||
onClose: () => void; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<boolean>(false); | ||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false); | ||
|
||
return ( | ||
<Flex gap="2" mb="8"> | ||
<Button | ||
w="fit-content" | ||
px="4" | ||
py="1" | ||
color="white" | ||
bg="orange" | ||
shadow="md" | ||
_hover={{ bg: 'orange' }} | ||
aria-label="" | ||
onClick={() => setIsEditModalOpen(true)} | ||
size="xs" | ||
> | ||
수정 | ||
</Button> | ||
<Button | ||
w="fit-content" | ||
px="4" | ||
py="1" | ||
color="black" | ||
bg="white" | ||
shadow="md" | ||
_hover={{ bg: 'white' }} | ||
aria-label="delete-team-button" | ||
onClick={() => setIsDeleteModalOpen(true)} | ||
size="xs" | ||
> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요 부분은 스터디코드 슬쩍해와씁니다! |
||
삭제 | ||
</Button> | ||
{isEditModalOpen && ( | ||
<TeamModal teamInfo={teamInfo} isOpen={isEditModalOpen} onClose={() => setIsEditModalOpen(false)} /> | ||
)} | ||
{isDeleteModalOpen && ( | ||
<DeleteTeamModal | ||
id={teamInfo?.id} | ||
name={teamInfo?.name} | ||
isOpen={isDeleteModalOpen} | ||
onClose={() => setIsDeleteModalOpen(false)} | ||
/> | ||
)} | ||
</Flex> | ||
); | ||
}; | ||
|
||
export default TeamControlPanel; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { TeamDetail } from '@/types'; | ||
|
||
export interface TeamControlPanelProps { | ||
teamInfo: TeamDetail; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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>(''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thumbnailPath는 서버에서 받는 이미지 주소입니다 |
||
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; | ||
}; | ||
Comment on lines
+44
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 함수 분리하니까 예쁘네요👍 |
||
|
||
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(); | ||
}); | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. handleEditTeamButtonClick, handleAddTeamButtonClick을 잘하면 좀 더 깔끔하게 짤 수 있지 않을까.. 했지만,, 지금은 아무 생각도 나지 않기때문에,,, 배포이후로 미루겠습니다 핫핫! 혹시 좋은 방법있으시면 추천 부탁드립니다! |
||
|
||
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} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
state를 초기화하고 modal을 닫는 동작이, 해당 104 line 말고 101, 67, 87 line에 다 들어간단 말이죠.. 물론 const `handleCloseModalButtonClick` = () => {resetAndCloseModal();}
const `handleCancleButtonClick` = () => {resetAndCloseModal();} 이렇게 함수 2개 만들면 핸들러 네이밍 붙힐 수 있긴 한데용,, 뭔가 굳이라는 생각이 들었습니다.... |
||
mainButtonText={teamInfo ? '수정' : '생성'} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 피그마엔 '생성' 대신 '저장'으로 되어있어서 스터디 모달도 '저장'으로 되어있는데, 하나로 통일하는 게 좋을 것 같아요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 음음 혹시 |
||
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(''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서는 만약 사용자가 이미지를 업로드 했다면, 서버 이미지를 그냥 빈 문자열로 바꿔줬습니다. 165 line에 보이는 것처럼, 둘중 하나만 보이도록 하려구요! 근데 이렇게 되면 다시 이미지 되돌리기를 못한다는 단점이 있습니다.. 사용자 입장에서는 이미지를 혹시라도 잘못 올리면(api는 안보냈지만, 프론트 상에서 이미지를 올렸을때), 원래 이미지로 못 돌아가고, 취소하기 위해서는 아예 모달창을 껐다 켜야 합니다.. 이부분은 썸네일 올리는 부분에 [취소] 버튼을 넣는다거나 해서... 아예 이미지 업로드 공통 컴포넌트를 만드는게 좋아보입니다! (배포 이후...) |
||
} | ||
}} | ||
/> | ||
|
@@ -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)} /> | ||
)} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
둘 중 1개만 보이도록 했습니다! |
||
</Flex> | ||
</Flex> | ||
</ActionModal> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
import { Team } from '@/types'; | ||
|
||
export interface TeamModalProps { | ||
teamInfo?: Team; | ||
isOpen: boolean; | ||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>; | ||
onClose: () => void; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 백엔드 이미지 아무거나 가져왔습니다! |
||
attendanceRate: 50, | ||
}; | ||
export default teamInfoData; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TeamDetail은 팀 조회에서만 쓰입니다. export interface Team {
readonly id: number;
name: string;
description: string;
imageUrl: string;
attendanceRate? number;
} 이것보다요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그리고 TeamDetail 타입명 짓기가 조금 어려웠는데요.. 제가 Team 타입의 데이터를 저장하는 변수를 만들때, const teamInfo: Team = {어쩌구 저쩌구} 그래서 보통 teamInfo를 붙혀주니까, 팀 조회에서 쓰여야 할 "팀 정보"의 타입(지금 보이는 더 좋은 방법/네이밍 있으시면 추천부탁드립니닷!! |
||
} | ||
|
||
export interface Curriculum { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아하! 이런 식으로 Pick 해서 쓰는 거군요! 새로 배워갑니다🤩