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>
   );