diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 71c0e5e0d..5e2e23096 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,7 +18,7 @@ import PrivateRoute from '@/pages/AdminPage/auth/PrivateRoute/PrivateRoute'; import PhotoEditTab from '@/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab'; import ApplicationFormPage from './pages/ApplicationFormPage/ApplicationFormPage'; import ApplicantsTab from './pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab'; -import ApplicantDetailPage from './pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage'; +import ApplicantDetailPage from './pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage'; const queryClient = new QueryClient(); @@ -78,11 +78,11 @@ const App = () => { path='application-edit' element={} /> - } /> - } /> diff --git a/frontend/src/assets/images/icons/applicant_drop.svg b/frontend/src/assets/images/icons/applicant_drop.svg new file mode 100644 index 000000000..810f11bf2 --- /dev/null +++ b/frontend/src/assets/images/icons/applicant_drop.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/images/icons/back_arrow_icon.svg b/frontend/src/assets/images/icons/back_arrow_icon.svg deleted file mode 100644 index 0a00b873e..000000000 --- a/frontend/src/assets/images/icons/back_arrow_icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/assets/images/icons/forward_arrow_icon.svg b/frontend/src/assets/images/icons/forward_arrow_icon.svg deleted file mode 100644 index 96483cd33..000000000 --- a/frontend/src/assets/images/icons/forward_arrow_icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/assets/images/icons/next_applicant.svg b/frontend/src/assets/images/icons/next_applicant.svg new file mode 100644 index 000000000..88790aeb2 --- /dev/null +++ b/frontend/src/assets/images/icons/next_applicant.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/images/icons/prev_applicant.svg b/frontend/src/assets/images/icons/prev_applicant.svg new file mode 100644 index 000000000..b3715b669 --- /dev/null +++ b/frontend/src/assets/images/icons/prev_applicant.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx index 24f8270e0..07776e311 100644 --- a/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx +++ b/frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx @@ -15,7 +15,7 @@ const tabs = [ { label: '모집 정보 수정', path: '/admin/recruit-edit' }, { label: '활동 사진 수정', path: '/admin/photo-edit' }, { label: '지원서 관리', path: '/admin/application-edit' }, - { label: '지원자 관리', path: '/admin/applicants' }, + { label: '지원자 현황', path: '/admin/applicants' }, { label: '계정 관리', path: '/admin/account-edit' }, ]; @@ -41,7 +41,11 @@ const SideBar = ({ clubLogo, clubName }: SideBarProps) => { if (!confirmed) return; try { - if (document.cookie.split(';').some((cookie) => cookie.trim().startsWith('refreshToken='))) { + if ( + document.cookie + .split(';') + .some((cookie) => cookie.trim().startsWith('refreshToken=')) + ) { await logout(); } localStorage.removeItem('accessToken'); diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.styles.ts b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.styles.ts new file mode 100644 index 000000000..f0b4a38be --- /dev/null +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.styles.ts @@ -0,0 +1,156 @@ +import styled from 'styled-components'; +import * as ApplicationFormPageStyles from '@/pages/ApplicationFormPage/ApplicationFormPage.styles'; +import DropdownArrow from '@/assets/images/icons/applicant_drop.svg'; + +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 20px; + margin-bottom: 10px; +`; + +export const HeaderContainer = styled.div` + display: flex; + align-items: center; + gap: 10px; + width: 100%; + height: 58px; +`; + +export const ApplicantContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 240px; + width: 82%; + height: 100%; + border: none; + border-radius: 10px; + background: var(--f5, #f5f5f5); + + select { + font-size: 24px; + font-style: normal; + font-weight: 700; + border: none; + background-color: transparent; + + background-image: url(${DropdownArrow}); + background-repeat: no-repeat; + background-position: right 8px center; + background-size: 11px 11px; + + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + } +`; + +export const StatusSelect = styled.select<{ $backgroundColor: string }>` + border: none; + border-radius: 10px; + padding: 8px 12px; + width: 18%; + height: 100%; + cursor: pointer; + + color: var(--4B, #4b4b4b); + text-align: center; + font-size: 16px; + font-style: normal; + font-weight: 600; + + background-color: ${(props) => props.$backgroundColor}; + + background-image: url(${DropdownArrow}); + background-repeat: no-repeat; + background-position: right 15px center; + padding-right: 30px; + background-size: 9px 9px; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + &:focus { + outline: none; + } +`; + +export const MemoContainer = styled.div` + display: flex; + flex-direction: row; + align-items: flex-start; + width: 100%; + gap: 24px; + padding: 12px 12px; + min-height: 100px; + background: var(--f5, #f5f5f5); + border-radius: 10px; +`; + +export const MemoLabel = styled.label` + position: relative; + display: flex; + align-items: center; + justify-content: center; + align-self: flex-start; + width: 60px; + height: 32px; + background: #fff; + border-radius: 10px; + padding: 6px 14px; + color: var(--, #111); + font-size: 16px; + font-style: normal; + font-weight: 600; + + &::after { + content: ''; + position: absolute; + right: -20px; + top: 30%; + height: 40%; + width: 4px; + height: 14px; + background: #d9d9d9; + } +`; + +export const MemoTextarea = styled.textarea` + flex: 1; + min-height: 80px; + border: none; + border-radius: 10px; + background: var(--f5, #f5f5f5); + padding: 12px; + resize: none; + font-size: 14px; + font-style: normal; + font-weight: 400; + color: var(--4B, #4b4b4b); + + &:focus { + outline: none; + } +`; + +export const ApplicantInfoContainer = styled.div` + display: block; + width: 100%; + padding: 10px 30px; + border-radius: 10px; + border: 1px solid #f2f2f2; +`; + +export const QuestionsWrapper = styled( + ApplicationFormPageStyles.QuestionsWrapper, +)` + cursor: default; +`; + +export const NavigationButton = styled.img` + cursor: pointer; +`; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx similarity index 61% rename from frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx rename to frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx index 4ee8c92b2..9e5b8994d 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx @@ -1,26 +1,39 @@ import React, { useEffect, useMemo, useState } from 'react'; +import * as Styled from './ApplicantDetailPage.styles'; import { useParams, useNavigate } from 'react-router-dom'; import { useAdminClubContext } from '@/context/AdminClubContext'; import Header from '@/components/common/Header/Header'; -import { PageContainer } from '@/styles/PageContainer.styles'; -import * as Styled from '@/pages/ApplicationFormPage/ApplicationFormPage.styles'; import QuestionContainer from '@/pages/ApplicationFormPage/components/QuestionContainer/QuestionContainer'; import QuestionAnswerer from '@/pages/ApplicationFormPage/components/QuestionAnswerer/QuestionAnswerer'; -import { useGetApplication } from '@/hooks/queries/application/useGetApplication'; import Spinner from '@/components/common/Spinner/Spinner'; -import BackButton from '@/assets/images/icons/back_arrow_icon.svg'; -import ForwardButton from '@/assets/images/icons/forward_arrow_icon.svg'; import debounce from '@/utils/debounce'; import updateApplicantMemo from '@/apis/application/updateApplicantDetail'; +import { useGetApplication } from '@/hooks/queries/application/useGetApplication'; import { ApplicationStatus } from '@/types/applicants'; import mapStatusToGroup from '@/utils/mapStatusToGroup'; import { Question } from '@/types/application'; +import PrevApplicantButton from '@/assets/images/icons/prev_applicant.svg'; +import NextApplicantButton from '@/assets/images/icons/next_applicant.svg'; const AVAILABLE_STATUSES = [ - ApplicationStatus.SCREENING, // 서류검토 + ApplicationStatus.SCREENING, // 서류검토 (SUBMITTED 포함) ApplicationStatus.INTERVIEW_SCHEDULED, // 면접예정 ApplicationStatus.ACCEPTED, // 합격 -]; +] as const; + +const getStatusColor = (status: ApplicationStatus | undefined): string => { + switch (status) { + case ApplicationStatus.ACCEPTED: + return 'var(--f5, #F5F5F5)'; + case ApplicationStatus.SCREENING: + case ApplicationStatus.SUBMITTED: + return '#E5F6FF'; + case ApplicationStatus.INTERVIEW_SCHEDULED: + return '#E9FFF1'; + default: + return 'var(--f5, #F5F5F5)'; + } +}; const ApplicantDetailPage = () => { const { questionId } = useParams<{ questionId: string }>(); @@ -32,7 +45,8 @@ const ApplicantDetailPage = () => { const { data: formData, isLoading, isError } = useGetApplication(clubId!); const { applicant, applicantIndex } = useMemo(() => { - const index = applicantsData?.applicants.findIndex((a) => a.id === questionId) ?? -1; + const index = + applicantsData?.applicants.findIndex((a) => a.id === questionId) ?? -1; const _applicant = applicantsData?.applicants[index]; return { applicant: _applicant, applicantIndex: index }; @@ -48,7 +62,12 @@ const ApplicantDetailPage = () => { const updateApplicantDetail = useMemo( () => debounce((memo: any, status: any) => { - updateApplicantMemo(memo as string, status as ApplicationStatus, clubId!, questionId!); + updateApplicantMemo( + memo as string, + status as ApplicationStatus, + clubId!, + questionId!, + ); }, 400), [clubId, questionId], ); @@ -71,7 +90,10 @@ const ApplicantDetailPage = () => { .map((ans) => ans.value); }; - const updateApplicantInContext = (memo: string, status: ApplicationStatus) => { + const updateApplicantInContext = ( + memo: string, + status: ApplicationStatus, + ) => { if (!applicantsData || applicantIndex === -1) return; const updatedApplicants = [...applicantsData.applicants]; @@ -111,43 +133,56 @@ const ApplicantDetailPage = () => { return ( <>
- -
- 이전 지원자 - navigate(`/admin/applicants/${e.target.value}`)} + > + {applicantsData.applicants.map((a) => ( + + ))} + + + + - {applicantsData.applicants.map((a) => ( - ))} - - 다음 지원자 -
- - - - + + + + + 메모 + + + + + {formData.questions.map((q: Question, i: number) => ( @@ -159,9 +194,9 @@ const ApplicantDetailPage = () => { ))} -
+ ); }; -export default ApplicantDetailPage; \ No newline at end of file +export default ApplicantDetailPage; diff --git a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts index b2190cddd..72933b873 100644 --- a/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts +++ b/frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts @@ -141,7 +141,7 @@ export const ApplicantTableHeader = styled.th<{ color: var(--78, #787878); width: ${({ width }) => (width ? `${width}px` : 'auto')}; text-align: ${({ isMemo }) => (isMemo ? 'left' : 'center')}; - padding-left: ${({ isMemo }) => (isMemo ? '30px' : 'none')}; + padding-left: ${({ isMemo }) => (isMemo ? '30px' : '8px')}; ${({ borderLeft }) => borderLeft && @@ -171,7 +171,7 @@ export const ApplicantTableCol = styled.td<{ isMemo?: boolean }>` padding: 12px 8px; font-size: 16px; text-align: ${({ isMemo }) => (isMemo ? 'left' : 'center')}; - padding-left: ${({ isMemo }) => (isMemo ? '30px' : 'none')}; + padding-left: ${({ isMemo }) => (isMemo ? '30px' : '8px')}; `; export const ApplicantTableCheckbox = styled.input.attrs({ type: 'checkbox' })`