Skip to content

Commit

Permalink
feat(volunteer): 봉사모집글 검색 페이지 msw 연결 및 무한 스크롤 기능 추가 (#164)
Browse files Browse the repository at this point in the history
* feat(volunteer): volunteerRecruitItem 에서 모집완료 상태에 따라 다른 Label 을 보여주는 기능 추가

* refactor(volunteer): createRecruitItem 함수를 util 함수로 분리

* fix(volunteer): recruitmentSearchFilter 타입의 keywordFilter 에 IS_SHELTER 추가

* feat(volunteer): 봉사모집글 검색페이지에 사용할 period 를 date 로 변환하는 util 함수 추가

* feat(volunteer): 봉사모집글 검색페이지에 msw 연결 및 무한 스크롤 기능 추가

* feat(volunteer): 봉사 모집글 검색 API response 에 recruitmentDeadline 추가

* feat(volunteer): 봉사 모집글 아이템에 디데이 계산 기능 추가

* feat(shared): 검색 period 관련 상수, 타입, 함수를 shared 로 이동

* refactor(shared): 공통화된 검색 period 를 보호소앱과 봉사자앱에서 사용하도록 수정
  • Loading branch information
kutta97 authored Nov 24, 2023
1 parent b08dea4 commit 776ef48
Show file tree
Hide file tree
Showing 15 changed files with 111 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { ChangeEvent } from 'react';
import SearchFilters, {
SearchFilterSelectData,
} from 'shared/components/SearchFilters';
import { PERIOD } from 'shared/constants/period';

import {
PERIOD,
RECRUITMENT_STATUS,
SEARCH_TYPE,
} from '@/pages/volunteers/search/_constants/filter';
Expand Down
7 changes: 0 additions & 7 deletions apps/shelter/src/pages/volunteers/search/_constants/filter.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
export const PERIOD = {
WITHIN_ONE_DAY: '1일 이내',
WITHIN_ONE_WEEK: '1주 이내',
WITHIN_ONE_MONTH: '1달 이내',
WITHIN_THREE_MONTH: '3달 이내',
} as const;

export const RECRUITMENT_STATUS = {
IS_OPENED: '모집 중',
IS_CLOSED: '모집 완료',
Expand Down
4 changes: 2 additions & 2 deletions apps/shelter/src/pages/volunteers/search/_types/filter.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Period } from 'shared/types/period';

import {
PERIOD,
RECRUITMENT_STATUS,
SEARCH_TYPE,
} from '@/pages/volunteers/search/_constants/filter';

export type Period = keyof typeof PERIOD;
export type RecruitmentStatus = keyof typeof RECRUITMENT_STATUS;
export type SearchType = keyof typeof SEARCH_TYPE;

Expand Down
2 changes: 1 addition & 1 deletion apps/shelter/src/pages/volunteers/search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { Box } from '@chakra-ui/react';
import { useInfiniteQuery } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import useIntersect from 'shared/hooks/useIntersection';
import { getDatesFromPeriod } from 'shared/utils/period';

import RecruitItem from '@/pages/volunteers/_components/RecruitItem';
import recruitmentQueryOptions from '@/pages/volunteers/_queryOptions/recruitment';
import RecruitmentsSearchFilter from '@/pages/volunteers/search/_components/RecruitmentsSearchFilter';
import { useRecruitmentSearch } from '@/pages/volunteers/search/_hooks/useRecruitmentSearch';
import { SearchFilter } from '@/pages/volunteers/search/_types/filter';
import { getDatesFromPeriod } from '@/pages/volunteers/search/_utils/period';
import { RecruitmentSearchFilter } from '@/types/apis/recruitment';

const getVolunteerSearchRequestFilter = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import { AspectRatio, Box, Flex, Image, Text, VStack } from '@chakra-ui/react';
import { MouseEvent } from 'react';
import ApplicantStatus from 'shared/components/ApplicantStatus';
import InfoSubText from 'shared/components/InfoSubtext';
import Label from 'shared/components/Label';
import LabelText from 'shared/components/LabelText';

type Recruitment = {
id: number;
title: string;
shelterName: string;
shelterProfileImage: string;
isRecruitmentClosed: boolean;
volunteerDate: string;
volunteerDateDday: number;
volunteerDateDday: string;
applicantCount: number;
recruitmentCapacity: number;
};
Expand All @@ -29,6 +31,7 @@ export default function VolunteerRecruitItem({
title,
shelterName,
shelterProfileImage,
isRecruitmentClosed,
volunteerDate,
volunteerDateDday,
applicantCount,
Expand All @@ -49,7 +52,15 @@ export default function VolunteerRecruitItem({
</AspectRatio>
<Box w="full" pos="relative">
<VStack w="full" align="start" gap={1}>
<LabelText content={`D-${volunteerDateDday}`} labelTitle="모집중" />
{isRecruitmentClosed ? (
<Label type="GRAY" labelTitle="마감완료" />
) : (
<LabelText
type="GREEN"
content={`D-${volunteerDateDday}`}
labelTitle="모집중"
/>
)}
<Text fontWeight="bold" lineHeight={6}>
{title}
</Text>
Expand Down
32 changes: 32 additions & 0 deletions apps/volunteer/src/pages/volunteers/_utils/recruitment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createFormattedTime, getDDay } from 'shared/utils/date';

import { Recruitment } from '@/types/apis/recruitment';

export const createRecruitmentItem = (recruitment: Recruitment) => {
const {
recruitmentId,
recruitmentTitle,
shelterName,
shelterImageUrl,
recruitmentApplicantCount,
recruitmentCapacity,
recruitmentStartTime,
recruitmentDeadline,
recruitmentIsClosed,
} = recruitment;

return {
id: recruitmentId,
title: recruitmentTitle,
shelterName: shelterName,
shelterProfileImage: shelterImageUrl,
isRecruitmentClosed: recruitmentIsClosed,
volunteerDate: createFormattedTime(
new Date(recruitmentStartTime),
'YY.MM.DD',
),
volunteerDateDday: getDDay(recruitmentDeadline),
applicantCount: recruitmentApplicantCount,
recruitmentCapacity: recruitmentCapacity,
};
};
30 changes: 1 addition & 29 deletions apps/volunteer/src/pages/volunteers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,10 @@ import { useSuspenseInfiniteQuery } from '@tanstack/react-query';
import { MouseEvent, Suspense } from 'react';
import { useNavigate } from 'react-router-dom';
import useIntersect from 'shared/hooks/useIntersection';
import { createFormattedTime } from 'shared/utils/date';

import VolunteerRecruitItem from '@/pages/volunteers/_components/VolunteerRecruitItem';
import recruitmentQueryOptions from '@/pages/volunteers/_queryOptions/recruitments';
import { Recruitment } from '@/types/apis/recruitment';

const createRecruitmentItem = (recruitment: Recruitment) => {
const {
recruitmentId,
recruitmentTitle,
shelterName,
shelterImageUrl,
recruitmentApplicantCount,
recruitmentCapacity,
recruitmentStartTime,
} = recruitment;

// TODO: volunteerDateDday 계산하기 위해 recruitmentDeadline 필요
return {
id: recruitmentId,
title: recruitmentTitle,
shelterName: shelterName,
shelterProfileImage: shelterImageUrl,
volunteerDate: createFormattedTime(
new Date(recruitmentStartTime),
'YY.MM.DD',
),
volunteerDateDday: 12,
applicantCount: recruitmentApplicantCount,
recruitmentCapacity: recruitmentCapacity,
};
};
import { createRecruitmentItem } from '@/pages/volunteers/_utils/recruitment';

function Recruitments() {
const navigate = useNavigate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { ChangeEvent } from 'react';
import SearchFilters, {
SearchFilterSelectData,
} from 'shared/components/SearchFilters';
import { PERIOD } from 'shared/constants/period';

import {
PERIOD,
RECRUITMENT_STATUS,
SEARCH_TYPE,
} from '@/pages/volunteers/search/_constants/filter';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
export const PERIOD = {
WITHIN_ONE_DAY: '1일 이내',
WITHIN_ONE_WEEK: '1주 이내',
WITHIN_ONE_MONTH: '1달 이내',
WITHIN_THREE_MONTH: '3달 이내',
} as const;

export const RECRUITMENT_STATUS = {
IS_OPENED: '모집 중',
IS_CLOSED: '모집 완료',
Expand Down
4 changes: 2 additions & 2 deletions apps/volunteer/src/pages/volunteers/search/_types/filter.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Period } from 'shared/types/period';

import {
PERIOD,
RECRUITMENT_STATUS,
SEARCH_TYPE,
} from '@/pages/volunteers/search/_constants/filter';

export type Period = keyof typeof PERIOD;
export type RecruitmentStatus = keyof typeof RECRUITMENT_STATUS;
export type SearchType = keyof typeof SEARCH_TYPE;

Expand Down
60 changes: 45 additions & 15 deletions apps/volunteer/src/pages/volunteers/search/index.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
import { Box } from '@chakra-ui/react';
import { useInfiniteQuery } from '@tanstack/react-query';
import { MouseEvent } from 'react';
import { useNavigate } from 'react-router-dom';
import useIntersect from 'shared/hooks/useIntersection';
import { getDatesFromPeriod } from 'shared/utils/period';

import VolunteerRecruitItem from '@/pages/volunteers/_components/VolunteerRecruitItem';
import recruitmentQueryOptions from '@/pages/volunteers/_queryOptions/recruitments';
import { createRecruitmentItem } from '@/pages/volunteers/_utils/recruitment';
import RecruitmentsSearchFilter from '@/pages/volunteers/search/_components/RecruitmentsSearchFilter';
import { useRecruitmentSearch } from '@/pages/volunteers/search/_hooks/useRecruitmentSearch';
import { SearchFilter } from '@/pages/volunteers/search/_types/filter';
import { RecruitmentSearchFilter } from '@/types/apis/recruitment';

const DUMMY_RECRUITMENT = {
id: 1,
title: '봉사자 모집합니다!!',
shelterName: '양천구 보건소',
shelterProfileImage: 'https://source.unsplash.com/random',
volunteerDate: '23.10.23',
volunteerDateDday: 12,
applicantCount: 2,
recruitmentCapacity: 6,
};
const getVolunteerSearchRequestFilter = (
searchFilter: Partial<SearchFilter>,
): Partial<RecruitmentSearchFilter> => {
const { keyword, period, recruitmentStatus, searchType } = searchFilter;
const { startDate, endDate } = getDatesFromPeriod(period);

const DUMMY_RECRUITMENT_LIST = Array.from(
{ length: 4 },
() => DUMMY_RECRUITMENT,
);
return {
keyword,
startDate,
endDate,
closedFilter: recruitmentStatus,
keywordFilter: searchType,
};
};

export default function VolunteersSearchPage() {
const { isKeywordSearched, searchFilter, handleChangeSearchFilter } =
Expand All @@ -36,23 +42,47 @@ export default function VolunteersSearchPage() {
}
};

const { data, hasNextPage, isFetchingNextPage, fetchNextPage, isLoading } =
useInfiniteQuery(
recruitmentQueryOptions.search(
getVolunteerSearchRequestFilter(searchFilter),
isKeywordSearched,
),
);

const recruitments = data?.pages
.flatMap(({ data }) => data.recruitments)
.map(createRecruitmentItem);

const ref = useIntersect(async (entry, observer) => {
observer.unobserve(entry.target);
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
});

if (!isKeywordSearched) {
return null;
}

if (isLoading) {
return <p>로딩중</p>;
}

return (
<Box>
<RecruitmentsSearchFilter
searchFilter={searchFilter}
onChangeFilter={handleChangeSearchFilter}
/>
{DUMMY_RECRUITMENT_LIST?.map((recruitment) => (
{recruitments?.map((recruitment) => (
<VolunteerRecruitItem
key={recruitment.id}
recruitment={recruitment}
onClickItem={goVolunteersDetail}
/>
))}
<div ref={ref} />
</Box>
);
}
3 changes: 2 additions & 1 deletion apps/volunteer/src/types/apis/recruitment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type Recruitment = {
recruitmentTitle: string;
recruitmentStartTime: string;
recruitmentEndTime: string;
recruitmentDeadline: string;
recruitmentIsClosed: boolean;
recruitmentApplicantCount: number;
recruitmentCapacity: number;
Expand All @@ -31,7 +32,7 @@ export type RecruitmentSearchFilter = {
startDate: string;
endDate: string;
closedFilter: 'IS_CLOSED' | 'IS_OPENED';
keywordFilter: 'IS_TITLE' | 'IS_CONTENT';
keywordFilter: 'IS_TITLE' | 'IS_CONTENT' | 'IS_SHELTER_NAME';
};

export type RecruitmentsRequest = Partial<RecruitmentSearchFilter> & Pagination;
6 changes: 6 additions & 0 deletions packages/shared/constants/period.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const PERIOD = {
WITHIN_ONE_DAY: '1일 이내',
WITHIN_ONE_WEEK: '1주 이내',
WITHIN_ONE_MONTH: '1달 이내',
WITHIN_THREE_MONTH: '3달 이내',
} as const;
3 changes: 3 additions & 0 deletions packages/shared/types/period.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { PERIOD } from '../constants/period';

export type Period = keyof typeof PERIOD;
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createFormattedTime } from 'shared/utils/date';

import { Period } from '@/pages/volunteers/search/_types/filter';
import { Period } from '../types/period';
import { createFormattedTime } from './date';

const periodEndDate: Record<Period, (date: Date) => number> = {
WITHIN_ONE_DAY: (date: Date) => date.getDate() + 1,
Expand Down

0 comments on commit 776ef48

Please sign in to comment.