Skip to content

Commit

Permalink
✨feat: 공고에 대한 지원자 목록 페이지 구현 및 api 정의하기 #50
Browse files Browse the repository at this point in the history
  • Loading branch information
naarang committed Oct 26, 2024
1 parent 22bfe9e commit 972c07b
Show file tree
Hide file tree
Showing 18 changed files with 371 additions and 48 deletions.
18 changes: 18 additions & 0 deletions src/api/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ export const getPostDetail = async (id: number) => {
return response.data;
};

// 4.6 (고용주) 공고에 대한 지원자 리스트 조회
// TODO: 정렬값 추가 필요
export const getApplicantList = async (id: number) => {
// TODO: 무한 스크롤 구현하기
const page = 1;
const size = 10;
const response = await api.get(
`/api/v1/owners/job-postings/${id}/user-owner-job-postings/users/overviews?page=${page}&size=${size}`,
);
return response.data;
};

// 4.7 (유학생/고용주) 공고 요약 정보 조회하기
export const getPostSummary = async (id: number) => {
const response = await api.get(`/api/v1/job-postings/${id}/summaries`);
return response.data;
};

// 4.13 (고용주) 공고 삭제하기
export const deletePost = async (id: number) => {
const response = await api.delete(`/api/v1/owners/job-postings/${id}`);
Expand Down
4 changes: 2 additions & 2 deletions src/components/Application/ApplicationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ const statusStyler = (status: ApplicationStepType) => {
}
};

