Skip to content

Commit

Permalink
Merge pull request #53 from Team-inglo/feat/IGW-38/50
Browse files Browse the repository at this point in the history
[Feat/igw-38/50] 고용주 버전 공고 목록 및 지원자 목록 페이지 작업하기
  • Loading branch information
naarang authored Oct 26, 2024
2 parents c384d96 + 8dee453 commit bb674bb
Show file tree
Hide file tree
Showing 26 changed files with 3,411 additions and 2,281 deletions.
4,896 changes: 2,678 additions & 2,218 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

35 changes: 34 additions & 1 deletion src/api/post.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
import { AscendingSortType } from '@/types/common/sort';
import { api } from '.';

// 4.4 (유학생/고용주) 공고 상세 조회하기
export const getPostDetail = async (id: number) => {
const response = await api.post(`/api/v1/job-postings/${id}/details`);
const response = await api.get(`/api/v1/job-postings/${id}/details`);
return response.data;
};

// 4.6 (고용주) 공고에 대한 지원자 리스트 조회
export const getApplicantList = async (
id: number,
sorting: string,
status: string,
) => {
// 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}&sorting=${sorting}&status=${status}`,
);
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;
};

Expand All @@ -11,3 +33,14 @@ export const deletePost = async (id: number) => {
const response = await api.delete(`/api/v1/owners/job-postings/${id}`);
return response.data;
};

// 6.6 (고용주) 등록한 공고 리스트 조회하기
export const getEmployerPostList = async (sorting: AscendingSortType) => {
// TODO: 무한 스크롤 구현하기
const page = 1;
const size = 10;
const response = await api.get(
`/api/v1/owners/job-postings/overviews?page=${page}&size=${size}&sorting=${sorting}`,
);
return response.data;
};
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
70 changes: 42 additions & 28 deletions src/components/Common/BottomSheetLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,36 +38,50 @@ const BottomSheetLayout = ({
}
}, [contentRef]);

useEffect(() => {
if (isShowBottomsheet) document.body.style.overflow = 'hidden';
else document.body.style.overflow = 'auto';

return () => {
document.body.style.overflow = 'auto';
};
}, [isShowBottomsheet]);

return (
<motion.div
drag="y"
initial="hidden"
{...(isAvailableHidden ? { onDragEnd } : {})}
animate={controls}
transition={{
type: 'spring',
damping: 40,
stiffness: 400,
}}
variants={{
visible: { y: 0 },
hidden: { y: '100%' },
}}
dragConstraints={{
top: 0,
bottom: contentHeight,
}} // 상단과 하단 드래그 제한 설정
dragElastic={0.2}
className={`fixed left-0 bottom-0 w-full h-[90vh] p-[1.5rem] pb-[2.5rem] rounded-t-[2.5rem] bg-white shadow-bottomSheetShadow z-30`}
style={{
top: `${VIEW_HEIGHT - contentHeight - LAYOUT_MARGIN}px`,
}}
>
{hasHandlebar && (
<div className="mx-auto mt-[-0.5rem] mb-[1.5rem] w-[4rem] border-[0.125rem] border-[#F1F2F6]"></div>
<>
{isShowBottomsheet && (
<div className="fixed top-0 bottom-0 left-0 right-0 bg-[rgba(0,0,0,0.3)] z-30"></div>
)}
<div ref={contentRef}>{children}</div>
</motion.div>
<motion.div
drag="y"
initial="hidden"
{...(isAvailableHidden ? { onDragEnd } : {})}
animate={controls}
transition={{
type: 'spring',
damping: 40,
stiffness: 400,
}}
variants={{
visible: { y: 0 },
hidden: { y: '100%' },
}}
dragConstraints={{
top: 0,
bottom: contentHeight,
}} // 상단과 하단 드래그 제한 설정
dragElastic={0.2}
className={`fixed left-0 bottom-0 w-full h-[90vh] p-[1.5rem] pb-[2.5rem] rounded-t-[2.5rem] bg-white shadow-bottomSheetShadow z-30`}
style={{
top: `${VIEW_HEIGHT - contentHeight - LAYOUT_MARGIN}px`,
}}
>
{hasHandlebar && (
<div className="mx-auto mt-[-0.5rem] mb-[1.5rem] w-[4rem] border-[0.125rem] border-[#F1F2F6]"></div>
)}
<div ref={contentRef}>{children}</div>
</motion.div>
</>
);
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/Common/Header/BaseHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const BaseHeader = ({
title,
}: HeaderProps) => {
return (
<section className="w-full h-[3.5rem] px-[0.75rem] py-[0.5rem] flex justify-between items-center bg-white">
<section className="w-full h-[3.5rem] px-[0.75rem] py-[0.5rem] flex justify-between items-center bg-white opacity-70">
{hasBackButton ? (
<button
className="p-[0.5rem] rounded-[0.75rem] border border-solid border-[#ECECEC]"
Expand Down
6 changes: 3 additions & 3 deletions src/components/Common/SearchSortDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ type DropdownProps = {
// DropdownModal 컴포넌트: 드롭다운 옵션을 표시하는 모달
const DropdownModal = ({ options, value, onSelect }: DropdownProps) => {
return (
<div className="w-max absolute top-[1.7rem] right-0 shadow rounded-2xl bg-white border border-[#dcdcdc] flex flex-row items-start justify-start p-2 text-left text-sm text-[#656565]">
<div className="flex-1 flex flex-col items-start justify-start gap-[5px]">
<div className="w-max absolute top-[1.7rem] right-0 shadow rounded-2xl bg-white border border-[#dcdcdc] flex flex-row items-start justify-start p-2 text-left text-sm text-black">
<div className="flex-1 flex flex-col items-center justify-start gap-[5px]">
{options.map((option, index) => (
<div
key={index}
className={`px-2 py-2 ${value == option && 'bg-[#f4f4f9] text-[#1e1926]'} 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;
Loading

0 comments on commit bb674bb

Please sign in to comment.