From ea9f45b04fa71597ed9975c7cedcdbfe5ccca7ea Mon Sep 17 00:00:00 2001 From: kutta97 <yhjin04170@gmail.com> Date: Sat, 2 Dec 2023 18:20:08 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix(shelter):=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=A4=91=EB=B3=B5=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/shelter/src/pages/my/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/shelter/src/pages/my/index.tsx b/apps/shelter/src/pages/my/index.tsx index 32a1ca1b..423b6f98 100644 --- a/apps/shelter/src/pages/my/index.tsx +++ b/apps/shelter/src/pages/my/index.tsx @@ -99,7 +99,6 @@ function ShelterMy() { settingItems={[ { itemTitle: '계정 정보 수정하기', onClick: goSettingsAccount }, { itemTitle: '비밀번호 변경하기', onClick: goSettingsPassword }, - { itemTitle: '로그아웃하기', onClick: goSettingsPassword }, ]} /> <SettingGroup From 85e2abae73c9b2ead92feb0cb411fc14fe736ea4 Mon Sep 17 00:00:00 2001 From: kutta97 <yhjin04170@gmail.com> Date: Sat, 2 Dec 2023 18:20:40 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix(shelter):=20=EB=B3=B4=ED=98=B8=EC=86=8C?= =?UTF-8?q?=20=EC=95=B1=EC=9D=98=20=EB=A7=88=EC=9D=B4=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20padding=20bottom=2050px=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/shelter/src/pages/my/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/shelter/src/pages/my/index.tsx b/apps/shelter/src/pages/my/index.tsx index 423b6f98..b6198145 100644 --- a/apps/shelter/src/pages/my/index.tsx +++ b/apps/shelter/src/pages/my/index.tsx @@ -67,7 +67,7 @@ function ShelterMy() { }; return ( - <Box> + <Box pb="50px"> <ProfileInfo infoImage={imageUrl} infoTitle={name} From 68b2a67edeb8eb0f6f94efe8b81fc5781e19382b Mon Sep 17 00:00:00 2001 From: Dongja <jdh981118@naver.com> Date: Sat, 2 Dec 2023 19:15:34 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix(shelter):=20=EB=AA=A8=EC=A7=91=EA=B8=80?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1,=20=EC=88=98=EC=A0=95=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EC=97=90=EC=84=9C=20=EC=8B=9C=EA=B0=84=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95=20(#308)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/volunteers/update/index.tsx | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/apps/shelter/src/pages/volunteers/update/index.tsx b/apps/shelter/src/pages/volunteers/update/index.tsx index 41802e6e..51e88a35 100644 --- a/apps/shelter/src/pages/volunteers/update/index.tsx +++ b/apps/shelter/src/pages/volunteers/update/index.tsx @@ -13,10 +13,11 @@ import { } from '@chakra-ui/react'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { useCallback, useEffect } from 'react'; +import { Suspense, useCallback, useEffect } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; import { useNavigate, useParams } from 'react-router-dom'; import EditPhotoList from 'shared/components/EditPhotoList'; +import Loader from 'shared/components/Loader'; import { usePhotosUpload } from 'shared/hooks/usePhotosUpload'; import { getKoreanTime } from 'shared/utils/date'; import * as z from 'zod'; @@ -31,21 +32,26 @@ import useGetVolunteerDetail, { const recruitmentSchema = z .object({ title: z.string().min(1, '제목은 필수로 입력해주세요'), - startTime: z.coerce.date(), - endTime: z.coerce.date(), - deadline: z.coerce.date(), + startTime: z.string(), + endTime: z.string(), + deadline: z.string(), capacity: z.coerce.number(), content: z .string() .optional() .refine((val) => val?.length && val.length < 500, '에러입니다'), }) - .refine(({ startTime, endTime }) => startTime.getTime() < endTime.getTime(), { - message: '봉사 시작 일시 이후로 입력해주세요 ', - path: ['endTime'], - }) .refine( - ({ startTime, deadline }) => deadline.getTime() <= startTime.getTime(), + ({ startTime, endTime }) => + new Date(startTime).getTime() < new Date(endTime).getTime(), + { + message: '봉사 시작 일시 이후로 입력해주세요 ', + path: ['endTime'], + }, + ) + .refine( + ({ startTime, deadline }) => + new Date(deadline).getTime() <= new Date(startTime).getTime(), { message: '봉사 시작 일시 전으로 입력해주세요', path: ['deadLine'], @@ -56,14 +62,11 @@ type RecruitmentSchema = z.infer<typeof recruitmentSchema>; const UPLOAD_LIMIT = 5; -export default function VolunteersUpdatePage() { +const UpdateForm = () => { const { id: recruitmentId } = useParams<{ id: string }>() as { id: string }; const navigate = useNavigate(); const queryClient = useQueryClient(); - // TODO 이 훅에서 startDate, endDate와 같은 날짜데이터를 가공하기 때문에 - // 다른 훅을 만들어서 사용해야 할 것 같습니다. - // 혹은 훅 내의 select 옵션을 수정해야 할 것 같습니다. const { data: recruitment, isPending: isRecruitFetchLoading } = useGetVolunteerDetail(Number(recruitmentId)); @@ -111,9 +114,9 @@ export default function VolunteersUpdatePage() { recruitmentId: Number(recruitmentId), request: { ...data, - startTime: getKoreanTime(startTime).toISOString(), - endTime: getKoreanTime(endTime).toISOString(), - deadline: getKoreanTime(deadline).toISOString(), + startTime: getKoreanTime(new Date(startTime)).toISOString(), + endTime: getKoreanTime(new Date(endTime)).toISOString(), + deadline: getKoreanTime(new Date(deadline)).toISOString(), imageUrls: photos .filter(({ url }) => url !== 'upload-failed') .map(({ url }) => url), @@ -124,9 +127,9 @@ export default function VolunteersUpdatePage() { const setVolunteersRecruitmentFormvalues = useCallback( (recruitment: RecruitmentDetail) => { setValue('title', recruitment.title); - setValue('startTime', new Date(recruitment.startTime)); - setValue('endTime', new Date(recruitment.endTime)); - setValue('deadline', new Date(recruitment.deadline)); + setValue('startTime', recruitment.startTime.slice(0, -3)); + setValue('endTime', recruitment.endTime.slice(0, -3)); + setValue('deadline', recruitment.deadline.slice(0, -3)); setValue('capacity', recruitment.capacity); setValue('content', recruitment?.content ?? ''); setImageUrls(recruitment.imageUrls); @@ -233,4 +236,12 @@ export default function VolunteersUpdatePage() { </form> </Box> ); +}; + +export default function VolunteersUpdatePage() { + return ( + <Suspense fallback={<Loader />}> + <UpdateForm /> + </Suspense> + ); } From ba54e90fadcdb20688ce519aa6d4be5d5db5a0db Mon Sep 17 00:00:00 2001 From: woo hyeonji <117665863+Eosdia@users.noreply.github.com> Date: Sat, 2 Dec 2023 19:26:18 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=EB=B4=89=EC=82=AC=EB=AA=A8?= =?UTF-8?q?=EC=A7=91=EB=A7=88=EA=B0=90=20=EC=B6=94=EA=B0=80=20(#309)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(shelter): 보호소 address validation 수정 * feat(shelter): 봉사모집 마감 api 추가 * feat(shelter): 봉사모집마감 추가 --- .../src/pages/settings/account/index.tsx | 2 +- .../src/pages/volunteers/detail/index.tsx | 74 ++++++++++++------- 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/apps/shelter/src/pages/settings/account/index.tsx b/apps/shelter/src/pages/settings/account/index.tsx index 0c8e621f..0962e811 100644 --- a/apps/shelter/src/pages/settings/account/index.tsx +++ b/apps/shelter/src/pages/settings/account/index.tsx @@ -29,7 +29,7 @@ const phoneRegx2 = /^(0(2|3[1-3]|4[1-4]|5[1-5]|6[1-4]))-(\d{3,4})-(\d{4})$/; const accountSchema = z.object({ name: z.string().trim().min(2, { message: '이름은 2글자 이상입니다' }), - address: z.string().min(3, { message: '보호소 주소 정보는 필수입니다' }), + address: z.string().min(1, { message: '보호소 주소 정보는 필수입니다' }), addressDetail: z .string() .trim() diff --git a/apps/shelter/src/pages/volunteers/detail/index.tsx b/apps/shelter/src/pages/volunteers/detail/index.tsx index 753e4668..bb72913d 100644 --- a/apps/shelter/src/pages/volunteers/detail/index.tsx +++ b/apps/shelter/src/pages/volunteers/detail/index.tsx @@ -5,14 +5,16 @@ import { HStack, Text, useDisclosure, + useToast, VStack, } from '@chakra-ui/react'; +import { useMutation } from '@tanstack/react-query'; import { Suspense, useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import AlertModal from 'shared/components/AlertModal'; import ImageCarousel from 'shared/components/ImageCarousel'; import InfoTextList from 'shared/components/InfoTextList'; -import { LabelProps } from 'shared/components/Label'; +import Label from 'shared/components/Label'; import LabelText from 'shared/components/LabelText'; import useDetailHeaderStore from 'shared/store/detailHeaderStore'; import { @@ -21,16 +23,21 @@ import { getDDay, } from 'shared/utils/date'; +import { + closeShelterRecruitment, + deleteShelterRecruitment, +} from '@/apis/recruitment'; + import useGetVolunteerDetail from './_hooks/useGetVolunteerDetail'; const handleDeletePost = (postId: number) => { - // TODO: VolunteerPost delete API 호출 - console.log('[Delete Volunteer] postId:', postId); + deleteShelterRecruitment(postId); }; function VolunteersDetail() { const setOnDelete = useDetailHeaderStore((state) => state.setOnDelete); + const toast = useToast(); useEffect(() => { setOnDelete(handleDeletePost); @@ -40,15 +47,34 @@ function VolunteersDetail() { }, [setOnDelete]); const navigate = useNavigate(); - const { id: recruitmentId } = useParams(); + const { id } = useParams(); + const recruitmentId = Number(id); const { isOpen, onOpen, onClose } = useDisclosure(); - const { data: recruitment } = useGetVolunteerDetail(Number(recruitmentId)); + const { data: recruitment } = useGetVolunteerDetail(recruitmentId); + + const { mutate: closedRecruitment } = useMutation({ + mutationFn: async (recruitmentId: number) => + closeShelterRecruitment(recruitmentId), + onSuccess: () => { + toast({ + position: 'top', + description: '모집마감되었습니다.', + status: 'success', + duration: 1500, + }); + setIsClosed(true); + }, + onError: (error) => { + console.error(error); + }, + }); const startDate = new Date(recruitment.startTime); const deadline = new Date(recruitment.deadline); const createdAt = new Date(recruitment.createdAt); + const volunteerDateDay = getDDay(recruitment.deadline); const volunteerDate = createFormattedTime(startDate); const volunteerDay = createWeekDayLocalString(startDate); @@ -56,26 +82,16 @@ function VolunteersDetail() { const deadlineDate = createFormattedTime(deadline); const deadlineDay = createWeekDayLocalString(deadline); - const [label, setLabel] = useState<LabelProps>({ - labelTitle: '모집중', - type: 'GREEN', - }); - const [isClosed, setIsClosed] = useState(false); - - useEffect(() => { - if (recruitment.isClosed) { - setIsClosed(true); - setLabel({ labelTitle: '마감완료', type: 'GRAY' }); - } - }, [recruitment.isClosed]); + const [isClosed, setIsClosed] = useState( + recruitment.isClosed || volunteerDateDay < 0, + ); const goManageApply = () => navigate(`/manage/apply/${recruitmentId}`); const goManageAttendance = () => navigate(`/manage/attendance/${recruitmentId}`); - const onCloseRecruitment = () => { + const onCloseRecruitment = (recruitmentId: number) => { + closedRecruitment(recruitmentId); onClose(); - setIsClosed(true); - setLabel({ labelTitle: '마감완료', type: 'GRAY' }); }; return ( @@ -84,16 +100,20 @@ function VolunteersDetail() { <ImageCarousel imageUrls={recruitment.imageUrls} /> )} <VStack spacing="5px" align="flex-start" p={4}> - <LabelText - labelTitle={label.labelTitle} - type={label.type} - content={`D-${getDDay(recruitment.deadline)}`} - /> + {isClosed ? ( + <Label labelTitle="마감완료" type="GRAY" /> + ) : ( + <LabelText + labelTitle="모집중" + content={`D-${volunteerDateDay === 0 ? 'Day' : volunteerDateDay}`} + /> + )} <Text fontSize="xl" fontWeight="semibold"> {recruitment.title} </Text> <Text fontSize="sm" fontWeight="normal" color="gray.500"> - 작성일 | {createFormattedTime(createdAt)}(수정됨) + 작성일 | {createFormattedTime(createdAt)} + {recruitment.createdAt && ' (수정됨)'} </Text> </VStack> <Divider /> @@ -168,7 +188,7 @@ function VolunteersDetail() { btnTitle="마감하기" isOpen={isOpen} onClose={onClose} - onClick={onCloseRecruitment} + onClick={() => onCloseRecruitment(recruitmentId)} /> </Box> );