type ApplicationCardType = {
type ApplicationCardPropsType = {
applicationData: AppicationItemType;
};

const ApplicationCard = ({ applicationData }: ApplicationCardType) => {
const ApplicationCard = ({ applicationData }: ApplicationCardPropsType) => {
const navigate = useNavigate();

return (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Common/SearchSortDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const DropdownModal = ({ options, value, onSelect }: DropdownProps) => {
{options.map((option, index) => (
<div
key={index}
className={`px-4 py-2 ${value == option && 'bg-[#37383C9C] text-white'} rounded-lg caption-1-sb`}
className={`w-full px-4 py-2 ${value == option && 'bg-[#37383C9C] text-white'} rounded-lg caption-1-sb`}
onClick={() => onSelect(option)}
>
{option}
Expand Down
108 changes: 108 additions & 0 deletions src/components/Employer/ApplicantList/EmployerApplicantCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { APPLICATION_STEP } from '@/constants/application';
import {
ApplicantItemType,
ApplicationStepType,
} from '@/types/application/applicationItem';
import ClockIcon from '@/assets/icons/ClockIcon.svg?react';
import MoneyIcon from '@/assets/icons/MoneyIcon.svg?react';
import RightArrowIcon from '@/assets/icons/RightArrowIcon.svg?react';
import Tag from '@/components/Common/Tag';
import { useNavigate } from 'react-router-dom';

const statusStyler = (status: ApplicationStepType) => {
switch (status) {
case APPLICATION_STEP.APPLICATION_SUCCESS:
return 'bg-[#C7C6F6]';
case APPLICATION_STEP.RESUME_REJECTED:
return 'bg-[#FFC6C0]';
case APPLICATION_STEP.APPLICATION_REJECTED:
return 'bg-[#FFC6C0]';
case APPLICATION_STEP.PENDING:
return 'bg-[#BDBDBD]';
default:
return 'bg-[#FEF387]';
}
};

type EmployerApplicantCardPropsType = {
applicantData: ApplicantItemType;
};

const EmployerApplicantCard = ({
applicantData,
}: EmployerApplicantCardPropsType) => {
const navigate = useNavigate();
return (
<article className="w-full border-[0.031rem] border-[#1E19263D] rounded-[1.125rem] overflow-hidden">
<div
className={`flex justify-between items-center px-[1rem] py-[0.5rem] ${statusStyler(applicantData.step)}`}
>
<div className="flex gap-[0.25rem]">
<p className="pl-[0.5rem] caption-1 text-[#1E1926]">
{applicantData.step.replace(/_/g, ' ').toLowerCase()}
</p>
<div className="w-[0.375rem] h-[0.375rem] rounded-full bg-[#FF6F61]"></div>
</div>
<RightArrowIcon
onClick={() => navigate(`/employer/application/${applicantData.id}`)}
/>
</div>
<div className="flex justify-between w-full px-[1.5rem] pt-[1rem] pb-[0.75rem]">
<div className="flex gap-[0.75rem]">
<div className='w-[2.5rem] h-[2.5rem] rounded-[0.5rem] bg-cover bg-[url("/src/assets/images/JobIconExample.jpeg")]'></div>
<div>
<h3 className="pb-[0.25rem] head-3 text-[#1E1926]">
{applicantData.name}
</h3>
<p className="body-3 text-[#464646]">{applicantData.nationality}</p>
</div>
</div>
<Tag
value={`${applicantData.duration_of_days}days After`}
padding="0.25rem 0.438rem"
isRounded={false}
hasCheckIcon={false}
backgroundColor="#1E1926"
color="#F4F4F9"
fontStyle="caption-2"
/>
</div>
<div className="flex flex-col gap-[0.125rem] w-full px-[1.5rem] pb-[0.75rem]">
<div className="flex items-center gap-[0.5rem] px-[0.5rem]">
<ClockIcon className="min-w-[0.5rem]" />
<p className="text-[#464646] caption-1">{applicantData.gender}</p>
</div>
<div className="flex items-center gap-[0.5rem] px-[0.5rem]">
<MoneyIcon className="min-w-[0.5rem]" />
<p className="text-[#464646] caption-1">
{applicantData.school_name}
</p>
</div>
<div className="flex items-center gap-[0.5rem] px-[0.5rem]">
<ClockIcon className="min-w-[0.5rem]" />
<p className="text-[#464646] caption-1">
{applicantData.visa.replace(/_/g, '-')}
</p>
</div>
</div>
<div className="flex flex-col gap-[0.625rem] px-[1rem] pb-[1rem]">
<button
className="w-full px-[1.5rem] py-[0.75rem] text-center rounded-full bg-[#1E1926] text-[#F4F4F9] caption-1-sb"
onClick={() => navigate(`/employer/application/${applicantData.id}`)}
>
Check Application Status
</button>
<button
className="w-full px-[1.5rem] py-[0.75rem] text-center rounded-full bg-[#FEF387] text-[#1E1926] caption-1-sb"
onClick={() =>
navigate(`/employer/application/resume/${applicantData.id}`)
}
>
See Resume
</button>
</div>
</article>
);
};

export default EmployerApplicantCard;
61 changes: 61 additions & 0 deletions src/components/Employer/ApplicantList/EmployerApplicantList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import SearchSortDropdown from '@/components/Common/SearchSortDropdown';
import EmployerApplicationCard from '@/components/Employer/ApplicantList/EmployerApplicantCard';
import {
APPLICANT_LIST_DATA,
KO_APPLICATION_STATUS_TYPE,
} from '@/constants/application';
import { KO_ASCENDING_SORT_TYPE } from '@/constants/sort';
import { ApplicantItemType } from '@/types/application/applicationItem';
import { KoApplicationStatusType } from '@/types/application/applicationStatus';
import { KoAscendingSortType } from '@/types/common/sort';
import { useEffect, useState } from 'react';

type EmployerApplicationListPropsType = {
title: string;
};

const EmployerApplicationList = ({
title,
}: EmployerApplicationListPropsType) => {
const [applicantList, setApplicantList] = useState<ApplicantItemType[]>([]);
const [selectedSort, setSelectedSort] = useState<KoAscendingSortType>(
KO_ASCENDING_SORT_TYPE.ASCENDING,
);
const [selectedStatus, setSelectedStatus] = useState<KoApplicationStatusType>(
KO_APPLICATION_STATUS_TYPE.INPROGRESS,
);

useEffect(() => {
// TODO: 4.6 호출하기
setApplicantList(APPLICANT_LIST_DATA);
}, [selectedSort, selectedStatus]);

return (
<section className="flex flex-col gap-[1rem] w-full p-[1.5rem] pb-[6.25rem]">
<div className="flex justify-between items-center">
<h3 className="px-[0.5rem] head-3 text-[#1E1926]">
<span className="pr-[0.25rem] text-[#7872ED]">{title}</span>의 지원자
</h3>
<div className="flex gap-[0.25rem] whitespace-nowrap">
<SearchSortDropdown
options={Object.values(KO_ASCENDING_SORT_TYPE)}
value={selectedSort}
onSelect={(sort) => setSelectedSort(sort as KoAscendingSortType)}
/>
<SearchSortDropdown
options={Object.values(KO_APPLICATION_STATUS_TYPE)}
value={selectedStatus}
onSelect={(sort) =>
setSelectedStatus(sort as KoApplicationStatusType)
}
/>
</div>
</div>
{applicantList?.map((data) => (
<EmployerApplicationCard key={data.id} applicantData={data} />
))}
</section>
);
};

export default EmployerApplicationList;
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,19 @@ import ClockIcon from '@/assets/icons/ClockIcon.svg?react';
import MoneyIcon from '@/assets/icons/MoneyIcon.svg?react';
import TopRightArrowIcons from '@/assets/icons/Home/TopRightArrowIcon.svg?react';
import { PostSummaryItemType } from '@/types/post/postSummaryItem';
import { useEffect, useState } from 'react';

import { useNavigate, useParams } from 'react-router-dom';

// 더미데이터 4.7로 불러오기
const POST_SUMMARY_ITEM: PostSummaryItemType = {
icon_img_url: 'https://example.com/icon.png',
company_name: 'Global Translations Ltd.',
title: 'General Interpretation & Translation',
tags: {
is_recruiting: true,
visa: 'D_2_1',
job_category: 'GENERAL_INTERPRETATION_TRANSLATION',
},
summaries: {
address: '123 Translation Ave, Seoul',
houlry_rate: 15000,
work_period: 'ONE_MONTH_TO_THREE_MONTHS',
work_days_per_week: 5,
},
type EmployerApplicantListTitlePropsType = {
postData: PostSummaryItemType;
};

const EmployerApplicantListTitle = () => {
const EmployerApplicantListTitle = ({
postData,
}: EmployerApplicantListTitlePropsType) => {
const navigate = useNavigate();
const { id } = useParams();

const [postData, setPostData] = useState<PostSummaryItemType>();

useEffect(() => {
setPostData(POST_SUMMARY_ITEM);
}, []);

if (!postData) return <></>;

return (
<section className="flex flex-col items-center gap-[0.25rem] w-full pt-[0.5rem]">
<div className='w-[5.125rem] h-[5.125rem] rounded-full bg-cover bg-center bg-[url("/src/assets/images/JobIconExample.jpeg")]'></div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Employer/Post/EmployerPostCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const EmployerPostCard = ({ postData }: EmployerPostCardType) => {
</button>
<button
className="flex-1 py-[0.75rem] caption-1-sb text-[#1E1926] bg-[#FEF387] text-center"
onClick={() => navigate(`/employer/application/${postData.id}`)}
onClick={() => navigate(`/employer/post/${postData.id}/applicant`)}
>
지원자 확인
</button>
Expand Down
6 changes: 3 additions & 3 deletions src/components/Employer/Post/EmployerPostCardList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { EMPLOYER_POST_LIST } from '@/constants/post';
import { AscendingSortType } from '@/types/common/sort';
import { KoAscendingSortType } from '@/types/common/sort';
import EmployerPostCard from '@/components/Employer/Post/EmployerPostCard';
import { useEffect } from 'react';

type EmployerPostCardListProps = {
selectedSort: AscendingSortType;
selectedSort: KoAscendingSortType;
};

const EmployerPostCardList = ({ selectedSort }: EmployerPostCardListProps) => {
Expand All @@ -14,7 +14,7 @@ const EmployerPostCardList = ({ selectedSort }: EmployerPostCardListProps) => {
return (
<>
{EMPLOYER_POST_LIST.map((data) => (
<EmployerPostCard postData={data} />
<EmployerPostCard key={data.id} postData={data} />
))}
</>
);
Expand Down
69 changes: 69 additions & 0 deletions src/constants/application.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ApplicantItemType } from '@/types/application/applicationItem';

export const enum APPLICATION_STEP {
RESUME_UNDER_REVIEW = 'RESUME_UNDER_REVIEW',
WAITING_FOR_INTERVIEW = 'WAITING_FOR_INTERVIEW',
Expand All @@ -18,3 +20,70 @@ export const APPLICATION_STATUS_TYPE = {
RESUME_REJECTED: 'resume rejected',
PENDING: 'pending',
} as const;

export const KO_APPLICATION_STATUS_TYPE = {
INPROGRESS: '진행중',
APPLICATION_SUCCESSFUL: '계약 성공',
APPLICATION_REJECTED: '시간제취업허가 실패',
RESUME_REJECTED: '이력서 거절',
PENDING: '대기',
} as const;

// 지원자 리스트 더미데이터
export const APPLICANT_LIST_DATA: ApplicantItemType[] = [
{
id: 1001,
profile_img_url: 'https://example.com/images/applicant1.jpg',
name: 'John Doe',
nationality: 'USA',
gender: 'Male',
visa: 'D_2_1',
school_name: 'Seoul National University',
duration_of_days: 30,
step: 'RESUME_UNDER_REVIEW',
},
{
id: 1002,
profile_img_url: 'https://example.com/images/applicant2.jpg',
name: 'Jane Smith',
nationality: 'Canada',
gender: 'Female',
visa: 'D_2_3',
school_name: 'Korea University',
duration_of_days: 60,
step: 'WAITING_FOR_INTERVIEW',
},
{
id: 1003,
profile_img_url: 'https://example.com/images/applicant3.jpg',
name: 'Samuel Green',
nationality: 'UK',
gender: 'Male',
visa: 'F_2',
school_name: 'Yonsei University',
duration_of_days: 90,
step: 'APPLICATION_SUCCESS',
},
{
id: 1004,
profile_img_url: 'https://example.com/images/applicant4.jpg',
name: 'Emily White',
nationality: 'Australia',
gender: 'Female',
visa: 'D_4_1',
school_name: 'Sogang University',
duration_of_days: 120,
step: 'APPLICATION_IN_PROGRESS',
},
{
id: 1005,
profile_img_url: 'https://example.com/images/applicant5.jpg',
name: 'Michael Brown',
nationality: 'Germany',
gender: 'Male',
visa: 'D_2_2',
school_name: 'Hanyang University',
duration_of_days: 45,
step: 'PENDING',
},
];
Loading

0 comments on commit 972c07b

Please sign in to comment.