diff --git a/src/components/common/modal/Modal.tsx b/src/components/common/modal/Modal.tsx index e5191906..5675c189 100644 --- a/src/components/common/modal/Modal.tsx +++ b/src/components/common/modal/Modal.tsx @@ -1,8 +1,9 @@ import { useState } from 'react'; import { createPortal } from 'react-dom'; -import { XMarkIcon, CheckCircleIcon } from '@heroicons/react/24/outline'; +import { CheckCircleIcon } from '@heroicons/react/24/outline'; import * as S from './Modal.styled'; import ScrollPreventor from './ScrollPreventor'; +import ModalCloseButton from './ModalCloseButton'; import { useOutsideClick } from '../../../hooks/useOutsideClick'; interface ModalProps { @@ -35,9 +36,7 @@ const Modal = ({ children, isOpen, onClose }: ModalProps) => { onAnimationEnd={handleAnimationEnd} > - - - + diff --git a/src/components/common/modal/ModalCloseButton.tsx b/src/components/common/modal/ModalCloseButton.tsx new file mode 100644 index 00000000..f97a3a2f --- /dev/null +++ b/src/components/common/modal/ModalCloseButton.tsx @@ -0,0 +1,15 @@ +import * as S from './Modal.styled'; +import { XMarkIcon } from '@heroicons/react/24/outline'; +interface ModalCloseButtonProps { + onClose: () => void; +} + +function ModalCloseButton({ onClose }: ModalCloseButtonProps) { + return ( + + + + ); +} + +export default ModalCloseButton; diff --git a/src/components/common/title/Title.styled.ts b/src/components/common/title/Title.styled.ts index 3a814dcd..b19f97c6 100644 --- a/src/components/common/title/Title.styled.ts +++ b/src/components/common/title/Title.styled.ts @@ -4,6 +4,11 @@ import { TitleProps } from './Title'; export const Container = styled.h1>` font-size: ${({ theme, size }) => theme.heading[size].fontSize}; color: ${({ theme }) => theme.color.primary}; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + text-overflow: ellipsis; + overflow: hidden; @media screen and ${({ theme }) => theme.mediaQuery.tablet} { font-size: ${({ theme, size }) => theme.heading[size].tabletFontSize}; diff --git a/src/components/manageProjects/applicantList/ApplicantItem.tsx b/src/components/manageProjects/applicantList/ApplicantItem.tsx index 67a5eefa..cc26be3e 100644 --- a/src/components/manageProjects/applicantList/ApplicantItem.tsx +++ b/src/components/manageProjects/applicantList/ApplicantItem.tsx @@ -1,3 +1,4 @@ +import { useEffect, useRef } from 'react'; import { ApplicantInfo } from '../../../models/applicant'; import * as S from './ApplicantItem.styled'; @@ -13,9 +14,21 @@ function ApplicantItem({ onClick, }: ApplicantItemProps) { const isSelected = selectedApplicant === applicantData.userId; + const itemRef = useRef(null); + + useEffect(() => { + if (isSelected && itemRef.current) { + itemRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + }); + } + }, [isSelected]); + return ( <> onClick(applicantData.userId)} $passStatus={applicantData.status} diff --git a/src/components/manageProjects/applicantList/ApplicantList.tsx b/src/components/manageProjects/applicantList/ApplicantList.tsx index 2f60773f..ca8e52ba 100644 --- a/src/components/manageProjects/applicantList/ApplicantList.tsx +++ b/src/components/manageProjects/applicantList/ApplicantList.tsx @@ -16,9 +16,9 @@ function ApplicantList({ {applicantsData.map((data) => ( ))} diff --git a/src/components/manageProjects/passNonPassList/PassNonPassItem.styled.ts b/src/components/manageProjects/passNonPassList/PassNonPassItem.styled.ts index fc0a486d..47d6fa14 100644 --- a/src/components/manageProjects/passNonPassList/PassNonPassItem.styled.ts +++ b/src/components/manageProjects/passNonPassList/PassNonPassItem.styled.ts @@ -21,6 +21,7 @@ export const ItemWrapper = styled.li` export const NickName = styled.p` font-size: ${({ theme }) => theme.heading.small.fontSize}; font-weight: 400; + cursor: pointer; @media ${({ theme }) => theme.mediaQuery.tablet} { font-size: ${({ theme }) => theme.heading.small.tabletFontSize}; diff --git a/src/components/manageProjects/passNonPassList/PassNonPassItem.tsx b/src/components/manageProjects/passNonPassList/PassNonPassItem.tsx index 819c890f..fa33f26d 100644 --- a/src/components/manageProjects/passNonPassList/PassNonPassItem.tsx +++ b/src/components/manageProjects/passNonPassList/PassNonPassItem.tsx @@ -7,20 +7,26 @@ import * as S from './PassNonPassItem.styled'; interface PassNonPassItemProps { userInfo: ApplicantInfo; projectData: ProjectDetailExtended; - onClick: ({ status, userId }: useMutationParams) => void; + hanldeStatus: ({ status, userId }: useMutationParams) => void; + handleUserInfo: (userId: number) => void; } function PassNonPassItem({ userInfo, - onClick, + hanldeStatus, projectData, + handleUserInfo, }: PassNonPassItemProps) { return ( - {userInfo.User.nickname} + handleUserInfo(userInfo.userId)}> + {userInfo.User.nickname} + onClick({ status: 'WAITING', userId: userInfo.userId })} + onClick={() => + hanldeStatus({ status: 'WAITING', userId: userInfo.userId }) + } /> ); diff --git a/src/components/manageProjects/passNonPassList/PassNonPassList.tsx b/src/components/manageProjects/passNonPassList/PassNonPassList.tsx index 6953e8f7..048e8251 100644 --- a/src/components/manageProjects/passNonPassList/PassNonPassList.tsx +++ b/src/components/manageProjects/passNonPassList/PassNonPassList.tsx @@ -1,3 +1,4 @@ +import { useNavigate } from 'react-router-dom'; import { useMutationParams } from '../../../hooks/usePassNonPassMutation'; import { ApplicantInfo } from '../../../models/applicant'; import { ProjectDetailExtended } from '../../../models/projectDetail'; @@ -7,20 +8,26 @@ import * as S from './PassNonPassList.styled'; interface PassNonPassListProps { passNonPassListData: ApplicantInfo[]; projectData: ProjectDetailExtended; - onClick: ({ status, userId }: useMutationParams) => void; + handleStatus: ({ status, userId }: useMutationParams) => void; } function PassNonPassList({ passNonPassListData, projectData, - onClick, + handleStatus, }: PassNonPassListProps) { + const navigate = useNavigate(); + const handleUserInfo = (userId: number) => { + navigate(`/manage/${projectData.id}?userId=${userId}`); + }; + return ( {passNonPassListData.map((data) => ( diff --git a/src/hooks/useApplicantInfo.ts b/src/hooks/useApplicantInfo.ts index c83a2273..347ad1c8 100644 --- a/src/hooks/useApplicantInfo.ts +++ b/src/hooks/useApplicantInfo.ts @@ -1,10 +1,13 @@ import { getApplicantInfo } from '../api/applicant.api'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { applicantKey } from './queries/keys'; +import { useLocation } from 'react-router-dom'; export const useApplicantInfo = (projectId: number) => { const [selectedApplicant, setSelectedUser] = useState(null); + const location = useLocation(); + const userId = new URLSearchParams(location.search).get('userId'); const { data } = useQuery({ queryKey: [applicantKey.info, projectId, selectedApplicant], @@ -14,8 +17,12 @@ export const useApplicantInfo = (projectId: number) => { }); const handleSelectedApplicant = (userId: number) => { - setSelectedUser(userId); + if (userId) setSelectedUser(userId); }; + useEffect(() => { + handleSelectedApplicant(Number(userId)); + }, [location.search]); + return { applicantInfo: data, selectedApplicant, handleSelectedApplicant }; }; diff --git a/src/mock/mockApplicantsData.json b/src/mock/mockApplicantsData.json index 6cab8383..28b47d1e 100644 --- a/src/mock/mockApplicantsData.json +++ b/src/mock/mockApplicantsData.json @@ -1,7 +1,7 @@ [ { "id": 7, - "userId": 8, + "userId": 1, "projectId": 6, "message": "기획자에게 하고 싶은 말", "email": "devpals@mail.com", @@ -34,7 +34,7 @@ }, { "id": 7, - "userId": 9, + "userId": 2, "projectId": 6, "message": "기획자에게 하고 싶은 말", "email": "devpals@mail.com", @@ -44,14 +44,14 @@ "createdAt": "2025-01-08T17:54:43.000Z", "updatedAt": "2025-01-09T09:01:59.000Z", "User": { - "id": 9, + "id": 2, "nickname": "김개발2", "email": "devpals@mail.com", "bio": null, "profileImg": "프로필 이미지 주소", "UserSkillTag": [ { - "userId": 9, + "userId": 2, "skillTagId": 28, "createdAt": "2025-01-08T11:57:17.000Z", "SkillTag": { @@ -361,5 +361,203 @@ {} ] } + }, + { + "id": 7, + "userId": 19, + "projectId": 6, + "message": "기획자에게 하고 싶은 말", + "email": "devpals@mail.com", + "phoneNumber": "010-9999-0000", + "career": null, + "status": "SUCCESS", + "createdAt": "2025-01-08T17:54:43.000Z", + "updatedAt": "2025-01-09T09:01:59.000Z", + "User": { + "id": 19, + "nickname": "김개발", + "email": "devpals@mail.com", + "bio": null, + "profileImg": "프로필 이미지 주소", + "UserSkillTag": [ + { + "userId": 19, + "skillTagId": 28, + "createdAt": "2025-01-08T11:57:17.000Z", + "SkillTag": { + "id": 28, + "name": "Figma", + "img": "스킬 태그 이미지 주소", + "createdAt": "2025-01-02T15:11:15.000Z" + } + }, + {} + ] + } + }, + { + "id": 7, + "userId": 20, + "projectId": 6, + "message": "기획자에게 하고 싶은 말", + "email": "devpals@mail.com", + "phoneNumber": "010-9999-0000", + "career": null, + "status": "SUCCESS", + "createdAt": "2025-01-08T17:54:43.000Z", + "updatedAt": "2025-01-09T09:01:59.000Z", + "User": { + "id": 20, + "nickname": "김개발", + "email": "devpals@mail.com", + "bio": null, + "profileImg": "프로필 이미지 주소", + "UserSkillTag": [ + { + "userId": 20, + "skillTagId": 28, + "createdAt": "2025-01-08T11:57:17.000Z", + "SkillTag": { + "id": 28, + "name": "Figma", + "img": "스킬 태그 이미지 주소", + "createdAt": "2025-01-02T15:11:15.000Z" + } + }, + {} + ] + } + }, + { + "id": 7, + "userId": 21, + "projectId": 6, + "message": "기획자에게 하고 싶은 말", + "email": "devpals@mail.com", + "phoneNumber": "010-9999-0000", + "career": null, + "status": "SUCCESS", + "createdAt": "2025-01-08T17:54:43.000Z", + "updatedAt": "2025-01-09T09:01:59.000Z", + "User": { + "id": 21, + "nickname": "김개발", + "email": "devpals@mail.com", + "bio": null, + "profileImg": "프로필 이미지 주소", + "UserSkillTag": [ + { + "userId": 21, + "skillTagId": 28, + "createdAt": "2025-01-08T11:57:17.000Z", + "SkillTag": { + "id": 28, + "name": "Figma", + "img": "스킬 태그 이미지 주소", + "createdAt": "2025-01-02T15:11:15.000Z" + } + }, + {} + ] + } + }, + { + "id": 7, + "userId": 22, + "projectId": 6, + "message": "기획자에게 하고 싶은 말", + "email": "devpals@mail.com", + "phoneNumber": "010-9999-0000", + "career": null, + "status": "SUCCESS", + "createdAt": "2025-01-08T17:54:43.000Z", + "updatedAt": "2025-01-09T09:01:59.000Z", + "User": { + "id": 22, + "nickname": "김개발", + "email": "devpals@mail.com", + "bio": null, + "profileImg": "프로필 이미지 주소", + "UserSkillTag": [ + { + "userId": 22, + "skillTagId": 28, + "createdAt": "2025-01-08T11:57:17.000Z", + "SkillTag": { + "id": 28, + "name": "Figma", + "img": "스킬 태그 이미지 주소", + "createdAt": "2025-01-02T15:11:15.000Z" + } + }, + {} + ] + } + }, + { + "id": 7, + "userId": 23, + "projectId": 6, + "message": "기획자에게 하고 싶은 말", + "email": "devpals@mail.com", + "phoneNumber": "010-9999-0000", + "career": null, + "status": "SUCCESS", + "createdAt": "2025-01-08T17:54:43.000Z", + "updatedAt": "2025-01-09T09:01:59.000Z", + "User": { + "id": 23, + "nickname": "김개발20", + "email": "devpals@mail.com", + "bio": null, + "profileImg": "프로필 이미지 주소", + "UserSkillTag": [ + { + "userId": 23, + "skillTagId": 28, + "createdAt": "2025-01-08T11:57:17.000Z", + "SkillTag": { + "id": 28, + "name": "Figma", + "img": "스킬 태그 이미지 주소", + "createdAt": "2025-01-02T15:11:15.000Z" + } + }, + {} + ] + } + }, + { + "id": 7, + "userId": 24, + "projectId": 6, + "message": "기획자에게 하고 싶은 말", + "email": "devpals@mail.com", + "phoneNumber": "010-9999-0000", + "career": null, + "status": "SUCCESS", + "createdAt": "2025-01-08T17:54:43.000Z", + "updatedAt": "2025-01-09T09:01:59.000Z", + "User": { + "id": 24, + "nickname": "김개발", + "email": "devpals@mail.com", + "bio": null, + "profileImg": "프로필 이미지 주소", + "UserSkillTag": [ + { + "userId": 24, + "skillTagId": 28, + "createdAt": "2025-01-08T11:57:17.000Z", + "SkillTag": { + "id": 28, + "name": "Figma", + "img": "스킬 태그 이미지 주소", + "createdAt": "2025-01-02T15:11:15.000Z" + } + }, + {} + ] + } } ] diff --git a/src/mock/mockPassNonPassListData.json b/src/mock/mockPassNonPassListData.json index d32458ab..58882751 100644 --- a/src/mock/mockPassNonPassListData.json +++ b/src/mock/mockPassNonPassListData.json @@ -164,7 +164,7 @@ }, { "id": 20, - "userId": 11, + "userId": 23, "projectId": 72, "message": null, "email": null, @@ -175,7 +175,7 @@ "updatedAt": "2025-01-23T09:08:25.000Z", "User": { "id": 11, - "nickname": "종다리" + "nickname": "김개발20" } } ] diff --git a/src/mock/mockProjectDetail.json b/src/mock/mockProjectDetail.json index 09da0223..052edf9a 100644 --- a/src/mock/mockProjectDetail.json +++ b/src/mock/mockProjectDetail.json @@ -1,5 +1,5 @@ { - "id": 10, + "id": 6, "title": "클론코딩 사이드 프로젝트 모집 공고", "description": "string", "totalMember": 3, diff --git a/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.tsx b/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.tsx index ae04c421..b8663373 100644 --- a/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.tsx +++ b/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.tsx @@ -52,7 +52,7 @@ const MyProjectVolunteersPass = () => { 합격자 리스트 {projectData && ( @@ -62,7 +62,7 @@ const MyProjectVolunteersPass = () => { 불 합격자 리스트 {projectData && ( diff --git a/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx b/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx index 0038b32c..c889e9db 100644 --- a/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx +++ b/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx @@ -25,17 +25,21 @@ const MyProjectVolunteer = () => { const { projectId } = useParams(); const { data: projectData } = useGetProjectData(Number(projectId)); const { isOpen, handleModalClose, handleModalOpen, message } = useModal(); + const { handlePassNonPassStatus } = usePassNonPassMutation( Number(projectId), handleModalOpen ); + const sidebarMenuItem = useMemo( () => applicantsMenuItems(Number(projectId)), [projectId] ); + const { applicantsData, isApplicantLoading } = useApllicantList( Number(projectId) ); + const { applicantInfo, selectedApplicant, handleSelectedApplicant } = useApplicantInfo(Number(projectId));