Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 보호소의 봉사자 프로필 페이지 api를 포함한 로직 추가 #216

Merged
merged 7 commits into from
Nov 29, 2023
31 changes: 31 additions & 0 deletions apps/shelter/src/apis/volunteers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import axiosInstance from 'shared/apis/axiosInstance';

import {
VolunteerCompletedsRequestParams,
VolunteerProfileResponseData,
VolunteerReviewsOnVolunteerResponseData,
VoluteerRecruitmentsOnVolunteerResponseData,
} from '@/types/apis/volunteers';

export const getVolunteerProfile = (volunteerId: number) =>
axiosInstance.get<VolunteerProfileResponseData>(
`/shelters/volunteers/${volunteerId}/profile`,
);

export const getVolunteerReviewsOnVolunteer = (
volunteerId: number,
params: VolunteerCompletedsRequestParams,
) =>
axiosInstance.get<VolunteerReviewsOnVolunteerResponseData>(
`/shelters/volunteers/${volunteerId}/reviews`,
{ params },
);

export const getVolunteerRecruitmentsOnVolunteer = (
volunteerId: number,
params: VolunteerCompletedsRequestParams,
) =>
axiosInstance.get<VoluteerRecruitmentsOnVolunteerResponseData>(
`/shelters/volunteers/${volunteerId}/recruitments/completed`,
{ params },
);
2 changes: 2 additions & 0 deletions apps/shelter/src/mocks/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { handlers as manageHandlers } from './handlers/manage';
import { handlers as recruitmentHandler } from './handlers/recruitment';
import { handlers as recruitmentDetailHandler } from './handlers/recruitmentDetail';
import { handlers as shelterHandlers } from './handlers/shelter';
import { handlers as volunteerHandlers } from './handlers/volunteers';

export const worker = setupWorker(
...authHandlers,
...shelterHandlers,
...recruitmentHandler,
...recruitmentDetailHandler,
...manageHandlers,
...volunteerHandlers,
);
62 changes: 62 additions & 0 deletions apps/shelter/src/mocks/handlers/volunteers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { delay, http, HttpResponse } from 'msw';

const DUMMY_REVIEWS_DATA = {
reviewId: 36,
shelterName: '남양주 보호소',
reviewCreatedAt: '2023-03-16T18:00',
reviewContent: '아이들이 너무 귀여워서 봉사하는 시간이 즐거웠습니다~!',
reviewImageUrls: [
'https://source.unsplash.com/random',
'https://source.unsplash.com/random',
'https://source.unsplash.com/random',
],
};

const DUMMY_RECRUITMENTS = {
recruitmentId: 1,
recruitmentTitle: '봉사자를 모집합니다',
recruitmentStartTime: '2023-03-16T18:00:00',
shelterName: '마석 보호소',
};

export const handlers = [
http.get('/shelters/volunteers/:volunteerId/profile', async () => {
await delay(200);
return HttpResponse.json(
{
volunteerEmail: '[email protected]',
volunteerName: '홍길동',
volunteerTemperature: 36,
volunteerImageUrl: 'https://source.unsplash.com/random',
volunteerPhoneNumber: '010-8237-1847',
},
{ status: 200 },
);
}),
http.get('/shelters/volunteers/:volunteerId/reviews', async () => {
await delay(2000);
return HttpResponse.json(
{
pageInfo: {
totalElements: 20,
hasNext: true,
},
reviews: Array.from({ length: 10 }, () => DUMMY_REVIEWS_DATA),
},
{ status: 200 },
);
}),
http.get(
'/shelters/volunteers/:volunteerId/recruitments/completed',
async () => {
await delay(2000);
return HttpResponse.json({
pageInfo: {
totalElements: 20,
hasNext: true,
},
recruitments: Array.from({ length: 10 }, () => DUMMY_RECRUITMENTS),
});
},
),
];

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Box, Card, CardBody, Heading, Text } from '@chakra-ui/react';
import { useSuspenseInfiniteQuery } from '@tanstack/react-query';
import useIntersect from 'shared/hooks/useIntersection';
import { createFormattedTime } from 'shared/utils/date';

import { getVolunteerRecruitmentsOnVolunteer } from '@/apis/volunteers';

type VolunteerRecruitmentItemProps = {
recruitmentTitle: string;
recruitmentStartTime: string;
shelterName: string;
};

type VolunteerRecruitmentsProps = {
id: number;
};

