diff --git a/src/api/applicant.api.ts b/src/api/applicant.api.ts index 1feba80e..b1bf3110 100644 --- a/src/api/applicant.api.ts +++ b/src/api/applicant.api.ts @@ -26,3 +26,10 @@ export const patchPassNonPassStatus = async ( ); return response.data; }; + +export const getPassNonPassList = async (projectId: number) => { + const response = await httpClient.get( + `/project/${projectId}/applicant/summary` + ); + return response.data; +}; diff --git a/src/components/common/noContent/NoContent.tsx b/src/components/common/noContent/NoContent.tsx index 55e36c8e..47eb3349 100644 --- a/src/components/common/noContent/NoContent.tsx +++ b/src/components/common/noContent/NoContent.tsx @@ -2,13 +2,14 @@ import * as S from './NoContent.styled'; import { DocumentTextIcon } from '@heroicons/react/24/outline'; interface NoContentProps { - type: 'projects' | 'applicants'; + type: 'projects' | 'applicants' | 'passNonPass'; } export default function NoContent({ type }: NoContentProps) { const INPUT_CONTENT = { projects: '공고가', applicants: '지원자가', + passNonPass: '합/불합격자 리스트가', }; return ( diff --git a/src/components/manageProjects/ProjectHeader.styled.ts b/src/components/manageProjects/ProjectHeader.styled.ts new file mode 100644 index 00000000..d762ddc7 --- /dev/null +++ b/src/components/manageProjects/ProjectHeader.styled.ts @@ -0,0 +1,15 @@ +import styled from 'styled-components'; + +export const TitleWrapper = styled.div` + display: flex; + justify-content: start; + align-items: center; + width: 100%; +`; + +export const RecruitmentEnd = styled.h3` + margin-left: 1.2rem; + font-size: 1rem; + font-weight: 400; + color: ${({ theme }) => theme.color.red}; +`; diff --git a/src/components/manageProjects/ProjectHeader.tsx b/src/components/manageProjects/ProjectHeader.tsx new file mode 100644 index 00000000..d9f8133d --- /dev/null +++ b/src/components/manageProjects/ProjectHeader.tsx @@ -0,0 +1,21 @@ +import * as S from './ProjectHeader.styled'; +import Title from '../common/title/Title'; +import { ProjectDetailExtended } from '../../models/projectDetail'; +import RecruitmentDate from './RecruitmentDate'; +interface ProjectHeaderProps { + projectData: ProjectDetailExtended; +} + +function ProjectHeader({ projectData }: ProjectHeaderProps) { + return ( + <> + + {projectData && {projectData.title} } + {projectData?.isDone && 공고 마감} + + {projectData && } + + ); +} + +export default ProjectHeader; diff --git a/src/components/manageProjects/passNonPassList/PassNonPassItem.styled.ts b/src/components/manageProjects/passNonPassList/PassNonPassItem.styled.ts new file mode 100644 index 00000000..13826de6 --- /dev/null +++ b/src/components/manageProjects/passNonPassList/PassNonPassItem.styled.ts @@ -0,0 +1,28 @@ +import styled from 'styled-components'; + +export const ItemWrapper = styled.li` + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 1rem; + border: 1px solid ${({ theme }) => theme.color.grey}; + border-radius: ${({ theme }) => theme.borderRadius.primary}; + &:hover { + background-color: ${({ theme }) => theme.color.navy}; + color: ${({ theme }) => theme.color.white}; + } + + svg { + color: #e69191; + width: 1.2rem; + height: 1.2rem; + } +`; + +export const NickName = styled.p` + font-size: ${({ theme }) => theme.heading.small.fontSize}; + font-weight: 400; +`; + +export const DeleteButton = styled.button``; diff --git a/src/components/manageProjects/passNonPassList/PassNonPassItem.tsx b/src/components/manageProjects/passNonPassList/PassNonPassItem.tsx new file mode 100644 index 00000000..61089540 --- /dev/null +++ b/src/components/manageProjects/passNonPassList/PassNonPassItem.tsx @@ -0,0 +1,20 @@ +import { ApplicantInfo } from '../../../models/applicant'; +import * as S from './PassNonPassItem.styled'; +import { XCircleIcon } from '@heroicons/react/24/outline'; + +interface PassNonPassItemProps { + userInfo: ApplicantInfo; +} + +function PassNonPassItem({ userInfo }: PassNonPassItemProps) { + return ( + + {userInfo.User.nickname} + + + + + ); +} + +export default PassNonPassItem; diff --git a/src/components/manageProjects/passNonPassList/PassNonPassList.styled.ts b/src/components/manageProjects/passNonPassList/PassNonPassList.styled.ts new file mode 100644 index 00000000..5493e4d2 --- /dev/null +++ b/src/components/manageProjects/passNonPassList/PassNonPassList.styled.ts @@ -0,0 +1,20 @@ +import styled from 'styled-components'; + +export const Wrapper = styled.ul` + width: 100%; + height: 27rem; + min-width: 22rem; + border-radius: ${({ theme }) => theme.borderRadius.primary}; + border: 1px solid ${({ theme }) => theme.color.border}; + padding: 1.6rem 2rem; + gap: 1rem; + display: flex; + flex-direction: column; + justify-content: start; + align-items: center; + overflow: hidden; + overflow-y: auto; + &::-webkit-scrollbar { + display: none; + } +`; diff --git a/src/components/manageProjects/passNonPassList/PassNonPassList.tsx b/src/components/manageProjects/passNonPassList/PassNonPassList.tsx new file mode 100644 index 00000000..c6bbdf48 --- /dev/null +++ b/src/components/manageProjects/passNonPassList/PassNonPassList.tsx @@ -0,0 +1,19 @@ +import { ApplicantInfo } from '../../../models/applicant'; +import PassNonPassItem from './PassNonPassItem'; +import * as S from './PassNonPassList.styled'; + +interface PassNonPassListProps { + passNonPassListData: ApplicantInfo[]; +} + +function PassNonPassList({ passNonPassListData }: PassNonPassListProps) { + return ( + + {passNonPassListData.map((data) => ( + + ))} + + ); +} + +export default PassNonPassList; diff --git a/src/hooks/queries/keys.ts b/src/hooks/queries/keys.ts index 5683f6e3..fb9d9247 100644 --- a/src/hooks/queries/keys.ts +++ b/src/hooks/queries/keys.ts @@ -5,6 +5,7 @@ export const managedProjectsKey = { export const applicantKey = { all: ['applicantList'], info: ['applicantInfo'], + passNonPass: ['passNonPassList'], }; export const myInfoKey = { diff --git a/src/hooks/usePassNonPassList.ts b/src/hooks/usePassNonPassList.ts new file mode 100644 index 00000000..6ef277ca --- /dev/null +++ b/src/hooks/usePassNonPassList.ts @@ -0,0 +1,13 @@ +import { useQuery } from '@tanstack/react-query'; +import { applicantKey } from './queries/keys'; +import { getPassNonPassList } from '../api/applicant.api'; + +export const usePassNonPassList = (projectId: number) => { + const { data } = useQuery({ + queryKey: applicantKey.passNonPass, + queryFn: () => getPassNonPassList(projectId), + staleTime: 1 * 60 * 1000, + }); + + return { passNonPassListData: data }; +}; diff --git a/src/hooks/usePassNonPass.ts b/src/hooks/usePassNonPassMutation.ts similarity index 96% rename from src/hooks/usePassNonPass.ts rename to src/hooks/usePassNonPassMutation.ts index 1d1d6bd2..3692beab 100644 --- a/src/hooks/usePassNonPass.ts +++ b/src/hooks/usePassNonPassMutation.ts @@ -9,7 +9,7 @@ interface useMutationParams { userId: number; } -export const usePassNonPass = ( +export const usePassNonPassMutation = ( projectId: number, openModal: (message: string) => void ) => { diff --git a/src/mock/applicant.ts b/src/mock/applicant.ts index a6827322..864a7bcd 100644 --- a/src/mock/applicant.ts +++ b/src/mock/applicant.ts @@ -1,6 +1,7 @@ import { http, HttpResponse } from 'msw'; import mockApplicantsData from './mockApplicantsData.json'; import mockApplicantData from './mockApplicantData.json'; +import mockPassNonPassListData from './mockPassNonPassListData.json'; export const applicantList = http.get( `${import.meta.env.VITE_API_BASE_URL}/project/:projectId/applicant`, @@ -20,6 +21,15 @@ export const applicantInfo = http.get( } ); +export const passNonPassList = http.get( + `${import.meta.env.VITE_API_BASE_URL}/project/:projectId/applicant/summary`, + () => { + return HttpResponse.json(mockPassNonPassListData, { + status: 200, + }); + } +); + export const passNonPass = http.patch( `${ import.meta.env.VITE_API_BASE_URL diff --git a/src/mock/browser.ts b/src/mock/browser.ts index c3c24032..efe033dd 100644 --- a/src/mock/browser.ts +++ b/src/mock/browser.ts @@ -1,6 +1,11 @@ import { setupWorker } from 'msw/browser'; import { myProjectList } from './manageProjectList'; -import { applicantInfo, applicantList, passNonPass } from './applicant'; +import { + applicantInfo, + applicantList, + passNonPass, + passNonPassList, +} from './applicant'; import { projectDetail } from './projectDetail'; export const handlers = [ @@ -9,6 +14,7 @@ export const handlers = [ projectDetail, applicantInfo, passNonPass, + passNonPassList, ]; export const worker = setupWorker(...handlers); diff --git a/src/mock/mockPassNonPassListData.json b/src/mock/mockPassNonPassListData.json new file mode 100644 index 00000000..d32458ab --- /dev/null +++ b/src/mock/mockPassNonPassListData.json @@ -0,0 +1,182 @@ +{ + "accepted": [ + { + "id": 18, + "userId": 1, + "projectId": 72, + "message": null, + "email": null, + "phoneNumber": null, + "career": null, + "status": "ACCEPTED", + "createdAt": "2025-01-23T09:08:13.000Z", + "updatedAt": "2025-01-23T09:08:13.000Z", + "User": { + "id": 1, + "nickname": "Jeny" + } + }, + { + "id": 18, + "userId": 2, + "projectId": 72, + "message": null, + "email": null, + "phoneNumber": null, + "career": null, + "status": "ACCEPTED", + "createdAt": "2025-01-23T09:08:13.000Z", + "updatedAt": "2025-01-23T09:08:13.000Z", + "User": { + "id": 2, + "nickname": "Jeny1" + } + }, + { + "id": 18, + "userId": 3, + "projectId": 72, + "message": null, + "email": null, + "phoneNumber": null, + "career": null, + "status": "ACCEPTED", + "createdAt": "2025-01-23T09:08:13.000Z", + "updatedAt": "2025-01-23T09:08:13.000Z", + "User": { + "id": 3, + "nickname": "Jeny3" + } + }, + { + "id": 18, + "userId": 4, + "projectId": 72, + "message": null, + "email": null, + "phoneNumber": null, + "career": null, + "status": "ACCEPTED", + "createdAt": "2025-01-23T09:08:13.000Z", + "updatedAt": "2025-01-23T09:08:13.000Z", + "User": { + "id": 4, + "nickname": "Jeny" + } + }, + { + "id": 18, + "userId": 5, + "projectId": 72, + "message": null, + "email": null, + "phoneNumber": null, + "career": null, + "status": "ACCEPTED", + "createdAt": "2025-01-23T09:08:13.000Z", + "updatedAt": "2025-01-23T09:08:13.000Z", + "User": { + "id": 5, + "nickname": "Jeny1" + } + }, + { + "id": 18, + "userId": 6, + "projectId": 72, + "message": null, + "email": null, + "phoneNumber": null, + "career": null, + "status": "ACCEPTED", + "createdAt": "2025-01-23T09:08:13.000Z", + "updatedAt": "2025-01-23T09:08:13.000Z", + "User": { + "id": 6, + "nickname": "Jeny" + } + }, + { + "id": 18, + "userId": 7, + "projectId": 72, + "message": null, + "email": null, + "phoneNumber": null, + "career": null, + "status": "ACCEPTED", + "createdAt": "2025-01-23T09:08:13.000Z", + "updatedAt": "2025-01-23T09:08:13.000Z", + "User": { + "id": 7, + "nickname": "Jeny1" + } + }, + { + "id": 18, + "userId": 8, + "projectId": 72, + "message": null, + "email": null, + "phoneNumber": null, + "career": null, + "status": "ACCEPTED", + "createdAt": "2025-01-23T09:08:13.000Z", + "updatedAt": "2025-01-23T09:08:13.000Z", + "User": { + "id": 8, + "nickname": "Jeny" + } + }, + { + "id": 18, + "userId": 9, + "projectId": 72, + "message": null, + "email": null, + "phoneNumber": null, + "career": null, + "status": "ACCEPTED", + "createdAt": "2025-01-23T09:08:13.000Z", + "updatedAt": "2025-01-23T09:08:13.000Z", + "User": { + "id": 9, + "nickname": "Jeny1" + } + } + ], + "rejected": [ + { + "id": 19, + "userId": 10, + "projectId": 72, + "message": null, + "email": null, + "phoneNumber": null, + "career": null, + "status": "REJECTED", + "createdAt": "2025-01-23T09:08:20.000Z", + "updatedAt": "2025-01-23T09:08:20.000Z", + "User": { + "id": 10, + "nickname": "룰루" + } + }, + { + "id": 20, + "userId": 11, + "projectId": 72, + "message": null, + "email": null, + "phoneNumber": null, + "career": null, + "status": "REJECTED", + "createdAt": "2025-01-23T09:08:25.000Z", + "updatedAt": "2025-01-23T09:08:25.000Z", + "User": { + "id": 11, + "nickname": "종다리" + } + } + ] +} diff --git a/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.styled.ts b/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.styled.ts index c3389834..f2d324db 100644 --- a/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.styled.ts +++ b/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.styled.ts @@ -1,3 +1,27 @@ import styled from 'styled-components'; -export const Container = styled.div``; +export const Container = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +export const Title = styled.h1` + font-size: 1.25rem; + font-weight: 700; + color: ${({ theme }) => theme.color.deepGrey}; + margin-left: 1rem; + margin-bottom: 1rem; +`; + +export const ResultContainer = styled.div` + display: flex; + width: 100%; + justify-content: space-between; + margin-top: 3.5rem; +`; + +export const ListWrapper = styled.div` + width: 100%; + margin-right: 1rem; +`; diff --git a/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.tsx b/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.tsx index 4a97ce56..261c0171 100644 --- a/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.tsx +++ b/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.tsx @@ -1,35 +1,47 @@ import * as S from './MyProjectVolunteersPass.styled'; import Sidebar from '../../../components/common/sidebar/Sidebar'; -import { - DocumentTextIcon, - PencilSquareIcon, - UserIcon, -} from '@heroicons/react/24/outline'; -import { ROUTES } from '../../../constants/routes'; import { useParams } from 'react-router-dom'; - +import { applicantsMenuItems } from '../../../constants/sidebarItems'; +import InfoCard from '../../../components/common/infoCard/InfoCard'; +import MainLogo from '../../../assets/mainlogo.svg'; +import { usePassNonPassList } from '../../../hooks/usePassNonPassList'; +import useGetProjectData from '../../../hooks/useJoinProject'; +import ProjectHeader from '../../../components/manageProjects/ProjectHeader'; +import PassNonPassList from '../../../components/manageProjects/passNonPassList/PassNonPassList'; +import NoContent from '../../../components/common/noContent/NoContent'; const MyProjectVolunteersPass = () => { const { projectId } = useParams(); - const menuItems = [ - { - label: '지원자 보기', - path: `${ROUTES.manageProjectsRoot}/${projectId}`, - icon: , - }, - { - label: '지원자 합/불 관리', - path: `${ROUTES.manageProjectsPassNonPass}/${projectId}`, - icon: , - }, - { - label: '공고 관리', - path: '/mypage/apply-projects', - icon: , - }, - ]; + const { data: projectData } = useGetProjectData(Number(projectId)); + const { passNonPassListData } = usePassNonPassList(Number(projectId)); + return ( - + + + {projectData && } + {passNonPassListData?.accepted.length > 0 || + passNonPassListData?.rejected.length > 0 ? ( + + + 합격자 리스트 + + + + 불 합격자 리스트 + + + + ) : ( + + )} + ); }; diff --git a/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.styled.ts b/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.styled.ts index 36c7462f..c0ada979 100644 --- a/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.styled.ts +++ b/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.styled.ts @@ -31,20 +31,6 @@ export const Title = styled.h1` margin-bottom: 1rem; `; -export const TitleWrapper = styled.div` - display: flex; - justify-content: start; - align-items: center; - width: 100%; -`; - -export const RecruitmentEnd = styled.h3` - margin-left: 1.2rem; - font-size: 1rem; - font-weight: 400; - color: ${({ theme }) => theme.color.red}; -`; - export const ButtonWrapper = styled.div` display: flex; `; diff --git a/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx b/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx index 6f9866bb..05590ce7 100644 --- a/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx +++ b/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx @@ -3,10 +3,8 @@ import { useParams } from 'react-router-dom'; import * as S from './MyProjectVolunteer.styled'; import Sidebar from '../../../components/common/sidebar/Sidebar'; import InfoCard from '../../../components/common/infoCard/InfoCard'; -import Title from '../../../components/common/title/Title'; import ApplicantList from '../../../components/manageProjects/applicantList/ApplicantList'; import ApplicantInfo from '../../../components/manageProjects/applicantInfo/ApplicantInfo'; -import RecruitmentDate from '../../../components/manageProjects/RecruitmentDate'; import useGetProjectData from '../../../hooks/useJoinProject'; import { useApllicantList } from '../../../hooks/useApllicantList'; @@ -14,19 +12,20 @@ import { useApllicantList } from '../../../hooks/useApllicantList'; import { applicantsMenuItems } from '../../../constants/sidebarItems'; import { useApplicantInfo } from '../../../hooks/useApplicantInfo'; import PassNonPassButton from '../../../components/manageProjects/applicantList/PassNonPassButton'; -import { usePassNonPass } from '../../../hooks/usePassNonPass'; +import { usePassNonPassMutation } from '../../../hooks/usePassNonPassMutation'; import NoContent from '../../../components/common/noContent/NoContent'; import MainLogo from '../../../assets/mainlogo.svg'; import Modal from '../../../components/common/modal/Modal'; import { useModal } from '../../../hooks/useModal'; +import ProjectHeader from '../../../components/manageProjects/ProjectHeader'; const MyProjectVolunteer = () => { const { projectId } = useParams(); const sidebarMenuItem = applicantsMenuItems(Number(projectId)); const { data: projectData } = useGetProjectData(Number(projectId)); const { isOpen, handleModalClose, handleModalOpen, message } = useModal(); - const { handlePassNonPassStatus } = usePassNonPass( + const { handlePassNonPassStatus } = usePassNonPassMutation( Number(projectId), handleModalOpen ); @@ -40,16 +39,9 @@ const MyProjectVolunteer = () => { - - {projectData && {projectData.title} } - {projectData?.isDone && ( - 공고 마감 - )} - - {projectData && } + {projectData && } - {isApplicantLoading || - (applicantsData && applicantsData?.length > 0) ? ( + {isApplicantLoading || (applicantsData && applicantsData.length > 0) ? ( 지원자 리스트 diff --git a/src/routes/AppRoutes.tsx b/src/routes/AppRoutes.tsx index 5ad5674e..4cd6a827 100644 --- a/src/routes/AppRoutes.tsx +++ b/src/routes/AppRoutes.tsx @@ -209,11 +209,11 @@ const AppRoutes = () => { path: `${ROUTES.manageProjectsPassNonPass}/:projectId`, element: ( - - }> + }> + - - + + ), },