diff --git a/src/api/myProjectList.api.ts b/src/api/myProjectList.api.ts index cd65a19a..415a989a 100644 --- a/src/api/myProjectList.api.ts +++ b/src/api/myProjectList.api.ts @@ -5,3 +5,10 @@ export const getMyProjectLists = async () => { const response = await httpClient.get(`/project/my`); return response.data; }; + +export const patchSendResult = async (projectId: number) => { + const response = await httpClient.patch( + `/project/${projectId}/is-done` + ); + return response.data; +}; diff --git a/src/components/common/Button/Button.styled.ts b/src/components/common/Button/Button.styled.ts index d48cbf22..a5b58813 100644 --- a/src/components/common/Button/Button.styled.ts +++ b/src/components/common/Button/Button.styled.ts @@ -9,4 +9,8 @@ export const CommonButton = styled.button>` border-radius: ${({ theme, radius }) => theme.borderRadius[radius]}; pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')}; cursor: ${({ disabled }) => (disabled ? 'none' : 'pointer')}; + + display: flex; + justify-content: center; + align-items: center; `; diff --git a/src/components/common/Button/Button.tsx b/src/components/common/Button/Button.tsx index a67e73f3..95e91c2c 100644 --- a/src/components/common/Button/Button.tsx +++ b/src/components/common/Button/Button.tsx @@ -1,4 +1,4 @@ -import { ButtonHTMLAttributes, ReactNode } from 'react'; +import { ButtonHTMLAttributes, PropsWithChildren } from 'react'; import * as S from './Button.styled'; import { BorderRadiusSize, @@ -7,7 +7,6 @@ import { } from '../../../style/theme'; export interface ButtonProps extends ButtonHTMLAttributes { - children: ReactNode; size: ButtonSize; schema: ButtonSchema; radius: BorderRadiusSize; @@ -21,7 +20,7 @@ function Button({ radius, disabled, ...props -}: ButtonProps) { +}: PropsWithChildren) { return ( { + onClick: () => void; +} + +function DeleteButton({ onClick }: DeleteButtonProps) { + return ( + + + + ); +} + +export default DeleteButton; diff --git a/src/components/manageProjects/passNonPassList/PassNonPassItem.styled.ts b/src/components/manageProjects/passNonPassList/PassNonPassItem.styled.ts index 13826de6..66fbfacc 100644 --- a/src/components/manageProjects/passNonPassList/PassNonPassItem.styled.ts +++ b/src/components/manageProjects/passNonPassList/PassNonPassItem.styled.ts @@ -12,17 +12,9 @@ export const ItemWrapper = styled.li` 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 index 61089540..ab2cb840 100644 --- a/src/components/manageProjects/passNonPassList/PassNonPassItem.tsx +++ b/src/components/manageProjects/passNonPassList/PassNonPassItem.tsx @@ -1,18 +1,20 @@ +import { useMutationParams } from '../../../hooks/usePassNonPassMutation'; import { ApplicantInfo } from '../../../models/applicant'; +import DeleteButton from './DeleteButton'; import * as S from './PassNonPassItem.styled'; -import { XCircleIcon } from '@heroicons/react/24/outline'; interface PassNonPassItemProps { userInfo: ApplicantInfo; + onClick: ({ status, userId }: useMutationParams) => void; } -function PassNonPassItem({ userInfo }: PassNonPassItemProps) { +function PassNonPassItem({ userInfo, onClick }: PassNonPassItemProps) { return ( {userInfo.User.nickname} - - - + onClick({ status: 'WAITING', userId: userInfo.userId })} + /> ); } diff --git a/src/components/manageProjects/passNonPassList/PassNonPassList.tsx b/src/components/manageProjects/passNonPassList/PassNonPassList.tsx index c6bbdf48..04bf8c6f 100644 --- a/src/components/manageProjects/passNonPassList/PassNonPassList.tsx +++ b/src/components/manageProjects/passNonPassList/PassNonPassList.tsx @@ -1,16 +1,21 @@ +import { useMutationParams } from '../../../hooks/usePassNonPassMutation'; import { ApplicantInfo } from '../../../models/applicant'; import PassNonPassItem from './PassNonPassItem'; import * as S from './PassNonPassList.styled'; interface PassNonPassListProps { passNonPassListData: ApplicantInfo[]; + onClick: ({ status, userId }: useMutationParams) => void; } -function PassNonPassList({ passNonPassListData }: PassNonPassListProps) { +function PassNonPassList({ + passNonPassListData, + onClick, +}: PassNonPassListProps) { return ( {passNonPassListData.map((data) => ( - + ))} ); diff --git a/src/components/manageProjects/passNonPassList/SendResultButton.styled.ts b/src/components/manageProjects/passNonPassList/SendResultButton.styled.ts new file mode 100644 index 00000000..59ce6cf6 --- /dev/null +++ b/src/components/manageProjects/passNonPassList/SendResultButton.styled.ts @@ -0,0 +1,24 @@ +import styled from 'styled-components'; +import Button from '../../common/Button/Button'; + +export const Wrapper = styled.div` + width: 100%; + display: flex; + justify-content: end; + align-items: center; + position: absolute; + top: 7rem; + right: 2.5rem; +`; + +export const SendEmailButton = styled(Button)` + width: 9rem; + + svg { + width: 1.5rem; + margin-left: 0.5rem; + } + &:hover { + opacity: 0.8; + } +`; diff --git a/src/components/manageProjects/passNonPassList/SendResultButton.tsx b/src/components/manageProjects/passNonPassList/SendResultButton.tsx new file mode 100644 index 00000000..4a6e69b2 --- /dev/null +++ b/src/components/manageProjects/passNonPassList/SendResultButton.tsx @@ -0,0 +1,24 @@ +import { ButtonHTMLAttributes } from 'react'; +import * as S from './SendResultButton.styled'; +import { EnvelopeIcon } from '@heroicons/react/24/outline'; +export interface SendResultButtonProps + extends ButtonHTMLAttributes { + onSubmit: () => void; +} + +function SendResultButton({ onSubmit }: SendResultButtonProps) { + return ( + + + 결과 전송 + + + ); +} + +export default SendResultButton; diff --git a/src/constants/modalMessage.ts b/src/constants/modalMessage.ts index af342656..7a28a9b2 100644 --- a/src/constants/modalMessage.ts +++ b/src/constants/modalMessage.ts @@ -1,7 +1,12 @@ export const MODAL_MESSAGE = { pass: '지원자를 합격 리스트에 추가했습니다.', nonPass: '지원자를 불합격 리스트에 추가했습니다.', + waiting: '지원자를 대기리스트에 추가했습니다', equalStatus: '이미 동일한 리스트에 추가하신 상태입니다.', + signUp: '회원가입에 성공하셨습니다!', + login: '로그인 되었습니다.', + sendResult: '지원자들에게 이메일로 결과를 전송했어요!', + needAuth: '권한이 없어요!', signUpSuccess: '회원가입 완료되었습니다.', signUpFail: '회원가입 실패하였습니다.', changePasswordSuccess: '비밀번호가 성공적으로 재설정 되었습니다.', diff --git a/src/hooks/usePassNonPassMutation.ts b/src/hooks/usePassNonPassMutation.ts index 3692beab..cd3d0f42 100644 --- a/src/hooks/usePassNonPassMutation.ts +++ b/src/hooks/usePassNonPassMutation.ts @@ -4,8 +4,8 @@ import { AxiosError } from 'axios'; import { applicantKey } from './queries/keys'; import { MODAL_MESSAGE } from '../constants/modalMessage'; -interface useMutationParams { - isPass: boolean; +export interface useMutationParams { + status: 'ACCEPTED' | 'REJECTED' | 'WAITING'; userId: number; } @@ -16,19 +16,33 @@ export const usePassNonPassMutation = ( const queryClient = useQueryClient(); const passNonPassMutation = useMutation({ - mutationFn: async ({ isPass, userId }: useMutationParams) => { - const data = { status: isPass ? 'ACCEPTED' : 'REJECTED' }; + mutationFn: async ({ status, userId }: useMutationParams) => { + const data = { status: status }; await patchPassNonPassStatus(data, projectId, userId); }, - onSuccess: (_, { isPass }) => { + onSuccess: (_, { status }) => { queryClient.invalidateQueries({ queryKey: [applicantKey.all, projectId], }); - const successMessage = isPass - ? MODAL_MESSAGE.pass - : MODAL_MESSAGE.nonPass; + queryClient.invalidateQueries({ + queryKey: [applicantKey.passNonPass, projectId], + }); + + let successMessage; + switch (status) { + case 'ACCEPTED': + successMessage = MODAL_MESSAGE.pass; + break; + case 'REJECTED': + successMessage = MODAL_MESSAGE.nonPass; + break; + case 'WAITING': + successMessage = MODAL_MESSAGE.waiting; + break; + } + openModal(successMessage); }, @@ -38,8 +52,8 @@ export const usePassNonPassMutation = ( }, }); - const handlePassNonPassStatus = (isPass: boolean, userId: number) => { - passNonPassMutation.mutate({ isPass, userId }); + const handlePassNonPassStatus = ({ status, userId }: useMutationParams) => { + passNonPassMutation.mutate({ status, userId }); }; return { handlePassNonPassStatus }; diff --git a/src/hooks/useSendResultMutation.ts b/src/hooks/useSendResultMutation.ts new file mode 100644 index 00000000..62ecbf6f --- /dev/null +++ b/src/hooks/useSendResultMutation.ts @@ -0,0 +1,30 @@ +import { useMutation } from '@tanstack/react-query'; +import { AxiosError } from 'axios'; +import { patchSendResult } from '../api/myProjectList.api'; +import { MODAL_MESSAGE } from '../constants/modalMessage'; + +export const useSendResultMutation = ( + projectId: number, + openModal: (message: string) => void +) => { + const sendResultMutaition = useMutation({ + mutationFn: async () => { + await patchSendResult(projectId); + }, + + onSuccess: () => { + openModal(MODAL_MESSAGE.sendResult); + }, + + onError: (error) => { + console.error(error); + openModal(MODAL_MESSAGE.needAuth); + }, + }); + + const handleSendResult = () => { + sendResultMutaition.mutate(); + }; + + return { handleSendResult }; +}; diff --git a/src/mock/browser.ts b/src/mock/browser.ts index 6febaff4..cac154c2 100644 --- a/src/mock/browser.ts +++ b/src/mock/browser.ts @@ -1,5 +1,5 @@ import { setupWorker } from 'msw/browser'; -import { myProjectList } from './manageProjectList'; +import { myProjectList, sendResult } from './manageProjectList'; import { applicantInfo, applicantList, @@ -31,6 +31,7 @@ export const handlers = [ fetchPositionTag, fetchSkillTag, passNonPassList, + sendResult, myProjectList, applicantList, projectDetail, diff --git a/src/mock/manageProjectList.ts b/src/mock/manageProjectList.ts index 14065d83..64124788 100644 --- a/src/mock/manageProjectList.ts +++ b/src/mock/manageProjectList.ts @@ -9,3 +9,15 @@ export const myProjectList = http.get( }); } ); + +export const sendResult = http.patch( + `${import.meta.env.VITE_API_BASE_URL}/project/:projectId/is-done`, + () => { + return HttpResponse.json( + { message: '지원자들에게 결과를 전송했어요' }, + { + status: 200, + } + ); + } +); diff --git a/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.tsx b/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.tsx index 261c0171..adc5bb58 100644 --- a/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.tsx +++ b/src/pages/manage/myProjectParticipantsPass/MyProjectVolunteersPass.tsx @@ -9,32 +9,53 @@ 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'; +import SendResultButton from '../../../components/manageProjects/passNonPassList/SendResultButton'; +import { useSendResultMutation } from '../../../hooks/useSendResultMutation'; +import { useModal } from '../../../hooks/useModal'; +import Modal from '../../../components/common/modal/Modal'; +import { usePassNonPassMutation } from '../../../hooks/usePassNonPassMutation'; +import { useMemo } from 'react'; + const MyProjectVolunteersPass = () => { const { projectId } = useParams(); const { data: projectData } = useGetProjectData(Number(projectId)); const { passNonPassListData } = usePassNonPassList(Number(projectId)); + const { isOpen, message, handleModalOpen, handleModalClose } = useModal(); + const { handleSendResult } = useSendResultMutation( + Number(projectId), + handleModalOpen + ); + const { handlePassNonPassStatus } = usePassNonPassMutation( + Number(projectId), + handleModalOpen + ); + const sidebarMenuItem = useMemo( + () => applicantsMenuItems(Number(projectId)), + [projectId] + ); return ( - + {projectData && } + + {passNonPassListData?.accepted.length > 0 || passNonPassListData?.rejected.length > 0 ? ( 합격자 리스트 불 합격자 리스트 @@ -42,6 +63,10 @@ const MyProjectVolunteersPass = () => { )} + + + {message} + ); }; diff --git a/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx b/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx index 0319336a..efd38978 100644 --- a/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx +++ b/src/pages/manage/myProjectVolunteer/MyProjectVolunteer.tsx @@ -66,7 +66,10 @@ const MyProjectVolunteer = () => { - handlePassNonPassStatus(true, selectedApplicant) + handlePassNonPassStatus({ + status: 'ACCEPTED', + userId: selectedApplicant, + }) } disabled={projectData?.isDone} > @@ -75,7 +78,10 @@ const MyProjectVolunteer = () => { - handlePassNonPassStatus(false, selectedApplicant) + handlePassNonPassStatus({ + status: 'REJECTED', + userId: selectedApplicant, + }) } disabled={projectData?.isDone} >