export default function VolunteerRecruitments({
id,
}: VolunteerRecruitmentsProps) {
const {
data: { pages },
hasNextPage,
isFetchingNextPage,
fetchNextPage,
} = useSuspenseInfiniteQuery({
queryKey: ['volunteer', 'profile', 'recruitments', id],
queryFn: ({ pageParam }) =>
getVolunteerRecruitmentsOnVolunteer(id, { page: pageParam, size: 10 }),
initialPageParam: 0,
getNextPageParam: ({ data: { pageInfo } }, _, lastPageParam) =>
pageInfo.hasNext ? lastPageParam + 1 : null,
});

const totalRecruitments = pages[0].data.pageInfo.totalElements;
const recruitments = pages.flatMap(
({ data: { recruitments } }) => recruitments,
);

const ref = useIntersect((entry, observer) => {
observer.unobserve(entry.target);

if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
});

return (
<Box>
<Heading fontWeight={600} fontSize="md" py={4}>
봉사 이력 {totalRecruitments}개
</Heading>
{recruitments.map((recruitment, index) => (
<VolunteerRecruitmentItem key={index} {...recruitment} />
))}
<Box ref={ref} />
</Box>
);
}

function VolunteerRecruitmentItem({
shelterName,
recruitmentTitle,
recruitmentStartTime,
}: VolunteerRecruitmentItemProps) {
return (
<Card p={4} pb={3.5} mb={2}>
<CardBody pos="relative" p={0}>
<Text pb={2} fontWeight={600}>
{recruitmentTitle}
</Text>
<Text fontSize="sm" color="gray.400">
{shelterName}
</Text>
<Text fontSize="sm" color="black">
{`봉사일 | ${createFormattedTime(new Date(recruitmentStartTime))}`}
</Text>
</CardBody>
</Card>
);
}
Original file line number Diff line number Diff line change
@@ -1,53 +1,72 @@
import { Box, Heading, HStack, Text, VStack } from '@chakra-ui/react';
import { useSuspenseInfiniteQuery } from '@tanstack/react-query';
import InfoSubtext from 'shared/components/InfoSubtext';
import ReviewItem from 'shared/components/ReviewItem';
import useIntersect from 'shared/hooks/useIntersection';
import { createFormattedTime } from 'shared/utils/date';

const DUMMY_REVIEWS_DATA = {
reviewId: 36,
shelterName: '남양주 보호소',
reviewCreatedAt: '2023-03-16T18:00',
reviewContent: '아이들이 너무 귀여워서 봉사하는 시간이 즐거웠습니다~!',
images: [
'https://source.unsplash.com/random',
'https://source.unsplash.com/random',
'https://source.unsplash.com/random',
],
};
import { getVolunteerReviewsOnVolunteer } from '@/apis/volunteers';

const DUMMY_DATA = {
pageInfo: {
totalElements: 32,
hasNext: false,
},
reviews: Array.from({ length: 5 }, () => DUMMY_REVIEWS_DATA),
type VolunteerReviewsProps = {
id: number;
};

export default function VolunteerReviews() {
export default function VolunteerReviews({ id }: VolunteerReviewsProps) {
const {
data: { pages },
hasNextPage,
isFetchingNextPage,
fetchNextPage,
} = useSuspenseInfiniteQuery({
queryKey: ['volunteer', 'profile', 'reviews', id],
queryFn: ({ pageParam }) =>
getVolunteerReviewsOnVolunteer(id, { page: pageParam, size: 10 }),
initialPageParam: 0,
getNextPageParam: ({ data: { pageInfo } }, _, lastPageParam) =>
pageInfo.hasNext ? lastPageParam + 1 : null,
});

const totalReviews = pages[0].data.pageInfo.totalElements;
const reviews = pages.flatMap(({ data: { reviews } }) => reviews);

const ref = useIntersect((entry, observer) => {
observer.unobserve(entry.target);

if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
});

return (
<Box>
<Heading fontSize="md" py={4}>
봉사 후기 {DUMMY_DATA.reviews.length}개
봉사 후기 {totalReviews}개
</Heading>
<VStack spacing={2}>
{DUMMY_DATA.reviews.map(
({ shelterName, reviewContent, reviewCreatedAt, images }, index) => {
return (
<ReviewItem key={index} content={reviewContent} images={images}>
<Box>
<HStack mb={1}>
<Text fontWeight={600}>{shelterName}</Text>
</HStack>
<InfoSubtext
title="작성일"
content={createFormattedTime(new Date(reviewCreatedAt))}
/>
</Box>
</ReviewItem>
);
},
{reviews.map(
(
{ shelterName, reviewContent, reviewCreatedAt, reviewImageUrls },
index,
) => (
<ReviewItem
key={index}
content={reviewContent}
images={reviewImageUrls}
>
<Box>
<HStack mb={1}>
<Text fontWeight={600}>{shelterName}</Text>
</HStack>
<InfoSubtext
title="작성일"
content={createFormattedTime(new Date(reviewCreatedAt))}
/>
</Box>
</ReviewItem>
),
)}
</VStack>
<Box ref={ref} />
</Box>
);
}
Loading