diff --git a/src/App.tsx b/src/App.tsx index da4f346..d3c73d8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import RetroRevisePage from './pages/RevisePage'; import RetroTeamPage from './pages/SectionPage'; import MainLayout from '@/components/layout/MainLayout'; import ProfileLayout from '@/components/layout/ProfileLayout'; +// import AcceptInvitePage from '@/pages/AccpetInvitePage'; import AuthPage from '@/pages/AuthPage'; import CreateRetroPage from '@/pages/CreateRetroPage'; import HomePage from '@/pages/HomePage'; @@ -131,7 +132,9 @@ const App = () => { } /> - + {/* 발급 될 초대 링크 */} + + {/* } /> */} diff --git a/src/api/@types/Comment.ts b/src/api/@types/Comment.ts index 5e12045..2a46852 100644 --- a/src/api/@types/Comment.ts +++ b/src/api/@types/Comment.ts @@ -1,48 +1,50 @@ -export interface GetCommentRequest { - id: string; +//post +export interface PostCommentRequest { + sectionId: number; + commentContent: string; } -export interface GetCommentResponse { +export interface PostCommentResponse { code: number; message: string; - data: { - id: number; - content: string; - }; + data: PostCommentData; } -//put -export interface PutCommentRequest { +export interface PostCommentData { id: number; + userId: number; + sectionId: number; + commentContent: string; } -//delete -export interface DeleteCommentRequest { - id: number; +//put +export interface PutCommentRequest { + commentId: number; + commentContent: string; } -export interface DeleteCommentResponse { +export interface PutCommentResponse { code: number; message: string; - data: object; + data: PutCommentData; } -//GetAllComment -export interface AllGetCommentResponse { - code: number; - message: string; - data: CommentData[]; +export interface PutCommentData { + commentId: number; + content: string; } -export interface CommentData { - id: number; - comment: string; +//delete +export interface DeleteCommentRequest { + commentId: number; } -//Post +export interface DeleteCommentResponse { + code: number; +} export interface CommentClient { - getComment(request: GetCommentRequest): Promise; + post(request: PostCommentRequest): Promise; delete(request: DeleteCommentRequest): Promise; - getAllComment(): Promise; + put(request: PutCommentRequest): Promise; } diff --git a/src/api/@types/InviteTeam.ts b/src/api/@types/InviteTeam.ts index 670852e..7102972 100644 --- a/src/api/@types/InviteTeam.ts +++ b/src/api/@types/InviteTeam.ts @@ -20,4 +20,3 @@ export interface InviteTeamData { export interface PostInviteTeamRequest { invitationCode: string; } -export interface PostInviteTeamResponse {} diff --git a/src/api/@types/Retrospectives.ts b/src/api/@types/Retrospectives.ts index 2d30cf3..f32c722 100644 --- a/src/api/@types/Retrospectives.ts +++ b/src/api/@types/Retrospectives.ts @@ -19,7 +19,7 @@ export interface RetrospectiveData { userId: number; leaderName: string; description: string; - status: keyof TStatus; + status: string; thumbnail: string; } @@ -46,6 +46,7 @@ export interface GetRetrospectiveResponseNodes { startDate: string; createdDate: string; updatedDate: string; + username: string; } export interface GetRetrospectiveData { @@ -84,12 +85,20 @@ export interface DeleteRetrospectiveRequest { } //put -export interface PutRetrospectiveRequest { +export interface PutTeamRetrospectiveRequest { retrospectiveId: number; title: string; teamId?: number; description: string; - status: keyof TStatus; + status: string; + thumbnail?: string; +} + +export interface PutPersonalRetrospectiveRequest { + retrospectiveId: number; + title: string; + description: string; + status: string; thumbnail?: string; } @@ -127,6 +136,7 @@ export interface RetrospectivesClient { create(request: PostRetrospectivesRequest): Promise; get(request: GetRetrospectiveRequest): Promise; delete(request: DeleteRetrospectiveRequest): Promise; - put(request: PutRetrospectiveRequest): Promise; + putTeam(request: PutTeamRetrospectiveRequest): Promise; + putPersonal(request: PutPersonalRetrospectiveRequest): Promise; patch(request: PatchRetrospectiveRequest): Promise; } diff --git a/src/api/@types/Survey.ts b/src/api/@types/Survey.ts index c5c02db..ee816f5 100644 --- a/src/api/@types/Survey.ts +++ b/src/api/@types/Survey.ts @@ -5,7 +5,8 @@ export interface PostSurveyRequest { occupation: string; region: string; source: string; - purpose: string; + purposes: string[] | undefined; + } export interface PostSurveyResponse { diff --git a/src/api/@types/TeamController.tsx b/src/api/@types/TeamController.tsx index 1389bfa..096d54f 100644 --- a/src/api/@types/TeamController.tsx +++ b/src/api/@types/TeamController.tsx @@ -1,3 +1,4 @@ +// 팀원 조회 export interface GetTeamMembersRequest { teamId: number; retrospectiveId: number; @@ -35,7 +36,20 @@ export interface TemplateNameData { sequence: number; } +// put 담당자 +export interface PutActionItemsRequest { + teamId: number; + retrospectiveId: number; + sectionId: number; +} + +export interface PutActionItemsResponse { + code: number; + message: string; + data: object; +} export interface TeamControllerClient { TeamMemberGet(request: GetTeamMembersRequest): Promise; TemplateNameGet(request: GetTemplateNameRequest): Promise; + ActionItemsMemberPut(request: PutActionItemsRequest): Promise; } diff --git a/src/api/@types/User.ts b/src/api/@types/User.ts index dff78c6..e070478 100644 --- a/src/api/@types/User.ts +++ b/src/api/@types/User.ts @@ -1,14 +1,14 @@ // get -export interface GetUsersRequest { - // userId: number; -} - export interface GetUsersResponse { - userId: number; - username: string; - email: string; - thumbnail: string | null; - phone: string | null; - createDate: Date; - updateDate: Date; + code: number; + data: { + userId: number; + userName: string; + email: string; + thumbnail: string | null; + phone: string | null; + createDate: Date; + updateDate: Date; + }; + message: string | null; } diff --git a/src/api/inviteTeamApi/postInviteTeam.tsx b/src/api/inviteTeamApi/postInviteTeam.tsx index 394bb16..10867c1 100644 --- a/src/api/inviteTeamApi/postInviteTeam.tsx +++ b/src/api/inviteTeamApi/postInviteTeam.tsx @@ -3,8 +3,8 @@ import axiosInstance from '../axiosConfig'; const postInviteTeam = async (invitationId: string) => { try { - const response = await axiosInstance.post('/team/accept-invitation', { - invitationCode: invitationId, + const response = await axiosInstance.post('/teams/accept-invitation', { + invitationCode: invitationId, // useParams 로 받아온 코드 }); console.log('팀원 초대 성공', response.data); return response.data; diff --git a/src/api/services/Comment.ts b/src/api/services/Comment.ts index 055e099..cd0db7f 100644 --- a/src/api/services/Comment.ts +++ b/src/api/services/Comment.ts @@ -1,16 +1,31 @@ -import { CommentClient } from '../@types/Comment'; -import { mswInstance } from '../client'; +import { CommentClient, DeleteCommentRequest, PostCommentRequest, PostCommentResponse } from '../@types/Comment'; +import axiosInstance from '../axiosConfig'; const ROUTE = '/comments'; export const CommentService: CommentClient = { - getComment: async id => { - return await mswInstance.get(`/api/${ROUTE}/${id}`); + post: async (request: PostCommentRequest): Promise => { + try { + const response = await axiosInstance.post(`${ROUTE}`, request); + return response.data; + } catch (error) { + throw new Error(error as string); + } }, - delete: async id => { - return await mswInstance.delete(`/api/${ROUTE}/${id}`); + delete: async ({ commentId }: DeleteCommentRequest) => { + try { + const response = await axiosInstance.delete(`${ROUTE}/${commentId}`); + return response.data; + } catch (error) { + throw new Error(error as string); + } }, - getAllComment: async () => { - return await mswInstance.get(`api/${ROUTE}`); + put: async ({ commentId, ...request }) => { + try { + const response = await axiosInstance.put(`${ROUTE}/${commentId}`, request); + return response.data; + } catch (error) { + throw new Error(error as string); + } }, }; diff --git a/src/api/services/Retrospectives.ts b/src/api/services/Retrospectives.ts index c130d58..0bd58a2 100644 --- a/src/api/services/Retrospectives.ts +++ b/src/api/services/Retrospectives.ts @@ -49,7 +49,16 @@ export const RetrospectiveService: RetrospectivesClient = { } }, - put: async ({ retrospectiveId }, ...request) => { + putTeam: async ({ retrospectiveId, ...request }) => { + try { + const response = await axiosInstance.put(`${ROUTE}/${retrospectiveId}`, request); + return response.data; + } catch (error) { + throw new Error(error as string); + } + }, + + putPersonal: async ({ retrospectiveId, ...request }) => { try { const response = await axiosInstance.put(`${ROUTE}/${retrospectiveId}`, request); return response.data; diff --git a/src/api/services/TeamController.ts b/src/api/services/TeamController.ts index 978a7cd..e40f33c 100644 --- a/src/api/services/TeamController.ts +++ b/src/api/services/TeamController.ts @@ -3,8 +3,10 @@ import { GetTemplateNameRequest, GetTemplateNameResponse, TeamControllerClient, -} from '../@types/TeamController'; -import axiosInstance from '../axiosConfig'; + PutActionItemsRequest, + PutActionItemsResponse, +} from '@/api/@types/TeamController'; +import axiosInstance from '@/api/axiosConfig'; const TEAMS_ROUTE = 'teams'; const TEMPLATE_ROUTE = 'retrospective-templates'; @@ -26,4 +28,12 @@ export const TeamControllerServices: TeamControllerClient = { throw new Error(error as string); } }, + ActionItemsMemberPut: async (request: PutActionItemsRequest): Promise => { + try { + const response = await axiosInstance.put(`/sections/action-itmes`, request); + return response.data; + } catch (error) { + throw new Error(error as string); + } + }, }; diff --git a/src/api/survey/postSurvey.tsx b/src/api/survey/postSurvey.tsx index ace0538..79f11cb 100644 --- a/src/api/survey/postSurvey.tsx +++ b/src/api/survey/postSurvey.tsx @@ -4,7 +4,7 @@ import axiosInstance from '../axiosConfig'; // post 요청 export const PostSurvey = async (requestData: PostSurveyRequest): Promise => { try { - const response = await axiosInstance.post('/surveys/response', requestData); + const response = await axiosInstance.post('/surveys/responses', requestData); console.log('설문조사 전송 성공', response.data); return response.data; } catch (error) { diff --git a/src/api/teamControllerApi/getMember.tsx b/src/api/teamControllerApi/getMember.tsx new file mode 100644 index 0000000..c946fdf --- /dev/null +++ b/src/api/teamControllerApi/getMember.tsx @@ -0,0 +1,15 @@ +import { GetTeamMembersRequest, GetTeamMembersResponse } from '@/api/@types/TeamController'; +import axiosInstance from '@/api/axiosConfig'; + +export const getMember = async ({ + teamId, + retrospectiveId, +}: GetTeamMembersRequest): Promise => { + try { + const response = await axiosInstance.get(`/teams/${teamId}/users?${retrospectiveId}=`); + console.log('팀 멤버 조회 성공', response.data); + return response.data; + } catch (error) { + throw new Error('팀 멤버 조회 실패'); + } +}; diff --git a/src/api/teamControllerApi/putActionItemsMember.tsx b/src/api/teamControllerApi/putActionItemsMember.tsx new file mode 100644 index 0000000..dccf514 --- /dev/null +++ b/src/api/teamControllerApi/putActionItemsMember.tsx @@ -0,0 +1,13 @@ +import { PutActionItemsRequest, PutActionItemsResponse } from '@/api/@types/TeamController'; +import axiosInstance from '@/api/axiosConfig'; + +export const putActionItemsMember = async (requestData: PutActionItemsRequest): Promise => { + try { + // console.log(requestData); + const response = await axiosInstance.put('/sections/action-items', requestData); + // console.log('action item 멤버 저장 성공', response); + return response.data; + } catch (error) { + throw new Error('action item 멤버 저장 실패'); + } +}; diff --git a/src/assets/BookmarkIcon_N.png b/src/assets/BookmarkIcon_N.png deleted file mode 100644 index 176bef5..0000000 Binary files a/src/assets/BookmarkIcon_N.png and /dev/null differ diff --git a/src/assets/BookmarkIcon_Y.png b/src/assets/BookmarkIcon_Y.png deleted file mode 100644 index 786cd9f..0000000 Binary files a/src/assets/BookmarkIcon_Y.png and /dev/null differ diff --git a/src/assets/Link.png b/src/assets/Link.png deleted file mode 100644 index 45aa0fb..0000000 Binary files a/src/assets/Link.png and /dev/null differ diff --git a/src/assets/TeamIcon.png b/src/assets/TeamIcon.png deleted file mode 100644 index 6a00cfe..0000000 Binary files a/src/assets/TeamIcon.png and /dev/null differ diff --git a/src/assets/UserProfile2.png b/src/assets/UserProfile2.png deleted file mode 100644 index 1387298..0000000 Binary files a/src/assets/UserProfile2.png and /dev/null differ diff --git a/src/components/RetroList/ContentsList.tsx b/src/components/RetroList/ContentsList.tsx index d13c4ee..b52b03c 100644 --- a/src/components/RetroList/ContentsList.tsx +++ b/src/components/RetroList/ContentsList.tsx @@ -1,22 +1,16 @@ import { useEffect, useState } from 'react'; import { CgTimelapse } from 'react-icons/cg'; // ing -import { CiStar } from 'react-icons/ci'; -import { FaStar } from 'react-icons/fa'; import { FaRegCircleCheck } from 'react-icons/fa6'; // done -import { HiOutlineDotsHorizontal } from 'react-icons/hi'; import { IoMdPerson } from 'react-icons/io'; import { MdPeople } from 'react-icons/md'; import { RxCounterClockwiseClock } from 'react-icons/rx'; //before import { useNavigate } from 'react-router-dom'; -import { useRecoilState } from 'recoil'; import { PatchRetrospectiveRequest } from '@/api/@types/Retrospectives'; import postImageToS3 from '@/api/imageApi/postImageToS3'; import { patchRetrospective } from '@/api/retrospectivesApi/patchRetrospective'; import Thumbnail from '@/assets/Thumbnail.png'; import Modal from '@/components/RetroList/Modal'; -import UserNickname from '@/components/user/UserNickname'; import { useCustomToast } from '@/hooks/useCustomToast'; -import { userNicknameState } from '@/recoil/user/userAtom'; import * as S from '@/styles/RetroList/ContentsList.styles'; interface Content { @@ -31,6 +25,7 @@ interface Content { startDate: string; createdDate: string; updatedDate: string; + username: string; } interface ContentListProps { @@ -42,7 +37,6 @@ interface ContentListProps { const ContentList: React.FC = ({ data, viewMode, searchData, setBookmarkUpdate }) => { // const [contentData, setContentData] = useState(data); 받아온데이터 - const [userNickname, setUserNickname] = useRecoilState(userNicknameState); const [openModalId, setOpenModalId] = useState(null); const toast = useCustomToast(); const [image, setImage] = useState<{ [key: number]: string }>({}); @@ -73,6 +67,19 @@ const ContentList: React.FC = ({ data, viewMode, searchData, s console.log('filter', filteredData); const navigate = useNavigate(); + const convertToLocalTime = (dateString: string | number | Date) => { + const date = new Date(dateString); + const localTime = new Date(date.getTime() - date.getTimezoneOffset() * 60000); + const options: Intl.DateTimeFormatOptions = { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + }; + return localTime.toLocaleString(undefined, options); // 로컬 타임존으로 변환하여 문자열로 반환 + }; + useEffect(() => { const fetchThumbnailsData = async (item: Content) => { try { @@ -121,10 +128,10 @@ const ContentList: React.FC = ({ data, viewMode, searchData, s }} > {item.isBookmarked && ( - handleBookmark(item.id)} style={{ color: '#fcea12' }} size="19" /> + handleBookmark(item.id)} style={{ color: '#fcea12' }} size="19" /> )} - {!item.isBookmarked && handleBookmark(item.id)} size={20} />} - handleBookmark(item.id)} size={20} />} + openModalForItem(item.id)} @@ -138,15 +145,13 @@ const ContentList: React.FC = ({ data, viewMode, searchData, s }} /> - - {/* 생성자 이름(유저 식별 필요) */} - {userNickname} - + {item.username}
- {item.updatedDate && item.updatedDate !== item.startDate - ? `${item.updatedDate} 수정` - : item.startDate} + {item.updatedDate !== item.createdDate + ? `${convertToLocalTime(item.updatedDate)} 수정` + : convertToLocalTime(item.createdDate)} + {/* {item.updatedDate !== item.createdDate ? `${item.updatedDate} 수정` : item.createdDate} */} {item.status === 'NOT_STARTED' && ( = ({ data, viewMode, searchData, s navigate(`/section?retrospectiveId=${item.id}&teamId=${item.teamId}`)}> {item.title} - - {/* 생성자이름(유저 식별 필요) */} - {userNickname} - + {item.username} {item.updatedDate && item.updatedDate !== item.startDate ? `${item.updatedDate}` : item.startDate} {item.isBookmarked && ( - handleBookmark(item.id)} style={{ color: '#fcea12' }} size="19" /> + handleBookmark(item.id)} style={{ color: '#fcea12' }} size="19" /> )} - {!item.isBookmarked && handleBookmark(item.id)} size={20} />} + {!item.isBookmarked && handleBookmark(item.id)} size={20} />} {item.status === 'NOT_STARTED' && ( - @@ -214,7 +216,7 @@ const ContentList: React.FC = ({ data, viewMode, searchData, s )} - openModalForItem(item.id)} diff --git a/src/components/createRetro/modal/ImageUpload.tsx b/src/components/createRetro/modal/ImageUpload.tsx index c5fbdb2..f8eafa9 100644 --- a/src/components/createRetro/modal/ImageUpload.tsx +++ b/src/components/createRetro/modal/ImageUpload.tsx @@ -45,16 +45,21 @@ const ImageUpload: React.FC = ({ onChange }) => { - {/* 이미지 미리보기 */} - {imagePreview && ( -
- Selected Image - -
- )} +
+ {imagePreview && ( + +
+ Selected Image +
+
+ +
+
+ )} +
); diff --git a/src/components/inviteTeam/AcceptInvite.tsx b/src/components/inviteTeam/AcceptInvite.tsx index bf8812b..e60f205 100644 --- a/src/components/inviteTeam/AcceptInvite.tsx +++ b/src/components/inviteTeam/AcceptInvite.tsx @@ -1,38 +1,55 @@ import React, { useEffect, useState } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { Button, Text } from '@chakra-ui/react'; import postInviteTeam from '@/api/inviteTeamApi/postInviteTeam'; +import * as S from '@/styles/inviteTeam/AcceptInvite'; const AcceptInvite: React.FC = () => { - const { invitationId } = useParams<{ invitationId?: string }>(); + const location = useLocation(); const navigate = useNavigate(); const [inviteSuccess, setInviteSuccess] = useState(false); - useEffect(() => { - const acceptInvitation = async () => { - try { - if (invitationId) { - await postInviteTeam(invitationId); + const searchParams = new URLSearchParams(location.search); + const invitationCode = searchParams.get('invitationId'); + + const acceptInvitation = async () => { + try { + if (invitationCode) { + const response = await postInviteTeam(invitationCode); + // 백엔드에서 204 반환해줌 + if (response.status === 204) { setInviteSuccess(true); // 초대 요청이 성공했을 때 상태를 true로 변경 } else { - console.error('InvitationId 추출 실패'); + console.error('초대 수락 실패'); } - } catch (error) { - console.error('에러', error); + } else { + console.error('invitationCode 추출 실패'); } - }; - - acceptInvitation(); - }, [invitationId]); + } catch (error) { + console.error('에러', error); + } + }; useEffect(() => { if (inviteSuccess) { // 초대 성공 시 알림을 띄우고 retrolist 페이지로 navigate - alert('초대 성공했습니다!'); + alert('초대를 성공적으로 수락했습니다!'); navigate('/retrolist'); } }, [inviteSuccess, navigate]); - return
초대를 수락하는 중...
; + return ( + <> + + + 초대를 수락하시겠습니까? + + + + + ); }; export default AcceptInvite; diff --git a/src/components/inviteTeam/InviteTeamModal.tsx b/src/components/inviteTeam/InviteTeamModal.tsx index ad48fa7..f440270 100644 --- a/src/components/inviteTeam/InviteTeamModal.tsx +++ b/src/components/inviteTeam/InviteTeamModal.tsx @@ -46,10 +46,17 @@ const InviteTeamModal: React.FC = ({ isOpen, onClose, team fetchInviteData(); }, []); + const generateInvitationUrl = (invitationCode: string) => { + // const domain = 'http://localhost:3000'; // 로컬 테스트용 + const domain = 'https://www.pastforward.link'; // 배포용 + return `${domain}/invitations?invitationId=${invitationCode}`; + }; + const copyToClipboard = () => { if (inviteData) { // inviteData가 null이 아닐 때만 실행 - navigator.clipboard.writeText(inviteData.invitationUrl).then( + const invitationUrl = generateInvitationUrl(inviteData.invitationCode); + navigator.clipboard.writeText(invitationUrl).then( () => { setShowAlert(true); // 알림 표시 }, @@ -71,11 +78,11 @@ const InviteTeamModal: React.FC = ({ isOpen, onClose, team {inviteData && ( - + QR과 Link를 통해 팀원을 초대하여 회고를 함께하세요! - + diff --git a/src/components/layout/parts/PageSideBar.tsx b/src/components/layout/parts/PageSideBar.tsx index 3cb52ff..c9e0256 100644 --- a/src/components/layout/parts/PageSideBar.tsx +++ b/src/components/layout/parts/PageSideBar.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { PeopleFill, Person, PersonCircle, PersonFill, PlusCircleFill } from 'react-bootstrap-icons'; +import { IoArrowUndoSharp } from 'react-icons/io5'; import { Accordion, AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, Flex } from '@chakra-ui/react'; import { useRecoilState } from 'recoil'; import Search_SideBar from './Search_SideBar'; @@ -92,6 +93,16 @@ const PageSideBar = () => { {/* Create New Retro */} + + + + + + Move to the List + + + + diff --git a/src/components/survey/GenderRadio.tsx b/src/components/survey/GenderRadio.tsx index 88958ab..5287be8 100644 --- a/src/components/survey/GenderRadio.tsx +++ b/src/components/survey/GenderRadio.tsx @@ -7,7 +7,8 @@ interface Gender { } const GenderRadio: React.FC = ({ onGenderChange }) => { - const [gender, setGender] = useState('female'); + const [gender, setGender] = useState('FEMALE'); + const handleGenderChange = (event: React.ChangeEvent) => { const newGender = event.target.value; setGender(newGender); @@ -21,10 +22,10 @@ const GenderRadio: React.FC = ({ onGenderChange }) => { - + 여성 - + 남성 diff --git a/src/components/survey/PurposeCheckbox.tsx b/src/components/survey/PurposeCheckbox.tsx index bf76002..4ff00a0 100644 --- a/src/components/survey/PurposeCheckbox.tsx +++ b/src/components/survey/PurposeCheckbox.tsx @@ -1,15 +1,26 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; + import { Checkbox, Stack, Input, Text } from '@chakra-ui/react'; import * as S from '@/styles/survey/PurposeCheckbox.style'; interface Purpose { - onPurposeChange: (purpose: string) => void; - onOtherPurposeChange: (otherPurpose: string) => void; - // onPurposeChange: (purpose: string[]) => void; + onPurposeChange: (purpose: string[]) => void; } -const PurposeCheckbox: React.FC = ({ onPurposeChange, onOtherPurposeChange }) => { +const PurposeCheckbox: React.FC = ({ onPurposeChange }) => { const [checkedPurposes, setCheckedPurposes] = useState([]); + const [otherPurpose, setOtherPurpose] = useState(''); + const [isOtherSelected, setIsOtherSelected] = useState(false); + + useEffect(() => { + const updatedPurposes = [...checkedPurposes]; + if (isOtherSelected) { + updatedPurposes.push(otherPurpose); + } + onPurposeChange(updatedPurposes); + }, [checkedPurposes, otherPurpose, isOtherSelected, onPurposeChange]); + + // purposes const handlePurposeChange = (event: React.ChangeEvent) => { const isChecked = event.target.checked; @@ -17,20 +28,19 @@ const PurposeCheckbox: React.FC = ({ onPurposeChange, onOtherPurposeCha let updatedPurposes: string[]; if (isChecked) { - updatedPurposes = [...checkedPurposes, purpose]; // 체크된 경우 배열에 추가 + updatedPurposes = [...checkedPurposes, purpose]; } else { - updatedPurposes = checkedPurposes.filter(item => item !== purpose); // 체크 해제된 경우 배열에서 제거 + updatedPurposes = checkedPurposes.filter(item => item !== purpose); } - setCheckedPurposes(updatedPurposes); // 최종 업데이트 한번만 수행 - onPurposeChange(updatedPurposes.join(', ')); // 선택된 목적들을 문자열로 변환하여 전달 + setCheckedPurposes(updatedPurposes); }; - const [otherPurpose, setOtherPurpose] = useState(''); + // otherPurpose const handleOtherPurposeChange = (event: React.ChangeEvent) => { const newOtherPurpose = event.target.value; setOtherPurpose(newOtherPurpose); - onOtherPurposeChange(newOtherPurpose); + setIsOtherSelected(!!newOtherPurpose); }; return ( @@ -58,7 +68,10 @@ const PurposeCheckbox: React.FC = ({ onPurposeChange, onOtherPurposeCha - 기타 + + 기타 + + = ({ onPurposeChange, onOtherPurposeCha }; export default PurposeCheckbox; - -// import React from 'react'; -// import { Checkbox, Stack, Input, Text } from '@chakra-ui/react'; -// import * as S from '@/styles/survey/PurposeCheckbox.style'; - -// interface Purpose { -// onPurposeChange: (city: string) => void; -// } - -// const PurposeCheckbox: React.FC = ({ onPurposeChange }) => { -// const [purpose, setPurpose] = useState('서울'); -// const handlePurposeChange = (event: React.ChangeEvent) => { -// const newPurpose = event.target.value; -// setPurpose(newPurpose); -// onPurposeChange(newPurpose); -// }; - -// return ( -// <> -// -// Past-Forward 서비스를 알게 된 경로는 무엇입니까? -// (복수 선택 가능) -// -// -// -// 업무 목적 -// 개인 발전 -// 팀 협업 -// 프로젝트 관리 -// 학습 및 개선 -// -// -// 기타 -// -// -// -// -// -// -// ); -// }; diff --git a/src/components/writeRetro/task/ActionItemTask.tsx b/src/components/writeRetro/ActionItems/ActionItemTask.tsx similarity index 50% rename from src/components/writeRetro/task/ActionItemTask.tsx rename to src/components/writeRetro/ActionItems/ActionItemTask.tsx index 1ac2cfd..9e5769b 100644 --- a/src/components/writeRetro/task/ActionItemTask.tsx +++ b/src/components/writeRetro/ActionItems/ActionItemTask.tsx @@ -1,25 +1,46 @@ -import React, { FC, useState } from 'react'; -import { sectionData } from '@/api/@types/Section'; -import UserProfile1 from '@/assets/UserProfile1.png'; -import UserProfile2 from '@/assets/UserProfile2.png'; +import React, { FC, useState, useEffect } from 'react'; +import { TeamControllerServices } from '@/api/services/TeamController'; import Members from '@/components/writeRetro/ActionItems/Members'; +import { useCustomToast } from '@/hooks/useCustomToast'; import * as S from '@/styles/writeRetroStyles/Layout.style'; -interface Props { - section: sectionData[]; +interface ActionItemTaskProps { + tId: number; + rId: number; + sId: number; } -const ActionItemTask: FC = ({ section }) => { - // action items 담당자 지정 +const ActionItemTask: FC = ({ tId, rId, sId }) => { const [hoveredUser, setHoveredUser] = useState(null); const [showPopup, setShowPopup] = useState(false); const [selectedUserName, setSelectedUserName] = useState(null); const [selectedUserImg, setSelectedUserImg] = useState(null); - const users = [ - { name: 'User 1', image: UserProfile1 }, - { name: 'User 2', image: UserProfile2 }, - ]; + const teamId: number = tId; + const retrospectiveId: number = rId; + const sectionId: number = sId; + + const [users, setUsers] = useState<{ name: string; image: string }[]>([]); + const toast = useCustomToast(); + + const fetchTeamMember = async () => { + try { + if (teamId) { + const data = await TeamControllerServices.TeamMemberGet({ teamId: teamId, retrospectiveId: retrospectiveId }); + const userData = data.data.map(member => ({ + name: member.username, + image: member.profileImage, + })); + setUsers(userData); + } + return; + } catch (e) { + toast.error('멤버 조회 실패'); + } + }; + useEffect(() => { + fetchTeamMember(); + }, []); const togglePopup = () => { setShowPopup(!showPopup); @@ -50,15 +71,19 @@ const ActionItemTask: FC = ({ section }) => { onMouseLeave={handleMouseLeave} > {selectedUserImg ? : 'M'} - {hoveredUser && {hoveredUser}} {/* 이름 : {name.username} */} + {hoveredUser && {hoveredUser}} {showPopup && ( - + )} - {section.map(section => ( - {section.username} - ))} ); }; diff --git a/src/components/writeRetro/ActionItems/Members.tsx b/src/components/writeRetro/ActionItems/Members.tsx index 67e9546..56d052f 100644 --- a/src/components/writeRetro/ActionItems/Members.tsx +++ b/src/components/writeRetro/ActionItems/Members.tsx @@ -1,15 +1,42 @@ +import { PutActionItemsRequest } from '@/api/@types/TeamController'; +import { putActionItemsMember } from '@/api/teamControllerApi/putActionItemsMember'; +import UserProfile from '@/assets/UserProfile1.png'; +import { useCustomToast } from '@/hooks/useCustomToast'; import * as S from '@/styles/writeRetroStyles/Members.styles'; interface UserListProps { users: { name: string; image: string }[]; onSelectUserImg: (image: string) => void; onSelectUserName: (name: string) => void; + tId: number; + rId: number; + sId: number; } -export const Members: React.FC = ({ users, onSelectUserImg, onSelectUserName }) => { - const handleUserClick = (name: string, image: string) => { +export const Members: React.FC = ({ users, onSelectUserImg, onSelectUserName, tId, rId, sId }) => { + const teamId: number = tId; + const retrospectiveId: number = rId; + const sectionId: number = sId; + + const toast = useCustomToast(); + + const putActionItemMember = async () => { + try { + const requestData: PutActionItemsRequest = { + teamId: teamId, + retrospectiveId: retrospectiveId, + sectionId: sectionId, + }; + await putActionItemsMember(requestData); + } catch (e) { + toast.error('담당자 지정 실패'); + } + }; + + const handleUserClick = async (name: string, image: string) => { onSelectUserName(name); onSelectUserImg(image); + await putActionItemMember(); }; return ( @@ -20,8 +47,8 @@ export const Members: React.FC = ({ users, onSelectUserImg, onSel
    {users.map((user, index) => ( - handleUserClick(user.name, user.image)}> - {user.name} + handleUserClick(user.name, user.image || UserProfile)}> + {user.name} ))}
diff --git a/src/components/writeRetro/revise/RetroImageUploader.tsx b/src/components/writeRetro/revise/RetroImageUploader.tsx index bc4959a..79deb90 100644 --- a/src/components/writeRetro/revise/RetroImageUploader.tsx +++ b/src/components/writeRetro/revise/RetroImageUploader.tsx @@ -1,40 +1,51 @@ -import { ChangeEventHandler, FC, MouseEventHandler, useRef } from 'react'; +import { ChangeEventHandler, FC, MouseEventHandler, useRef, useState } from 'react'; import { MdOutlineFileUpload } from 'react-icons/md'; import { Button, Image } from '@chakra-ui/react'; +import { v4 as uuidv4 } from 'uuid'; interface Props { - image: string; - setImage: (image: string) => void; + image: string | undefined; + onChange: (image: File | null, uuid: string) => void; } -const RetroImageUploader: FC = ({ image, setImage }) => { +const RetroImageUploader: FC = ({ image, onChange }) => { + const [preview, setPreview] = useState(null); + const [_, setImageUUID] = useState(null); // 상태를 활용할 수 있도록 수정 + const inputRef = useRef(null); const handleUploadButtonClick: MouseEventHandler = () => { inputRef.current?.click(); }; const handleImageChange: ChangeEventHandler = async event => { - const files = event.target.files; - if (!files) return; + const files = event.target.files?.[0]; + if (files) { - const file = files[0]; - const imageUrl = URL.createObjectURL(file); - const imageUrlString: string = imageUrl; - setImage(imageUrlString); + const reader = new FileReader(); + // const imageUrl = URL.createObjectURL(files); + // const imageUrlString: string = imageUrl; + const uuid = uuidv4(); + // const fix_uuid = uuid.replace(/\n/gi, '\\n'); + onChange(files, uuid); + + reader.onloadend = () => { + const result = reader.result as string; + setPreview(result); + setImageUUID(uuid); + }; + reader.readAsDataURL(files); } }; const DeleteImage: MouseEventHandler = () => { - setImage(''); + setPreview(null); + setImageUUID(null); + onChange(null, ''); }; return ( <> - {image ? ( - - ) : ( - - )} +
+ + + + ); +}; + +export default ReviseCommentModal; diff --git a/src/components/writeRetro/task/TeamTask.tsx b/src/components/writeRetro/task/TeamTask.tsx index 29969c7..edf61af 100644 --- a/src/components/writeRetro/task/TeamTask.tsx +++ b/src/components/writeRetro/task/TeamTask.tsx @@ -3,6 +3,7 @@ import { BiLike, BiSolidLike } from 'react-icons/bi'; import { CgProfile } from 'react-icons/cg'; import { FaRegTrashAlt } from 'react-icons/fa'; import { MdAccessAlarm, MdMessage } from 'react-icons/md'; +import { useLocation } from 'react-router-dom'; import { Button, Flex, @@ -19,7 +20,7 @@ import { formattedDate } from './PersonalTask'; import TeamTaskMessage from './taskMessage/TeamTaskMessage'; import { sectionData } from '@/api/@types/Section'; import { SectionServices } from '@/api/services/Section'; - +import ActionItemTask from '@/components/writeRetro/ActionItems/ActionItemTask'; import ReviseModal from '@/components/writeRetro/task/ReviseModal'; import { useCustomToast } from '@/hooks/useCustomToast'; import * as S from '@/styles/writeRetroStyles/Layout.style'; @@ -33,6 +34,12 @@ const TeamTask: FC = ({ section }) => { const [liked, setLiked] = useState(0); const [messaged, setMessaged] = useState(false); const [isVisible, setIsVisible] = useState(false); + const { search } = useLocation(); + + const query = search.split(/[=,&]/); + const rId = Number(query[1]); // action-items로 넘겨줄 Id값들 + const tId = Number(query[3]); + const sId: number = section.sectionId; const handleLike = async () => { try { @@ -100,6 +107,12 @@ const TeamTask: FC = ({ section }) => { {section.content} {/* (수정됨) */} + +
+ +
+ 담당자 +
@@ -123,7 +136,7 @@ const TeamTask: FC = ({ section }) => { {messaged ? : } - 0 + {section.comments.length} {/* DaysLeft */} diff --git a/src/components/writeRetro/task/taskMessage/TeamTaskMessage.tsx b/src/components/writeRetro/task/taskMessage/TeamTaskMessage.tsx index 23b3686..ce2cc0a 100644 --- a/src/components/writeRetro/task/taskMessage/TeamTaskMessage.tsx +++ b/src/components/writeRetro/task/taskMessage/TeamTaskMessage.tsx @@ -1,7 +1,23 @@ import { ChangeEvent, FC, useState } from 'react'; import { CgProfile } from 'react-icons/cg'; -import { Flex, Modal, ModalCloseButton, ModalContent, ModalOverlay, useDisclosure } from '@chakra-ui/react'; +import { FaRegTrashAlt } from 'react-icons/fa'; +import { + Button, + Flex, + Popover, + PopoverArrow, + PopoverBody, + PopoverCloseButton, + PopoverContent, + PopoverHeader, + PopoverTrigger, + Portal, +} from '@chakra-ui/react'; +import ReviseCommentModal from '../ReviseCommentModal'; +import { PostCommentData } from '@/api/@types/Comment'; import { sectionData } from '@/api/@types/Section'; +import { CommentService } from '@/api/services/Comment'; +import { useCustomToast } from '@/hooks/useCustomToast'; import * as S from '@/styles/writeRetroStyles/Layout.style'; interface Props { @@ -9,62 +25,100 @@ interface Props { } const TeamTaskMessage: FC = ({ section }) => { - const { isOpen, onOpen, onClose } = useDisclosure(); const [value, setValue] = useState(''); + const [comment, setComment] = useState(); + const toast = useCustomToast(); const handleChange = (e: ChangeEvent) => { setValue(e.target.value); e.target.style.height = 'auto'; e.target.style.height = `${e.target.scrollHeight}px`; }; + const handlePostComment = async () => { + try { + const response = await CommentService.post({ sectionId: section.sectionId, commentContent: value }); + setComment(response.data); + console.log(comment); + } catch (e) { + toast.error(e); + } + }; + + const handleDeleteComment = async (id: number) => { + try { + await CommentService.delete({ commentId: id }); + } catch (e) { + toast.error(e); + } + }; + return ( <> {/* TaskMessage */} {/* TaskMessageTop */} - 0개의 댓글 + {section.comments.length}개의 댓글 {/* TaskMessages */}
- {section.comments.map(data => ( + {section.comments.map(section => ( {/* TaskMessageTop */} - - - {data.username} + + + + {section.username} + {/* 1일 전 */} -
- + + 삭제 - -
-
+ + + + + + + 삭제요청 + + + + 선택한 회고 카드를 삭제하시겠습니까? + + + + + + + +
+ + + + {section.content} + {/* (수정됨) */} + + + + + {/* TaskTextModal */} + + + {/* TaskMessageMain */} - - {data.content} - {/* TeamActionItemsTask */} - - {/* MessageModal */} - - - - - - ))}
@@ -73,7 +127,7 @@ const TeamTaskMessage: FC = ({ section }) => { {/* AddMessage */} - 확인 + 확인 diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index 65d120e..68b7534 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -64,6 +64,14 @@ export const SectionHandlers: RequestHandler[] = [ }; return HttpResponse.json(mockLikes); }), + http.put(`${SECTION_ROUTE}/action-items`, () => { + const mockActionItems = { + code: 0, + message: 'string', + data: {}, + }; + return HttpResponse.json(mockActionItems); + }), ]; //teamMembers diff --git a/src/pages/MyPage.tsx b/src/pages/MyPage.tsx index bc1561c..a552b96 100644 --- a/src/pages/MyPage.tsx +++ b/src/pages/MyPage.tsx @@ -56,7 +56,7 @@ const MyPage = () => { - {userData?.thumbnail} + {userData?.data.thumbnail} diff --git a/src/pages/RetroListPage.tsx b/src/pages/RetroListPage.tsx index f04808d..9dd30f6 100644 --- a/src/pages/RetroListPage.tsx +++ b/src/pages/RetroListPage.tsx @@ -83,6 +83,7 @@ const RetroListPage = () => { startDate: formatDate(item.startDate), createdDate: formatDate(item.createdDate), updatedDate: formatDate(item.updatedDate), + username: item.username, })); setRetroData(rawData); }, [data.nodes]); @@ -104,6 +105,7 @@ const RetroListPage = () => { startDate: formatDate(item.startDate), createdDate: formatDate(item.createdDate), updatedDate: formatDate(item.updatedDate), + username: item.username, })); setRetroData(filtered); } else if (filterType === 'Teams') { @@ -121,6 +123,7 @@ const RetroListPage = () => { startDate: formatDate(item.startDate), createdDate: formatDate(item.createdDate), updatedDate: formatDate(item.updatedDate), + username: item.username, })); setRetroData(filtered); } else if (filterType === 'ALL') { @@ -136,6 +139,7 @@ const RetroListPage = () => { startDate: formatDate(item.startDate), createdDate: formatDate(item.createdDate), updatedDate: formatDate(item.updatedDate), + username: item.username, })); setRetroData(rawData); } diff --git a/src/pages/RevisePage.tsx b/src/pages/RevisePage.tsx index 8edef0a..2738a72 100644 --- a/src/pages/RevisePage.tsx +++ b/src/pages/RevisePage.tsx @@ -20,14 +20,16 @@ const RetroRevisePage = () => { const teamId = Number(query[3]); const [retro, setRetro] = useState(); const [members, setMembers] = useState(); - const [status, setStatus] = useState(); + const [status, setStatus] = useState('NOT_STARTED'); const toast = useCustomToast(); const FetchRetrospective = async () => { try { const data = await RetrospectiveService.onlyGet({ retrospectiveId: retrospectiveId }); setRetro(data.data); - setStatus(retro?.status); + if (retro) { + setStatus(retro.status); + } } catch (e) { toast.error(e); } diff --git a/src/pages/SectionPage.tsx b/src/pages/SectionPage.tsx index f9e2229..4327402 100644 --- a/src/pages/SectionPage.tsx +++ b/src/pages/SectionPage.tsx @@ -67,7 +67,7 @@ const RetroTeamPage = () => { fetchSection(); fetchRetrospective(); fetchTemplate(); - }, [retro?.status, template?.values, section]); + }, [retro?.status, template?.values]); return ( diff --git a/src/pages/SurveyPage.tsx b/src/pages/SurveyPage.tsx index 7e3403d..4cf9f43 100644 --- a/src/pages/SurveyPage.tsx +++ b/src/pages/SurveyPage.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; import { Text, Button, Divider } from '@chakra-ui/react'; -// import { PostSurvey } from '@/api/survey/postSurvey'; +import { PostSurvey } from '@/api/survey/postSurvey'; import AgeInput from '@/components/survey/AgeInput'; import CityRadio from '@/components/survey/CityRadio'; import GenderRadio from '@/components/survey/GenderRadio'; @@ -14,13 +15,14 @@ const SurveyPage: React.FC = () => { localStorage.setItem('surveyVisited', 'true'); }, []); + const navigate = useNavigate(); + const handleSurveyButtonClick = () => { handleSurvey(); }; const handleSurvey = async () => { try { - // + path 기타 + purpose 기타 + purpose 복수 답안 + 직업 value console.log( '나이는:', age, @@ -34,36 +36,34 @@ const SurveyPage: React.FC = () => { path, '/목적은(복수선택):', purpose, - '/기타 목적은:', - otherPurpose, ); - // const SurveyRequest = await PostSurvey({ - // age: age, - // gender: gender, - // occupation: job, - // region: city, - // source: path, - // purpose: purpose, - // otherPurpose: otherPurpose - // }); - // console.log('설문조사 전송 성공', SurveyRequest); - // alert('설문조사가 전송되었습니다.'); + const SurveyRequest = await PostSurvey({ + age: numAge, + gender: gender, + occupation: job, + region: city, + source: path, + purposes: purpose, + }); + console.log('설문조사 전송 성공', SurveyRequest); + alert('설문조사가 전송되었습니다.'); + navigate('/'); } catch (error) { console.error('실패입니다.', error); } }; const [age, setAge] = useState(''); - const [gender, setGender] = useState('female'); + const [gender, setGender] = useState('FEMALE'); const [job, setJob] = useState(''); const [city, setCity] = useState('서울'); const [path, setPath] = useState(''); - const [purpose, setPurpose] = useState(); - const [otherPurpose, setOtherPurpose] = useState(); + const [purpose, setPurpose] = useState(); const handleAgeChange = (age: string) => { setAge(age); }; + const numAge: number = parseInt(age, 10); const handleGenderChange = (gender: string) => { setGender(gender); }; @@ -76,12 +76,10 @@ const SurveyPage: React.FC = () => { const handlePathChange = (path: string) => { setPath(path); }; - const handlePurposeChange = (purpose: string) => { + const handlePurposeChange = (purpose: string[]) => { setPurpose(purpose); }; - const handleOtherPurposeChange = (otherPurpose: string) => { - setOtherPurpose(otherPurpose); - }; + return ( <> @@ -99,7 +97,7 @@ const SurveyPage: React.FC = () => { - + diff --git a/src/styles/RetroList/ContentsList.styles.ts b/src/styles/RetroList/ContentsList.styles.ts index 0d2be60..c8a7345 100644 --- a/src/styles/RetroList/ContentsList.styles.ts +++ b/src/styles/RetroList/ContentsList.styles.ts @@ -1,3 +1,6 @@ +import { CiStar } from 'react-icons/ci'; +import { FaStar } from 'react-icons/fa'; +import { HiOutlineDotsHorizontal } from 'react-icons/hi'; import styled from 'styled-components'; export const BoardContainer = styled.div` @@ -134,3 +137,20 @@ export const Icon = styled.img` cursor: pointer; } `; +export const StyledCiStar = styled(CiStar)` + &:hover { + cursor: pointer; + } +`; + +export const StyledHiOutlineDotsHorizontal = styled(HiOutlineDotsHorizontal)` + &:hover { + cursor: pointer; + } +`; + +export const StyledFaStar = styled(FaStar)` + &:hover { + cursor: pointer; + } +`; diff --git a/src/styles/inviteTeam/AcceptInvite.ts b/src/styles/inviteTeam/AcceptInvite.ts new file mode 100644 index 0000000..dac61b5 --- /dev/null +++ b/src/styles/inviteTeam/AcceptInvite.ts @@ -0,0 +1,13 @@ +import styled from 'styled-components'; + +export const Container = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100vh; +`; + +export const TextContainer = styled.div` + margin-bottom: 2rem; +`; diff --git a/src/styles/writeRetroStyles/Layout.style.ts b/src/styles/writeRetroStyles/Layout.style.ts index 9cf5618..311d5ce 100644 --- a/src/styles/writeRetroStyles/Layout.style.ts +++ b/src/styles/writeRetroStyles/Layout.style.ts @@ -177,7 +177,7 @@ export const TaskText = styled.p` vertical-align: top; display: inline-block; margin: 20px 0; - margin-top: 5px; + margin-top: 20px; &:hover { cursor: pointer; } @@ -242,16 +242,12 @@ export const TaskMessageLine = styled.div` export const TaskMessageStyle = styled.div` min-height: 35px; - display: flex; margin-top: 10px; `; export const MessageUserProfile = styled.div``; -export const MessageTopStyle = styled.div` - display: flex; - position: relative; -`; +export const MessageTopStyle = styled.div``; export const MessageUserName = styled.p` font-size: 15px; diff --git a/yarn.lock b/yarn.lock index 10c65c3..e0916ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9620,7 +9620,7 @@ react-remove-scroll@^2.5.6: react-router-dom@^6.22.3: version "6.22.3" - resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.22.3.tgz#9781415667fd1361a475146c5826d9f16752a691" integrity sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw== dependencies: "@remix-run/router" "1.15.3" @@ -11469,6 +11469,7 @@ wordwrap@~0.0.2: integrity sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw== "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + name wrap-ansi-cjs version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==