From 7872e070fde7e786b19dc70854e97a166d031197 Mon Sep 17 00:00:00 2001 From: holdn2 Date: Tue, 23 Dec 2025 15:24:04 +0900 Subject: [PATCH 01/19] =?UTF-8?q?feat:=20=EA=B8=B0=EC=A1=B4=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=EA=B0=80=20=EC=9E=88=EB=8B=A4=EB=A9=B4=20?= =?UTF-8?q?=ED=8F=BC=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../estimate/hooks/use-estimate-form.ts | 4 +- .../@owner/food-truck-form/FoodTruckForm.tsx | 23 +++++----- .../food-truck-form/constants/food-truck.ts | 2 +- .../hooks/use-food-truck-form.ts | 44 +++++++++++++++++-- .../schemas/food-truck-form.schema.ts | 13 +++--- .../hooks/use-food-truck-detail.ts | 5 ++- src/shared/constant/need-electricity.ts | 2 +- src/shared/constant/payment-method.ts | 4 +- src/shared/utils/date/date-formatter.ts | 4 +- 9 files changed, 71 insertions(+), 30 deletions(-) diff --git a/src/pages/@owner/estimate/hooks/use-estimate-form.ts b/src/pages/@owner/estimate/hooks/use-estimate-form.ts index 98ee82a4..1e26a965 100644 --- a/src/pages/@owner/estimate/hooks/use-estimate-form.ts +++ b/src/pages/@owner/estimate/hooks/use-estimate-form.ts @@ -6,7 +6,7 @@ import type { } from 'apis/data-contracts'; import { zodResolver } from '@hookform/resolvers/zod'; -import { formatEstimateDatesToAvailableDates } from '@utils/date'; +import { formatStringDatesToAvailableDates } from '@utils/date'; import { estimateSchema, type EstimateFormData, @@ -67,7 +67,7 @@ export const useEstimateForm = ( { location: estimateData.address ?? '', detailLocation: estimateData.detailAddress ?? '', - availableDates: formatEstimateDatesToAvailableDates( + availableDates: formatStringDatesToAvailableDates( estimateData.reservationDates ?? [] ), activeTime: estimateData.operationHour ?? undefined, diff --git a/src/pages/@owner/food-truck-form/FoodTruckForm.tsx b/src/pages/@owner/food-truck-form/FoodTruckForm.tsx index c23b610e..001b5024 100644 --- a/src/pages/@owner/food-truck-form/FoodTruckForm.tsx +++ b/src/pages/@owner/food-truck-form/FoodTruckForm.tsx @@ -4,6 +4,9 @@ import { FormProvider } from 'react-hook-form'; import Navigation from '@layout/navigation/Navigation'; import { Icon } from '@icon/Icon'; import Button from '@ui/button/Button'; +import { ROUTES } from '@router/constant/routes'; +import useToast from '@hooks/use-toast'; + import { FoodTruckName, FoodTruckDescription, @@ -12,7 +15,6 @@ import { FoodTruckOption, FoodTruckPhoto, } from '@pages/@owner/food-truck-form/@section/basic-info-section/index'; - import { AvailableQuantity, NeedElectricity, @@ -30,8 +32,6 @@ import { } from '@pages/@owner/food-truck-form/constants/food-truck'; import ActiveTime from '@components/active-time/ActiveTime'; import ActiveDate from '@components/active-date/ActiveDate'; -import useFoodTruckDetail from '@pages/food-truck-detail/hooks/use-food-truck-detail'; -import { ROUTES } from '@router/constant/routes'; // 메인 컴포넌트 export default function FoodTruckForm() { @@ -40,9 +40,10 @@ export default function FoodTruckForm() { const navigate = useNavigate(); const location = useLocation(); + const toast = useToast(); - // TODO: id 값이 있을 시 푸드트럭 정보 가져오기 - const { methods, reset, isFormValid, handleSubmit } = useFoodTruckForm(); + const { isEdit, methods, reset, isFormValid, handleSubmit } = + useFoodTruckForm(foodTruckIdNumber); const { formActiveTime, @@ -58,8 +59,6 @@ export default function FoodTruckForm() { handleActiveDateSetValue, handleActiveDateError, } = useFoodTruckFormDate(methods); - // 서버에서 활동 가능 지역은 지역코드로 받아야함 - const { foodTruckDetailData } = useFoodTruckDetail(foodTruckIdNumber); useEffect(() => { if (location.state?.formData && location.state?.from) { @@ -67,6 +66,12 @@ export default function FoodTruckForm() { } }, [location.state, reset]); + if (!foodTruckId || isNaN(foodTruckIdNumber)) { + toast.error('잘못된 접근입니다.'); + navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); + return null; + } + const handleNavigateBack = () => { navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); }; @@ -74,9 +79,7 @@ export default function FoodTruckForm() { return ( } handleLeftClick={handleNavigateBack} /> diff --git a/src/pages/@owner/food-truck-form/constants/food-truck.ts b/src/pages/@owner/food-truck-form/constants/food-truck.ts index 67639459..3fef08e4 100644 --- a/src/pages/@owner/food-truck-form/constants/food-truck.ts +++ b/src/pages/@owner/food-truck-form/constants/food-truck.ts @@ -23,7 +23,7 @@ export const FOOD_TRUCK_MAX_LENGTH = { }, availableDates: { min: 1, - max: 2, + max: 4, }, photoUrls: { min: 1, diff --git a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts index b635a584..afbca31e 100644 --- a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts +++ b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts @@ -1,10 +1,14 @@ -import { zodResolver } from '@hookform/resolvers/zod'; +import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; + +import { formatStringDatesToAvailableDates } from '@utils/date'; import { FOOD_TRUCK_ERROR_MESSAGE } from '@pages/@owner/food-truck-form/constants/food-truck'; import { foodTruckSchema, type FoodTruckFormData, } from '@pages/@owner/food-truck-form/schemas/food-truck-form.schema'; +import useFoodTruckDetail from '@pages/food-truck-detail/hooks/use-food-truck-detail'; const initialData = { name: '', @@ -25,10 +29,10 @@ const initialData = { menus: false, }; -export const useFoodTruckForm = (prevData?: FoodTruckFormData) => { +export const useFoodTruckForm = (foodTruckIdNumber: number) => { const methods = useForm({ resolver: zodResolver(foodTruckSchema), - defaultValues: prevData ?? initialData, + defaultValues: initialData, mode: 'onChange', }); @@ -39,6 +43,39 @@ export const useFoodTruckForm = (prevData?: FoodTruckFormData) => { setError, } = methods; + const [isEdit, setIsEdit] = useState(false); + + const { foodTruckDetailData } = useFoodTruckDetail(foodTruckIdNumber); + + // TODO: 메뉴 리스트 조회 필요. 있다면 활성화, 없다면 비활성화 + + useEffect(() => { + if (foodTruckDetailData) { + setIsEdit(true); + reset({ + name: foodTruckDetailData.name, + nameDuplicate: true, + description: foodTruckDetailData.description, + phoneNumber: foodTruckDetailData.phoneNumber, + regionCodes: foodTruckDetailData.regionCodes, + availableQuantity: foodTruckDetailData.availableQuantity, + needElectricity: foodTruckDetailData.needElectricity, + paymentMethod: foodTruckDetailData.paymentMethod, + menuCategories: foodTruckDetailData.menuCategories, + photoUrls: foodTruckDetailData.photoUrl, + operatingInfo: foodTruckDetailData.operatingInfo, + option: foodTruckDetailData.option, + availableDates: formatStringDatesToAvailableDates( + foodTruckDetailData.availableDates ?? [] + ), + activeTime: foodTruckDetailData.activeTime, + timeDiscussRequired: foodTruckDetailData.timeDiscussRequired, + // TODO : 메뉴 리스트 있을 때 true + menus: true, + }); + } + }, [foodTruckDetailData, reset]); + const onSubmit = async (formData: FoodTruckFormData) => { if (!formData.nameDuplicate) { setError('name', { @@ -54,6 +91,7 @@ export const useFoodTruckForm = (prevData?: FoodTruckFormData) => { return { // Form methods + isEdit, methods, handleSubmit: handleSubmit(onSubmit), reset, diff --git a/src/pages/@owner/food-truck-form/schemas/food-truck-form.schema.ts b/src/pages/@owner/food-truck-form/schemas/food-truck-form.schema.ts index db19d344..eb58ceb2 100644 --- a/src/pages/@owner/food-truck-form/schemas/food-truck-form.schema.ts +++ b/src/pages/@owner/food-truck-form/schemas/food-truck-form.schema.ts @@ -5,10 +5,7 @@ import { FOOD_TRUCK_ERROR_MESSAGE, FOOD_TRUCK_MAX_LENGTH, } from '@pages/@owner/food-truck-form/constants/food-truck'; -import { AVAILABLE_QUANTITY } from '@constant/available-quantity'; -import { NEED_ELECTRICITY } from '@constant/need-electricity'; -import { PAYMENT_METHOD } from '@constant/payment-method'; -import { FOOD_CATEGORIES } from '@constant/food-categories'; + import type { AvailableDate } from '@type/available-date'; import { validateFoodTruckFormTime } from '@pages/@owner/food-truck-form/utils/validate-food-truck-form-time'; @@ -34,10 +31,10 @@ export const foodTruckSchema = z.object({ FOOD_TRUCK_ERROR_MESSAGE.phoneNumber.required ), regionCodes: z.array(z.custom()), - availableQuantity: z.nativeEnum(AVAILABLE_QUANTITY), - needElectricity: z.nativeEnum(NEED_ELECTRICITY), - paymentMethod: z.nativeEnum(PAYMENT_METHOD), - menuCategories: z.array(z.nativeEnum(FOOD_CATEGORIES)), + availableQuantity: z.string(), + needElectricity: z.string(), + paymentMethod: z.string(), + menuCategories: z.array(z.string()), photoUrls: z.array(z.string()).min(1, { message: FOOD_TRUCK_ERROR_MESSAGE.photoUrls.required, }), diff --git a/src/pages/food-truck-detail/hooks/use-food-truck-detail.ts b/src/pages/food-truck-detail/hooks/use-food-truck-detail.ts index 92198b9d..04e59d70 100644 --- a/src/pages/food-truck-detail/hooks/use-food-truck-detail.ts +++ b/src/pages/food-truck-detail/hooks/use-food-truck-detail.ts @@ -10,6 +10,7 @@ const foodTruckDetailQuery = (foodTruckId: number) => ({ queryKey: FOOD_TRUCKS_QUERY_KEY.DETAIL(foodTruckId), queryFn: () => getFoodTruckDetail(foodTruckId), staleTime: 5000, + enabled: !!foodTruckId, }); export default function useFoodTruckDetail(foodTruckId: number) { @@ -19,7 +20,9 @@ export default function useFoodTruckDetail(foodTruckId: number) { data: foodTruckDetailData, isPending: isPendingFoodTruckDetail, isError: isErrorFoodTruckDetail, - } = useQuery(foodTruckDetailQuery(foodTruckId)); + } = useQuery( + foodTruckDetailQuery(foodTruckId) + ); const { mutate: updateSaveStatus } = useUpdateFoodTruckSaveStatus(foodTruckId); diff --git a/src/shared/constant/need-electricity.ts b/src/shared/constant/need-electricity.ts index e137f036..88fa9f5b 100644 --- a/src/shared/constant/need-electricity.ts +++ b/src/shared/constant/need-electricity.ts @@ -1,6 +1,6 @@ export const NEED_ELECTRICITY = { REQUIRED: '필요', - NOT_REQUIRED: '필요 없음', + NOT_REQUIRED: '불필요', NEED_DISCUSSION: '논의 필요', } as const; diff --git a/src/shared/constant/payment-method.ts b/src/shared/constant/payment-method.ts index f23d2927..e73f4bfc 100644 --- a/src/shared/constant/payment-method.ts +++ b/src/shared/constant/payment-method.ts @@ -1,7 +1,7 @@ export const PAYMENT_METHOD = { CARD: '카드', - BANK_TRANSFER: '계좌 이체', - ANY: '아무거나', + BANK_TRANSFER: '계좌이체', + ANY: '무관', } as const; export type PaymentMethodKey = keyof typeof PAYMENT_METHOD; diff --git a/src/shared/utils/date/date-formatter.ts b/src/shared/utils/date/date-formatter.ts index 163f858b..4bb8351f 100644 --- a/src/shared/utils/date/date-formatter.ts +++ b/src/shared/utils/date/date-formatter.ts @@ -66,11 +66,11 @@ export const formatSelectedDateToSchedules = ( }; /** - * 기존 견적서의 날짜를 AvailableDates 형식으로 포맷하는 함수 + * string[] 형식의 날짜를 AvailableDates 형식으로 포맷하는 함수 * ["2025.09.20 ~ 2025.09.20", "2025.09.25 ~ 2025.09.25"] * -> AvailableDate[] */ -export const formatEstimateDatesToAvailableDates = ( +export const formatStringDatesToAvailableDates = ( estimateDates: string[] ): AvailableDate[] => { return estimateDates From 16981134d3f0ea717b832d3f814fdfab810f9ca5 Mon Sep 17 00:00:00 2001 From: holdn2 Date: Tue, 23 Dec 2025 15:47:00 +0900 Subject: [PATCH 02/19] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20api=EB=A5=BC=20=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=97=AC=20=EC=9E=88=EB=8B=A4=EB=A9=B4=20?= =?UTF-8?q?=ED=8F=BC=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EC=9D=98=20menu?= =?UTF-8?q?=EA=B0=80=20true=20=EA=B0=80=20=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@section/menu-section/MenuInfo.tsx | 5 +---- src/pages/@owner/food-truck-form/FoodTruckForm.tsx | 12 ++---------- .../food-truck-form/hooks/use-food-truck-form.ts | 10 +++++++--- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/pages/@owner/food-truck-form/@section/menu-section/MenuInfo.tsx b/src/pages/@owner/food-truck-form/@section/menu-section/MenuInfo.tsx index 0ce7b5f1..30d5ca6a 100644 --- a/src/pages/@owner/food-truck-form/@section/menu-section/MenuInfo.tsx +++ b/src/pages/@owner/food-truck-form/@section/menu-section/MenuInfo.tsx @@ -4,7 +4,6 @@ import { ROUTES } from '@router/constant/routes'; import FormLayout from '@components/layout/form-layout/FormLayout'; import PageSwitchButton from '@pages/@owner/food-truck-form/components/PageSwitchButton'; import { FOOD_TRUCK_ERROR_MESSAGE } from '@pages/@owner/food-truck-form/constants/food-truck'; -import { getNavigateState } from '@pages/@owner/food-truck-form/utils/navigate-state'; import type { FoodTruckFormData } from '@pages/@owner/food-truck-form/schemas/food-truck-form.schema'; export default function MenuInfo() { @@ -15,9 +14,7 @@ export default function MenuInfo() { const formData = watch(); const handleClick = () => { if (foodTruckId) { - navigate(ROUTES.MENU_LIST(foodTruckId), { - state: getNavigateState(formData), - }); + navigate(ROUTES.MENU_LIST(foodTruckId)); } }; diff --git a/src/pages/@owner/food-truck-form/FoodTruckForm.tsx b/src/pages/@owner/food-truck-form/FoodTruckForm.tsx index 001b5024..080a6129 100644 --- a/src/pages/@owner/food-truck-form/FoodTruckForm.tsx +++ b/src/pages/@owner/food-truck-form/FoodTruckForm.tsx @@ -1,5 +1,4 @@ -import { useEffect } from 'react'; -import { useNavigate, useParams, useLocation } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { FormProvider } from 'react-hook-form'; import Navigation from '@layout/navigation/Navigation'; import { Icon } from '@icon/Icon'; @@ -39,10 +38,9 @@ export default function FoodTruckForm() { const foodTruckIdNumber = Number(foodTruckId); const navigate = useNavigate(); - const location = useLocation(); const toast = useToast(); - const { isEdit, methods, reset, isFormValid, handleSubmit } = + const { isEdit, methods, isFormValid, handleSubmit } = useFoodTruckForm(foodTruckIdNumber); const { @@ -60,12 +58,6 @@ export default function FoodTruckForm() { handleActiveDateError, } = useFoodTruckFormDate(methods); - useEffect(() => { - if (location.state?.formData && location.state?.from) { - reset(location.state.formData); - } - }, [location.state, reset]); - if (!foodTruckId || isNaN(foodTruckIdNumber)) { toast.error('잘못된 접근입니다.'); navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); diff --git a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts index afbca31e..fea9f8b1 100644 --- a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts +++ b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts @@ -9,6 +9,7 @@ import { type FoodTruckFormData, } from '@pages/@owner/food-truck-form/schemas/food-truck-form.schema'; import useFoodTruckDetail from '@pages/food-truck-detail/hooks/use-food-truck-detail'; +import { useMenusQuery } from '@pages/@owner/menu/hooks/use-menus-query'; const initialData = { name: '', @@ -43,11 +44,14 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { setError, } = methods; + // 기존 데이터가 있다면 수정 mode const [isEdit, setIsEdit] = useState(false); + // 기존 등록 푸드트럭 데이터 조회 const { foodTruckDetailData } = useFoodTruckDetail(foodTruckIdNumber); - // TODO: 메뉴 리스트 조회 필요. 있다면 활성화, 없다면 비활성화 + // 메뉴 등록 여부를 위한 조회 + const { data: menuData } = useMenusQuery(foodTruckIdNumber, '최신순'); useEffect(() => { if (foodTruckDetailData) { @@ -71,10 +75,10 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { activeTime: foodTruckDetailData.activeTime, timeDiscussRequired: foodTruckDetailData.timeDiscussRequired, // TODO : 메뉴 리스트 있을 때 true - menus: true, + menus: menuData !== undefined, }); } - }, [foodTruckDetailData, reset]); + }, [foodTruckDetailData, menuData, reset]); const onSubmit = async (formData: FoodTruckFormData) => { if (!formData.nameDuplicate) { From 35eb0e15672d3aa44a30abe66592b7a038e1d95e Mon Sep 17 00:00:00 2001 From: holdn2 Date: Tue, 23 Dec 2025 16:05:23 +0900 Subject: [PATCH 03/19] =?UTF-8?q?refactor:=20=EB=8B=A4=EB=A5=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=B4=EB=8F=99=20=EC=8B=9C=20?= =?UTF-8?q?getNavigateState=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=8B=A4=EC=8B=9C=20=EB=A1=A4=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@section/menu-section/MenuInfo.tsx | 5 ++++- src/pages/@owner/food-truck-form/FoodTruckForm.tsx | 14 +++++++++++--- .../food-truck-form/hooks/use-food-truck-form.ts | 3 +-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/pages/@owner/food-truck-form/@section/menu-section/MenuInfo.tsx b/src/pages/@owner/food-truck-form/@section/menu-section/MenuInfo.tsx index 30d5ca6a..0ce7b5f1 100644 --- a/src/pages/@owner/food-truck-form/@section/menu-section/MenuInfo.tsx +++ b/src/pages/@owner/food-truck-form/@section/menu-section/MenuInfo.tsx @@ -4,6 +4,7 @@ import { ROUTES } from '@router/constant/routes'; import FormLayout from '@components/layout/form-layout/FormLayout'; import PageSwitchButton from '@pages/@owner/food-truck-form/components/PageSwitchButton'; import { FOOD_TRUCK_ERROR_MESSAGE } from '@pages/@owner/food-truck-form/constants/food-truck'; +import { getNavigateState } from '@pages/@owner/food-truck-form/utils/navigate-state'; import type { FoodTruckFormData } from '@pages/@owner/food-truck-form/schemas/food-truck-form.schema'; export default function MenuInfo() { @@ -14,7 +15,9 @@ export default function MenuInfo() { const formData = watch(); const handleClick = () => { if (foodTruckId) { - navigate(ROUTES.MENU_LIST(foodTruckId)); + navigate(ROUTES.MENU_LIST(foodTruckId), { + state: getNavigateState(formData), + }); } }; diff --git a/src/pages/@owner/food-truck-form/FoodTruckForm.tsx b/src/pages/@owner/food-truck-form/FoodTruckForm.tsx index 080a6129..157a9b34 100644 --- a/src/pages/@owner/food-truck-form/FoodTruckForm.tsx +++ b/src/pages/@owner/food-truck-form/FoodTruckForm.tsx @@ -1,4 +1,5 @@ -import { useNavigate, useParams } from 'react-router-dom'; +import { useEffect } from 'react'; +import { useNavigate, useParams, useLocation } from 'react-router-dom'; import { FormProvider } from 'react-hook-form'; import Navigation from '@layout/navigation/Navigation'; import { Icon } from '@icon/Icon'; @@ -38,9 +39,10 @@ export default function FoodTruckForm() { const foodTruckIdNumber = Number(foodTruckId); const navigate = useNavigate(); + const location = useLocation(); const toast = useToast(); - const { isEdit, methods, isFormValid, handleSubmit } = + const { isEdit, methods, reset, isFormValid, handleSubmit } = useFoodTruckForm(foodTruckIdNumber); const { @@ -58,6 +60,12 @@ export default function FoodTruckForm() { handleActiveDateError, } = useFoodTruckFormDate(methods); + useEffect(() => { + if (location.state?.formData && location.state?.from) { + reset(location.state.formData); + } + }, [location.state, reset]); + if (!foodTruckId || isNaN(foodTruckIdNumber)) { toast.error('잘못된 접근입니다.'); navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); @@ -86,7 +94,7 @@ export default function FoodTruckForm() { handleActiveTimeSetValue={handleActiveTimeSetValue} handleTimeDiscussRequiredSetValue={handleTimeDiscussRequiredSetValue} /> - + diff --git a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts index fea9f8b1..956779bb 100644 --- a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts +++ b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts @@ -74,7 +74,6 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { ), activeTime: foodTruckDetailData.activeTime, timeDiscussRequired: foodTruckDetailData.timeDiscussRequired, - // TODO : 메뉴 리스트 있을 때 true menus: menuData !== undefined, }); } @@ -88,7 +87,7 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { return; } if (isValid && formData) { - //TODO: 계좌 등록 제출 + // TODO: 푸드트럭 등록 api 호출 alert('푸드트럭 등록 제출'); } }; From 535d2a3219f3a30cf598a1cab9a4672b21f33572 Mon Sep 17 00:00:00 2001 From: holdn2 Date: Sat, 3 Jan 2026 11:16:00 +0900 Subject: [PATCH 04/19] =?UTF-8?q?code=20review:=20zod=20=EC=8A=A4=ED=82=A4?= =?UTF-8?q?=EB=A7=88=EC=97=90=EC=84=9C=20=EC=88=98=EB=9F=89,=20=EC=A0=84?= =?UTF-8?q?=EA=B8=B0,=20=EA=B2=B0=EC=A0=9C=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20string=EC=97=90=EC=84=9C=20enum=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@owner/food-truck-form/FoodTruckForm.tsx | 1 - .../hooks/use-food-truck-form.ts | 23 +++++++++++++++---- .../schemas/food-truck-form.schema.ts | 11 ++++++--- src/shared/utils/normalize-enum-value.ts | 11 +++++++++ 4 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 src/shared/utils/normalize-enum-value.ts diff --git a/src/pages/@owner/food-truck-form/FoodTruckForm.tsx b/src/pages/@owner/food-truck-form/FoodTruckForm.tsx index 157a9b34..e1754370 100644 --- a/src/pages/@owner/food-truck-form/FoodTruckForm.tsx +++ b/src/pages/@owner/food-truck-form/FoodTruckForm.tsx @@ -69,7 +69,6 @@ export default function FoodTruckForm() { if (!foodTruckId || isNaN(foodTruckIdNumber)) { toast.error('잘못된 접근입니다.'); navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); - return null; } const handleNavigateBack = () => { diff --git a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts index 956779bb..7b0706a2 100644 --- a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts +++ b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts @@ -3,6 +3,11 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { formatStringDatesToAvailableDates } from '@utils/date'; +import { normalizeEnumValue } from '@utils/normalize-enum-value'; +import { AVAILABLE_QUANTITY } from '@constant/available-quantity'; +import { NEED_ELECTRICITY } from '@constant/need-electricity'; +import { PAYMENT_METHOD } from '@constant/payment-method'; + import { FOOD_TRUCK_ERROR_MESSAGE } from '@pages/@owner/food-truck-form/constants/food-truck'; import { foodTruckSchema, @@ -55,6 +60,7 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { useEffect(() => { if (foodTruckDetailData) { + const menus = menuData?.pages.flatMap(page => page?.content || []); setIsEdit(true); reset({ name: foodTruckDetailData.name, @@ -62,9 +68,18 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { description: foodTruckDetailData.description, phoneNumber: foodTruckDetailData.phoneNumber, regionCodes: foodTruckDetailData.regionCodes, - availableQuantity: foodTruckDetailData.availableQuantity, - needElectricity: foodTruckDetailData.needElectricity, - paymentMethod: foodTruckDetailData.paymentMethod, + availableQuantity: normalizeEnumValue( + AVAILABLE_QUANTITY, + foodTruckDetailData.availableQuantity + ), + needElectricity: normalizeEnumValue( + NEED_ELECTRICITY, + foodTruckDetailData.needElectricity + ), + paymentMethod: normalizeEnumValue( + PAYMENT_METHOD, + foodTruckDetailData.paymentMethod + ), menuCategories: foodTruckDetailData.menuCategories, photoUrls: foodTruckDetailData.photoUrl, operatingInfo: foodTruckDetailData.operatingInfo, @@ -74,7 +89,7 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { ), activeTime: foodTruckDetailData.activeTime, timeDiscussRequired: foodTruckDetailData.timeDiscussRequired, - menus: menuData !== undefined, + menus: menus !== undefined && menus.length !== 0, }); } }, [foodTruckDetailData, menuData, reset]); diff --git a/src/pages/@owner/food-truck-form/schemas/food-truck-form.schema.ts b/src/pages/@owner/food-truck-form/schemas/food-truck-form.schema.ts index eb58ceb2..b08c05f4 100644 --- a/src/pages/@owner/food-truck-form/schemas/food-truck-form.schema.ts +++ b/src/pages/@owner/food-truck-form/schemas/food-truck-form.schema.ts @@ -8,6 +8,9 @@ import { import type { AvailableDate } from '@type/available-date'; import { validateFoodTruckFormTime } from '@pages/@owner/food-truck-form/utils/validate-food-truck-form-time'; +import { NEED_ELECTRICITY } from '@constant/need-electricity'; +import { PAYMENT_METHOD } from '@constant/payment-method'; +import { AVAILABLE_QUANTITY } from '@constant/available-quantity'; export const foodTruckSchema = z.object({ name: z @@ -31,9 +34,11 @@ export const foodTruckSchema = z.object({ FOOD_TRUCK_ERROR_MESSAGE.phoneNumber.required ), regionCodes: z.array(z.custom()), - availableQuantity: z.string(), - needElectricity: z.string(), - paymentMethod: z.string(), + availableQuantity: z.enum( + Object.values(AVAILABLE_QUANTITY).map(item => item) + ), + needElectricity: z.enum(Object.values(NEED_ELECTRICITY).map(item => item)), + paymentMethod: z.enum(Object.values(PAYMENT_METHOD).map(item => item)), menuCategories: z.array(z.string()), photoUrls: z.array(z.string()).min(1, { message: FOOD_TRUCK_ERROR_MESSAGE.photoUrls.required, diff --git a/src/shared/utils/normalize-enum-value.ts b/src/shared/utils/normalize-enum-value.ts new file mode 100644 index 00000000..3d2e3087 --- /dev/null +++ b/src/shared/utils/normalize-enum-value.ts @@ -0,0 +1,11 @@ +export const normalizeEnumValue = >( + enumObj: T, + value?: string +): T[keyof T] | undefined => { + if (!value) return undefined; + + const values = Object.values(enumObj) as Array; + return values.includes(value as T[keyof T]) + ? (value as T[keyof T]) + : undefined; +}; From 93eeb76c44d510ddb59233176c1bc9d4b7e21e17 Mon Sep 17 00:00:00 2001 From: holdn2 Date: Sat, 3 Jan 2026 11:32:23 +0900 Subject: [PATCH 05/19] =?UTF-8?q?code=20review:=20normalizeEnumValue?= =?UTF-8?q?=EC=9D=98=20undefined=20=EB=B0=98=ED=99=98=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hooks/use-food-truck-form.ts | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts index 7b0706a2..9236419c 100644 --- a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts +++ b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; +import useToast from '@hooks/use-toast'; import { formatStringDatesToAvailableDates } from '@utils/date'; import { normalizeEnumValue } from '@utils/normalize-enum-value'; import { AVAILABLE_QUANTITY } from '@constant/available-quantity'; @@ -36,6 +37,7 @@ const initialData = { }; export const useFoodTruckForm = (foodTruckIdNumber: number) => { + const toast = useToast(); const methods = useForm({ resolver: zodResolver(foodTruckSchema), defaultValues: initialData, @@ -61,6 +63,22 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { useEffect(() => { if (foodTruckDetailData) { const menus = menuData?.pages.flatMap(page => page?.content || []); + const availableQuantity = normalizeEnumValue( + AVAILABLE_QUANTITY, + foodTruckDetailData.availableQuantity + ); + const needElectricity = normalizeEnumValue( + NEED_ELECTRICITY, + foodTruckDetailData.needElectricity + ); + const payment = normalizeEnumValue( + PAYMENT_METHOD, + foodTruckDetailData.paymentMethod + ); + if (!availableQuantity || !needElectricity || !payment) { + toast.error('잘못된 정보입니다. 다시 시도해주세요.'); + return; + } setIsEdit(true); reset({ name: foodTruckDetailData.name, @@ -68,18 +86,9 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { description: foodTruckDetailData.description, phoneNumber: foodTruckDetailData.phoneNumber, regionCodes: foodTruckDetailData.regionCodes, - availableQuantity: normalizeEnumValue( - AVAILABLE_QUANTITY, - foodTruckDetailData.availableQuantity - ), - needElectricity: normalizeEnumValue( - NEED_ELECTRICITY, - foodTruckDetailData.needElectricity - ), - paymentMethod: normalizeEnumValue( - PAYMENT_METHOD, - foodTruckDetailData.paymentMethod - ), + availableQuantity: availableQuantity, + needElectricity: needElectricity, + paymentMethod: payment, menuCategories: foodTruckDetailData.menuCategories, photoUrls: foodTruckDetailData.photoUrl, operatingInfo: foodTruckDetailData.operatingInfo, From 017fe67c6d7c8567f1acf096a450859d0b784ea7 Mon Sep 17 00:00:00 2001 From: holdn2 Date: Sat, 3 Jan 2026 21:58:34 +0900 Subject: [PATCH 06/19] =?UTF-8?q?refactor:=20=ED=99=9C=EB=8F=99=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=EC=A7=80=EC=97=AD=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=ED=95=B4=EA=B2=B0=20/=20=EA=B0=80=EB=8F=85=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9C=A0=EC=A7=80=EB=B3=B4=EC=88=98=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20resetFoodTruckFormValue=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20/=20=EC=B5=9C=EC=A0=81=ED=99=94=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=B4=20useToast=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TODO: 푸드트럭 사진 관련해서도 지역처럼 리팩토링 필요 --- .../@section/region-section/RegionSection.tsx | 7 +- .../hooks/use-food-truck-form.ts | 69 +++++-------------- .../utils/reset-food-truck-form-value.ts | 57 +++++++++++++++ src/pages/@owner/set-region/SetRegion.tsx | 21 +++--- .../@owner/set-region/hooks/use-region.ts | 51 +++++++------- .../UploadFoodTruckImages.tsx | 8 +-- .../components/region/hooks/use-region.ts | 19 +++-- src/shared/hooks/use-toast.ts | 33 ++++++--- 8 files changed, 160 insertions(+), 105 deletions(-) create mode 100644 src/pages/@owner/food-truck-form/utils/reset-food-truck-form-value.ts diff --git a/src/pages/@owner/food-truck-form/@section/region-section/RegionSection.tsx b/src/pages/@owner/food-truck-form/@section/region-section/RegionSection.tsx index 602475af..1bed2f9c 100644 --- a/src/pages/@owner/food-truck-form/@section/region-section/RegionSection.tsx +++ b/src/pages/@owner/food-truck-form/@section/region-section/RegionSection.tsx @@ -1,10 +1,10 @@ import { useNavigate } from 'react-router-dom'; import { useFormContext } from 'react-hook-form'; + import FormLayout from '@components/layout/form-layout/FormLayout'; import { ROUTES } from '@router/constant/routes'; import type { FoodTruckFormData } from '@pages/@owner/food-truck-form/schemas/food-truck-form.schema'; import { getNavigateState } from '@pages/@owner/food-truck-form/utils/navigate-state'; -import { useRegion } from '@pages/@owner/set-region/hooks/use-region'; import RegionButton from '@pages/@owner/food-truck-form/components/RegionButton'; import useToast from '@hooks/use-toast'; @@ -25,11 +25,14 @@ export default function RegionSection({ foodTruckId }: RegionSectionProps) { return; } + console.info('바깥쪽, ', formData); + navigate(ROUTES.SET_REGION_FORM(foodTruckId), { state: getNavigateState(formData), }); }; - const { regionCodes } = useRegion(foodTruckId); + + const regionCodes = formData.regionCodes ?? []; return ( diff --git a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts index 9236419c..ed83abf4 100644 --- a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts +++ b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts @@ -1,21 +1,17 @@ -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import useToast from '@hooks/use-toast'; -import { formatStringDatesToAvailableDates } from '@utils/date'; -import { normalizeEnumValue } from '@utils/normalize-enum-value'; -import { AVAILABLE_QUANTITY } from '@constant/available-quantity'; -import { NEED_ELECTRICITY } from '@constant/need-electricity'; -import { PAYMENT_METHOD } from '@constant/payment-method'; -import { FOOD_TRUCK_ERROR_MESSAGE } from '@pages/@owner/food-truck-form/constants/food-truck'; +import useFoodTruckDetail from '@pages/food-truck-detail/hooks/use-food-truck-detail'; +import { useMenusQuery } from '@pages/@owner/menu/hooks/use-menus-query'; import { foodTruckSchema, type FoodTruckFormData, } from '@pages/@owner/food-truck-form/schemas/food-truck-form.schema'; -import useFoodTruckDetail from '@pages/food-truck-detail/hooks/use-food-truck-detail'; -import { useMenusQuery } from '@pages/@owner/menu/hooks/use-menus-query'; +import { resetFoodTruckFormValue } from '@pages/@owner/food-truck-form/utils/reset-food-truck-form-value'; +import { FOOD_TRUCK_ERROR_MESSAGE } from '@pages/@owner/food-truck-form/constants/food-truck'; const initialData = { name: '', @@ -51,57 +47,26 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { setError, } = methods; - // 기존 데이터가 있다면 수정 mode - const [isEdit, setIsEdit] = useState(false); - // 기존 등록 푸드트럭 데이터 조회 const { foodTruckDetailData } = useFoodTruckDetail(foodTruckIdNumber); // 메뉴 등록 여부를 위한 조회 const { data: menuData } = useMenusQuery(foodTruckIdNumber, '최신순'); + const isEdit = !!foodTruckDetailData; + useEffect(() => { - if (foodTruckDetailData) { - const menus = menuData?.pages.flatMap(page => page?.content || []); - const availableQuantity = normalizeEnumValue( - AVAILABLE_QUANTITY, - foodTruckDetailData.availableQuantity - ); - const needElectricity = normalizeEnumValue( - NEED_ELECTRICITY, - foodTruckDetailData.needElectricity - ); - const payment = normalizeEnumValue( - PAYMENT_METHOD, - foodTruckDetailData.paymentMethod - ); - if (!availableQuantity || !needElectricity || !payment) { - toast.error('잘못된 정보입니다. 다시 시도해주세요.'); - return; - } - setIsEdit(true); - reset({ - name: foodTruckDetailData.name, - nameDuplicate: true, - description: foodTruckDetailData.description, - phoneNumber: foodTruckDetailData.phoneNumber, - regionCodes: foodTruckDetailData.regionCodes, - availableQuantity: availableQuantity, - needElectricity: needElectricity, - paymentMethod: payment, - menuCategories: foodTruckDetailData.menuCategories, - photoUrls: foodTruckDetailData.photoUrl, - operatingInfo: foodTruckDetailData.operatingInfo, - option: foodTruckDetailData.option, - availableDates: formatStringDatesToAvailableDates( - foodTruckDetailData.availableDates ?? [] - ), - activeTime: foodTruckDetailData.activeTime, - timeDiscussRequired: foodTruckDetailData.timeDiscussRequired, - menus: menus !== undefined && menus.length !== 0, - }); + if (!foodTruckDetailData) return; + + const menus = menuData?.pages.flatMap(page => page?.content || []); + const result = resetFoodTruckFormValue(foodTruckDetailData, menus); + + if (result.isError) { + toast.error('잘못된 정보입니다. 다시 시도해주세요.'); + return; } - }, [foodTruckDetailData, menuData, reset]); + reset(result.values); + }, [foodTruckDetailData, menuData, reset, toast]); const onSubmit = async (formData: FoodTruckFormData) => { if (!formData.nameDuplicate) { diff --git a/src/pages/@owner/food-truck-form/utils/reset-food-truck-form-value.ts b/src/pages/@owner/food-truck-form/utils/reset-food-truck-form-value.ts new file mode 100644 index 00000000..a7d2a0c8 --- /dev/null +++ b/src/pages/@owner/food-truck-form/utils/reset-food-truck-form-value.ts @@ -0,0 +1,57 @@ +import type { + FoodTruckDetailResponse, + MyFoodTruckMenuResponse, +} from 'apis/data-contracts'; + +import { normalizeEnumValue } from '@utils/normalize-enum-value'; +import { AVAILABLE_QUANTITY } from '@constant/available-quantity'; +import { NEED_ELECTRICITY } from '@constant/need-electricity'; +import { PAYMENT_METHOD } from '@constant/payment-method'; +import { formatStringDatesToAvailableDates } from '@utils/date'; + +export const resetFoodTruckFormValue = ( + foodTruckDetailData: FoodTruckDetailResponse, + menus?: MyFoodTruckMenuResponse[] +) => { + const availableQuantity = normalizeEnumValue( + AVAILABLE_QUANTITY, + foodTruckDetailData.availableQuantity + ); + const needElectricity = normalizeEnumValue( + NEED_ELECTRICITY, + foodTruckDetailData.needElectricity + ); + const payment = normalizeEnumValue( + PAYMENT_METHOD, + foodTruckDetailData.paymentMethod + ); + if (!availableQuantity || !needElectricity || !payment) { + return { + isError: true, + }; + } + + return { + isError: false, + values: { + name: foodTruckDetailData.name, + nameDuplicate: true, + description: foodTruckDetailData.description, + phoneNumber: foodTruckDetailData.phoneNumber, + regionCodes: foodTruckDetailData.regionCodes, + availableQuantity: availableQuantity, + needElectricity: needElectricity, + paymentMethod: payment, + menuCategories: foodTruckDetailData.menuCategories, + photoUrls: foodTruckDetailData.photoUrl, + operatingInfo: foodTruckDetailData.operatingInfo, + option: foodTruckDetailData.option, + availableDates: formatStringDatesToAvailableDates( + foodTruckDetailData.availableDates ?? [] + ), + activeTime: foodTruckDetailData.activeTime, + timeDiscussRequired: foodTruckDetailData.timeDiscussRequired, + menus: menus && menus.length > 0, + }, + }; +}; diff --git a/src/pages/@owner/set-region/SetRegion.tsx b/src/pages/@owner/set-region/SetRegion.tsx index 157ffa8c..97955182 100644 --- a/src/pages/@owner/set-region/SetRegion.tsx +++ b/src/pages/@owner/set-region/SetRegion.tsx @@ -1,5 +1,5 @@ import { useNavigate, useLocation, useParams } from 'react-router-dom'; -import { FormProvider, useFormContext } from 'react-hook-form'; +import { FormProvider } from 'react-hook-form'; import { Icon } from '@icon/Icon'; import Navigation from '@layout/navigation/Navigation'; @@ -7,12 +7,11 @@ import Region from '@shared/components/region/Region'; import { ROUTES } from '@router/constant/routes'; import { useRegion } from '@pages/@owner/set-region/hooks/use-region'; import { useFoodTruckForm } from '@pages/@owner/food-truck-form/hooks/use-food-truck-form'; -import type { FoodTruckFormData } from '@pages/@owner/food-truck-form/schemas/food-truck-form.schema'; export default function SetRegion() { - const location = useLocation(); - const formData = location.state?.formData; - const methods = useFoodTruckForm(formData); + const { foodTruckId } = useParams(); + const foodTruckIdNumber = Number(foodTruckId); + const methods = useFoodTruckForm(foodTruckIdNumber); return ( @@ -25,7 +24,7 @@ function SetRegionContent() { const { foodTruckId } = useParams(); const navigate = useNavigate(); const location = useLocation(); - const { getValues } = useFormContext(); + const formData = location.state.formData; const handleLeftClick = () => { if (!foodTruckId) { @@ -36,13 +35,15 @@ function SetRegionContent() { navigate(ROUTES.FOOD_TRUCK_FORM(foodTruckId), { state: { from: fromPage || 'food-truck-form', - formData: getValues(), + formData: formData, }, }); }; - const { regionCodes, handleSubmitRegion, handleResetRegionFoodTruck } = - useRegion(foodTruckId); + const { handleSubmitRegion, handleResetRegionFoodTruck } = useRegion( + formData, + foodTruckId + ); return ( <> @@ -52,7 +53,7 @@ function SetRegionContent() { handleLeftClick={handleLeftClick} /> diff --git a/src/pages/@owner/set-region/hooks/use-region.ts b/src/pages/@owner/set-region/hooks/use-region.ts index a1ced8bf..29f9d87c 100644 --- a/src/pages/@owner/set-region/hooks/use-region.ts +++ b/src/pages/@owner/set-region/hooks/use-region.ts @@ -1,51 +1,54 @@ -import { useFormContext } from 'react-hook-form'; import type { FoodTruckFormData } from '@pages/@owner/food-truck-form/schemas/food-truck-form.schema'; import type { RegionResponse } from 'apis/data-contracts'; import { ROUTES } from '@router/constant/routes'; import { useNavigate } from 'react-router-dom'; -import { getNavigateState } from '@pages/@owner/food-truck-form/utils/navigate-state'; + import useToast from '@hooks/use-toast'; -export const useRegion = (foodTruckId?: string) => { +export const useRegion = ( + formData: FoodTruckFormData, + foodTruckId?: string +) => { const navigate = useNavigate(); const toast = useToast(); - const { - setValue, - watch, - getValues, - formState: { errors }, - } = useFormContext(); - - const formData = watch(); - - const updateRegionCodes = (regions: RegionResponse[]) => { - setValue('regionCodes', regions, { shouldValidate: true }); - }; const handleSubmitRegion = (regions: RegionResponse[]) => { - updateRegionCodes(regions); - // setValue 후 최신 값을 가져오기 위해 getValues() 사용 - const updatedFormData = getValues(); if (!foodTruckId) { toast.error('잘못된 접근입니다.'); navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); return; } navigate(ROUTES.FOOD_TRUCK_FORM(foodTruckId), { - state: getNavigateState(updatedFormData), + state: { + from: 'set-region', + formData: { + ...formData, + regionCodes: regions, + }, + }, }); }; const handleResetRegionFoodTruck = () => { - updateRegionCodes([]); + if (!foodTruckId) { + toast.error('잘못된 접근입니다.'); + navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); + return; + } + navigate(ROUTES.FOOD_TRUCK_FORM(foodTruckId), { + state: { + from: 'set-region', + formData: { + ...formData, + regionCodes: [], + }, + }, + }); }; return { - regionCodes: formData.regionCodes ?? [], - - regionCodesError: errors.regionCodes?.message, + regionCodes: formData?.regionCodes ?? [], - updateRegionCodes, handleSubmitRegion, handleResetRegionFoodTruck, }; diff --git a/src/pages/@owner/upload-food-truck-images/UploadFoodTruckImages.tsx b/src/pages/@owner/upload-food-truck-images/UploadFoodTruckImages.tsx index 62e78560..31767b8f 100644 --- a/src/pages/@owner/upload-food-truck-images/UploadFoodTruckImages.tsx +++ b/src/pages/@owner/upload-food-truck-images/UploadFoodTruckImages.tsx @@ -1,4 +1,4 @@ -import { useLocation } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { FormProvider } from 'react-hook-form'; import Navigation from '@components/layout/navigation/Navigation'; import { Icon } from '@components/icon/Icon'; @@ -13,9 +13,9 @@ import { } from '@pages/@owner/upload-food-truck-images/components'; export default function UploadFoodTruckImages() { - const location = useLocation(); - const formData = location.state?.formData; - const methods = useFoodTruckForm(formData); + const { foodTruckId } = useParams(); + const foodTruckIdNumber = Number(foodTruckId); + const methods = useFoodTruckForm(foodTruckIdNumber); return ( diff --git a/src/shared/components/region/hooks/use-region.ts b/src/shared/components/region/hooks/use-region.ts index df7678d8..ecf6d48a 100644 --- a/src/shared/components/region/hooks/use-region.ts +++ b/src/shared/components/region/hooks/use-region.ts @@ -19,11 +19,20 @@ export default function useRegion({ const toast = useToast(); const handleSelectRegion = (region: RegionResponse) => { - if (selectedRegions.length >= MAX_SELECTED) { - toast.error('최대 선택 개수를 초과했습니다.'); - return; - } - setSelectedRegions(prev => [...prev, region]); + setSelectedRegions(prev => { + const alreadySelected = prev.some(r => r.code === region.code); + + if (alreadySelected) { + return prev.filter(r => r.code !== region.code); + } + + if (prev.length >= MAX_SELECTED) { + toast.error('최대 선택 개수를 초과했습니다.'); + return prev; + } + + return [...prev, region]; + }); }; const handleDeleteRegion = (region: RegionResponse) => { setSelectedRegions(prev => prev.filter(r => r.code !== region.code)); diff --git a/src/shared/hooks/use-toast.ts b/src/shared/hooks/use-toast.ts index 9a78b403..48b4f71b 100644 --- a/src/shared/hooks/use-toast.ts +++ b/src/shared/hooks/use-toast.ts @@ -1,18 +1,35 @@ import { useSetAtom } from 'jotai'; import { ToastAtom } from '@utils/toast'; import { TOAST_TYPE } from '@constant/toast'; +import { useCallback, useMemo } from 'react'; const useToast = () => { const addToast = useSetAtom(ToastAtom); - return { - success: (message: string) => - addToast({ type: TOAST_TYPE.SUCCESS, message }), - error: (message: string) => addToast({ type: TOAST_TYPE.ERROR, message }), - warning: (message: string) => - addToast({ type: TOAST_TYPE.WARNING, message }), - info: (message: string) => addToast({ type: TOAST_TYPE.INFO, message }), - }; + const success = useCallback( + (message: string) => addToast({ type: TOAST_TYPE.SUCCESS, message }), + [addToast] + ); + + const error = useCallback( + (message: string) => addToast({ type: TOAST_TYPE.ERROR, message }), + [addToast] + ); + + const warning = useCallback( + (message: string) => addToast({ type: TOAST_TYPE.WARNING, message }), + [addToast] + ); + + const info = useCallback( + (message: string) => addToast({ type: TOAST_TYPE.INFO, message }), + [addToast] + ); + + return useMemo( + () => ({ success, error, warning, info }), + [success, error, warning, info] + ); }; export default useToast; From ac68c43d0408da77662092dcde1fe0cf60bb5abb Mon Sep 17 00:00:00 2001 From: holdn2 Date: Sun, 4 Jan 2026 00:05:31 +0900 Subject: [PATCH 07/19] =?UTF-8?q?code=20review:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@section/region-section/RegionSection.tsx | 2 -- src/pages/@owner/set-region/SetRegion.tsx | 16 +++++++++++- .../UploadFoodTruckImages.tsx | 13 ++++++++-- .../components/region/hooks/use-region.ts | 25 ++++++++----------- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/pages/@owner/food-truck-form/@section/region-section/RegionSection.tsx b/src/pages/@owner/food-truck-form/@section/region-section/RegionSection.tsx index 1bed2f9c..fbc2102b 100644 --- a/src/pages/@owner/food-truck-form/@section/region-section/RegionSection.tsx +++ b/src/pages/@owner/food-truck-form/@section/region-section/RegionSection.tsx @@ -25,8 +25,6 @@ export default function RegionSection({ foodTruckId }: RegionSectionProps) { return; } - console.info('바깥쪽, ', formData); - navigate(ROUTES.SET_REGION_FORM(foodTruckId), { state: getNavigateState(formData), }); diff --git a/src/pages/@owner/set-region/SetRegion.tsx b/src/pages/@owner/set-region/SetRegion.tsx index 97955182..79626350 100644 --- a/src/pages/@owner/set-region/SetRegion.tsx +++ b/src/pages/@owner/set-region/SetRegion.tsx @@ -1,18 +1,27 @@ import { useNavigate, useLocation, useParams } from 'react-router-dom'; import { FormProvider } from 'react-hook-form'; +import useToast from '@hooks/use-toast'; import { Icon } from '@icon/Icon'; import Navigation from '@layout/navigation/Navigation'; -import Region from '@shared/components/region/Region'; +import Region from '@components/region/Region'; import { ROUTES } from '@router/constant/routes'; import { useRegion } from '@pages/@owner/set-region/hooks/use-region'; import { useFoodTruckForm } from '@pages/@owner/food-truck-form/hooks/use-food-truck-form'; export default function SetRegion() { + const navigate = useNavigate(); + const toast = useToast(); + const { foodTruckId } = useParams(); const foodTruckIdNumber = Number(foodTruckId); const methods = useFoodTruckForm(foodTruckIdNumber); + if (!foodTruckId || isNaN(foodTruckIdNumber)) { + toast.error('잘못된 접근입니다.'); + navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); + } + return ( @@ -45,6 +54,11 @@ function SetRegionContent() { foodTruckId ); + if (!formData) { + navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); + return null; + } + return ( <> diff --git a/src/shared/components/region/hooks/use-region.ts b/src/shared/components/region/hooks/use-region.ts index ecf6d48a..0d40fc51 100644 --- a/src/shared/components/region/hooks/use-region.ts +++ b/src/shared/components/region/hooks/use-region.ts @@ -19,21 +19,18 @@ export default function useRegion({ const toast = useToast(); const handleSelectRegion = (region: RegionResponse) => { - setSelectedRegions(prev => { - const alreadySelected = prev.some(r => r.code === region.code); - - if (alreadySelected) { - return prev.filter(r => r.code !== region.code); - } - - if (prev.length >= MAX_SELECTED) { - toast.error('최대 선택 개수를 초과했습니다.'); - return prev; - } - - return [...prev, region]; - }); + const alreadySelected = selectedRegions.some(r => r.code === region.code); + if (alreadySelected) { + setSelectedRegions(prev => prev.filter(r => r.code !== region.code)); + return; + } + if (selectedRegions.length >= MAX_SELECTED) { + toast.error('최대 선택 개수를 초과했습니다.'); + return; + } + setSelectedRegions(prev => [...prev, region]); }; + const handleDeleteRegion = (region: RegionResponse) => { setSelectedRegions(prev => prev.filter(r => r.code !== region.code)); }; From 102fed42537c0b0edfda3844df82f54ee859b6cc Mon Sep 17 00:00:00 2001 From: holdn2 Date: Sun, 4 Jan 2026 01:08:35 +0900 Subject: [PATCH 08/19] =?UTF-8?q?refactor:=20=ED=91=B8=EB=93=9C=ED=8A=B8?= =?UTF-8?q?=EB=9F=AD=20=EC=82=AC=EC=A7=84=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hooks/use-food-truck-image.ts | 8 ++++++++ .../hooks/use-upload-images.ts | 18 ++++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/pages/@owner/upload-food-truck-images/hooks/use-food-truck-image.ts b/src/pages/@owner/upload-food-truck-images/hooks/use-food-truck-image.ts index bec2655b..291e4391 100644 --- a/src/pages/@owner/upload-food-truck-images/hooks/use-food-truck-image.ts +++ b/src/pages/@owner/upload-food-truck-images/hooks/use-food-truck-image.ts @@ -6,6 +6,7 @@ import { } from '@pages/@owner/upload-food-truck-images/api'; import type { FoodTruckImageUrl } from '@pages/@owner/upload-food-truck-images/types/food-truck-image-url'; import { FOOD_TRUCKS_QUERY_KEY } from '@shared/querykey/food-trucks'; +import useToast from '@hooks/use-toast'; export const useFoodTruckImage = () => { return useMutation({ @@ -39,10 +40,17 @@ export const useFoodTruckImage = () => { }; export const useUploadImage = () => { + const toast = useToast(); return useMutation({ mutationFn: async ({ presignedUrl, file }) => { await uploadImage(presignedUrl, file); }, + onSuccess: () => { + toast.success('이미지 추가 완료'); + }, + onError: () => { + toast.error('이미지 업로드 오류'); + }, }); }; diff --git a/src/pages/@owner/upload-food-truck-images/hooks/use-upload-images.ts b/src/pages/@owner/upload-food-truck-images/hooks/use-upload-images.ts index 7fcbfc76..86140a64 100644 --- a/src/pages/@owner/upload-food-truck-images/hooks/use-upload-images.ts +++ b/src/pages/@owner/upload-food-truck-images/hooks/use-upload-images.ts @@ -1,5 +1,4 @@ import { useEffect, useState, type ChangeEvent } from 'react'; -import { useFormContext } from 'react-hook-form'; import { useNavigate, useLocation, useParams } from 'react-router-dom'; import { ROUTES } from '@router/constant/routes'; import { arrayMove } from '@dnd-kit/sortable'; @@ -11,15 +10,13 @@ import { } from '@pages/@owner/upload-food-truck-images/hooks/use-food-truck-image'; import type { DisplayImage } from '@pages/@owner/upload-food-truck-images/types/food-truck-image-display'; -import type { FoodTruckFormData } from '@pages/@owner/food-truck-form/schemas/food-truck-form.schema'; import { imageFileSchema } from '@pages/@owner/upload-food-truck-images/schemas/upload-food-truck-images.schema'; export const useUploadImages = () => { const navigate = useNavigate(); const location = useLocation(); const { foodTruckId } = useParams<{ foodTruckId: string }>(); - - const { setValue, getValues } = useFormContext(); + const formData = location.state.formData; const [initialImageUrls, setInitialImageUrls] = useState([]); const [images, setImages] = useState([]); @@ -31,7 +28,7 @@ export const useUploadImages = () => { const { mutateAsync: deleteFromS3 } = useDeleteImage(); useEffect(() => { - const existingUrls: string[] = getValues('photoUrls') || []; + const existingUrls: string[] = formData.photoUrls || []; setInitialImageUrls(existingUrls); const displayImages = existingUrls.map(url => ({ @@ -40,7 +37,7 @@ export const useUploadImages = () => { url: url, })); setImages(displayImages); - }, [getValues]); + }, [formData]); useEffect(() => { const previews = images.map(image => { @@ -143,12 +140,13 @@ export const useUploadImages = () => { }) .filter((url): url is string => !!url); - setValue('photoUrls', finalOrderedUrls, { shouldValidate: true }); - navigate(ROUTES.FOOD_TRUCK_FORM(foodTruckId), { state: { - formData: getValues(), from: 'upload-food-truck-images', + formData: { + ...formData, + photoUrls: finalOrderedUrls, + }, }, }); } catch (error) { @@ -161,7 +159,7 @@ export const useUploadImages = () => { const handleLeftClick = () => { if (!foodTruckId) return; navigate(ROUTES.FOOD_TRUCK_FORM(foodTruckId), { - state: { formData: getValues(), from: location.pathname }, + state: { from: location.pathname, formData: formData }, }); }; From a7613d7b3b700b8492a4846e83c729c643665245 Mon Sep 17 00:00:00 2001 From: holdn2 Date: Sun, 4 Jan 2026 01:50:26 +0900 Subject: [PATCH 09/19] =?UTF-8?q?code=20review:=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81.=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EB=B0=A9=EC=A7=80=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../upload-food-truck-images/UploadFoodTruckImages.tsx | 1 + .../upload-food-truck-images/hooks/use-upload-images.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/pages/@owner/upload-food-truck-images/UploadFoodTruckImages.tsx b/src/pages/@owner/upload-food-truck-images/UploadFoodTruckImages.tsx index 5880dc6e..82dd1104 100644 --- a/src/pages/@owner/upload-food-truck-images/UploadFoodTruckImages.tsx +++ b/src/pages/@owner/upload-food-truck-images/UploadFoodTruckImages.tsx @@ -24,6 +24,7 @@ export default function UploadFoodTruckImages() { if (!foodTruckId || isNaN(foodTruckIdNumber)) { toast.error('잘못된 접근입니다.'); navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); + return; } return ( diff --git a/src/pages/@owner/upload-food-truck-images/hooks/use-upload-images.ts b/src/pages/@owner/upload-food-truck-images/hooks/use-upload-images.ts index 86140a64..198305e1 100644 --- a/src/pages/@owner/upload-food-truck-images/hooks/use-upload-images.ts +++ b/src/pages/@owner/upload-food-truck-images/hooks/use-upload-images.ts @@ -1,8 +1,9 @@ import { useEffect, useState, type ChangeEvent } from 'react'; import { useNavigate, useLocation, useParams } from 'react-router-dom'; -import { ROUTES } from '@router/constant/routes'; import { arrayMove } from '@dnd-kit/sortable'; +import useToast from '@hooks/use-toast'; +import { ROUTES } from '@router/constant/routes'; import { useFoodTruckImage, useUploadImage, @@ -13,6 +14,7 @@ import type { DisplayImage } from '@pages/@owner/upload-food-truck-images/types/ import { imageFileSchema } from '@pages/@owner/upload-food-truck-images/schemas/upload-food-truck-images.schema'; export const useUploadImages = () => { + const toast = useToast(); const navigate = useNavigate(); const location = useLocation(); const { foodTruckId } = useParams<{ foodTruckId: string }>(); @@ -27,6 +29,11 @@ export const useUploadImages = () => { const { mutateAsync: uploadToS3 } = useUploadImage(); const { mutateAsync: deleteFromS3 } = useDeleteImage(); + if (!formData) { + toast.error('잘못된 접근입니다.'); + navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); + } + useEffect(() => { const existingUrls: string[] = formData.photoUrls || []; setInitialImageUrls(existingUrls); From 9e6d4d0e1f8949e8b1f1d53cd68cfb1bdfa7fa29 Mon Sep 17 00:00:00 2001 From: holdn2 Date: Sun, 4 Jan 2026 11:04:11 +0900 Subject: [PATCH 10/19] =?UTF-8?q?chore:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=AA=85=EB=A0=B9=EC=96=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apis/data-contracts.ts | 1 - apis/http-client.ts | 2 +- package.json | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apis/data-contracts.ts b/apis/data-contracts.ts index feb4d50b..365231db 100644 --- a/apis/data-contracts.ts +++ b/apis/data-contracts.ts @@ -76,7 +76,6 @@ export interface UpdateReservationRequest { /** * 운영 시간 (형식: HH:MM ~ HH:MM) * @minLength 1 - * @pattern ^([01]\d|2[0-3]):([0-5]\d) ~ ([01]\d|2[0-3]):([0-5]\d)$ * @example "15:00 ~ 16:00" */ operationHour: string; diff --git a/apis/http-client.ts b/apis/http-client.ts index 0649301a..8932f811 100644 --- a/apis/http-client.ts +++ b/apis/http-client.ts @@ -74,7 +74,7 @@ export class HttpClient { }: ApiConfig = {}) { this.instance = axios.create({ ...axiosConfig, - baseURL: axiosConfig.baseURL || "http://52.79.221.101:8000", + baseURL: axiosConfig.baseURL || "http://13.125.207.84:8000", }); this.secure = secure; this.format = format; diff --git a/package.json b/package.json index 3b21ca54..6e34c5d3 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", "chromatic": "npx chromatic --project-token=chpt_b45a4ae4e58f49f", - "swagger-typescript-api": "swagger-typescript-api generate -p http://52.79.221.101:8000/v3/api-docs -r -o ./apis --modular -d --extract-request-body --extract-response-body --extract-response-error --axios --clean-output", + "swagger-typescript-api": "swagger-typescript-api generate -p http://13.125.207.84:8000/v3/api-docs -r -o ./apis --modular -d --extract-request-body --extract-response-body --extract-response-error --axios --clean-output", "update-icon-ids": "tsx src/shared/utils/extract-icon-ids.ts" }, "dependencies": { From 75372e467963db67df444ce234b7828f6f27d9d2 Mon Sep 17 00:00:00 2001 From: holdn2 Date: Sun, 4 Jan 2026 11:05:17 +0900 Subject: [PATCH 11/19] =?UTF-8?q?code=20review:=20UploadFoodTruckImages?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=9B=85=20=ED=98=B8=EC=B6=9C=20=EC=A0=84?= =?UTF-8?q?=20foodTruckId=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@owner/upload-food-truck-images/UploadFoodTruckImages.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/@owner/upload-food-truck-images/UploadFoodTruckImages.tsx b/src/pages/@owner/upload-food-truck-images/UploadFoodTruckImages.tsx index 82dd1104..d4d0487c 100644 --- a/src/pages/@owner/upload-food-truck-images/UploadFoodTruckImages.tsx +++ b/src/pages/@owner/upload-food-truck-images/UploadFoodTruckImages.tsx @@ -19,14 +19,14 @@ export default function UploadFoodTruckImages() { const toast = useToast(); const { foodTruckId } = useParams(); const foodTruckIdNumber = Number(foodTruckId); - const methods = useFoodTruckForm(foodTruckIdNumber); if (!foodTruckId || isNaN(foodTruckIdNumber)) { toast.error('잘못된 접근입니다.'); navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); - return; } + const methods = useFoodTruckForm(foodTruckIdNumber); + return ( From 549542b37b04791025a84de5ef96feaa88887dca Mon Sep 17 00:00:00 2001 From: holdn2 Date: Sun, 4 Jan 2026 11:05:47 +0900 Subject: [PATCH 12/19] =?UTF-8?q?feat:=20=EB=82=98=EC=9D=98=20=ED=91=B8?= =?UTF-8?q?=EB=93=9C=ED=8A=B8=EB=9F=AD=20=EB=93=B1=EB=A1=9D=20=EB=B0=8F=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20api=20=ED=95=A8=EC=88=98=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B0=8F=20=EA=B4=80=EB=A0=A8=20=ED=9B=85=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/@owner/food-truck-form/api/index.ts | 17 +++++++++ .../hooks/use-mutation-food-truck-form.ts | 36 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/pages/@owner/food-truck-form/api/index.ts create mode 100644 src/pages/@owner/food-truck-form/hooks/use-mutation-food-truck-form.ts diff --git a/src/pages/@owner/food-truck-form/api/index.ts b/src/pages/@owner/food-truck-form/api/index.ts new file mode 100644 index 00000000..287831f7 --- /dev/null +++ b/src/pages/@owner/food-truck-form/api/index.ts @@ -0,0 +1,17 @@ +import type { + BaseResponseFoodTruckIdResponse, + UpdateFoodTruckInfoRequest, +} from 'apis/data-contracts'; +import { apiRequest } from '@api/apiRequest'; + +export const updateMyFoodTruckInfoApi = async ( + foodTruckId: number, + data: UpdateFoodTruckInfoRequest +) => { + const response = await apiRequest({ + endPoint: `/food-trucks/${foodTruckId}`, + method: 'PUT', + data, + }); + return response.data; +}; diff --git a/src/pages/@owner/food-truck-form/hooks/use-mutation-food-truck-form.ts b/src/pages/@owner/food-truck-form/hooks/use-mutation-food-truck-form.ts new file mode 100644 index 00000000..cf1904b0 --- /dev/null +++ b/src/pages/@owner/food-truck-form/hooks/use-mutation-food-truck-form.ts @@ -0,0 +1,36 @@ +import { useNavigate } from 'react-router-dom'; +import type { UpdateFoodTruckInfoRequest } from 'apis/data-contracts'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import useToast from '@hooks/use-toast'; +import { updateMyFoodTruckInfoApi } from '@pages/@owner/food-truck-form/api'; +import { ROUTES } from '@router/constant/routes'; +import { FOOD_TRUCKS_QUERY_KEY } from '@shared/querykey/food-trucks'; + +export const useMutationFoodTruckForm = () => { + const navigate = useNavigate(); + const toast = useToast(); + const queryClient = useQueryClient(); + + const { mutate: updateFoodTruckInfo } = useMutation({ + mutationFn: ({ + foodTruckId, + data, + }: { + foodTruckId: number; + data: UpdateFoodTruckInfoRequest; + }) => updateMyFoodTruckInfoApi(foodTruckId, data), + onSuccess: () => { + toast.success('푸드트럭 정보가 업데이트 되었습니다.'); + queryClient.invalidateQueries({ queryKey: FOOD_TRUCKS_QUERY_KEY.ALL }); + navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); + }, + onError: error => { + toast.error(`업데이트 실패 : ${error.message}`); + }, + }); + + return { + updateFoodTruckInfo, + }; +}; From 048c5958a627f57a4ceeee2cd0cc7edd9f8456fe Mon Sep 17 00:00:00 2001 From: holdn2 Date: Mon, 5 Jan 2026 16:07:01 +0900 Subject: [PATCH 13/19] =?UTF-8?q?fix:=20=EB=A9=94=EB=89=B4=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EA=B0=94?= =?UTF-8?q?=EB=8B=A4=20=EB=8F=8C=EC=95=84=EC=98=AC=20=EB=95=8C=EB=8F=84=20?= =?UTF-8?q?state=EB=A5=BC=20=EC=A0=81=EC=A0=88=ED=9E=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=B4=EC=84=9C=20=EC=97=90=EB=9F=AC=EA=B0=80=20?= =?UTF-8?q?=EC=83=9D=EA=B8=B0=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/@owner/menu/hooks/use-menu-list.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/pages/@owner/menu/hooks/use-menu-list.ts b/src/pages/@owner/menu/hooks/use-menu-list.ts index 37730adb..023bb28c 100644 --- a/src/pages/@owner/menu/hooks/use-menu-list.ts +++ b/src/pages/@owner/menu/hooks/use-menu-list.ts @@ -15,6 +15,7 @@ export const useMenuList = (foodTruckId: number) => { const navigate = useNavigate(); const location = useLocation(); const toast = useToast(); + const foodTruckFormData = location.state?.formData; const { isSorted, handleSortByLatest, handleSortByOldest } = useMenuSort(); const { isBottomSheetOpen, handleOpenBottomSheet, handleCloseBottomSheet } = @@ -44,20 +45,14 @@ export const useMenuList = (foodTruckId: number) => { // 네비게이션 핸들러 const handleClickBack = () => { - const formData = location.state?.formData; - const updatedFormData = { - ...formData, - menus: menus.length > 0, - }; navigate(ROUTES.FOOD_TRUCK_FORM(String(foodTruckId)), { - state: getNavigateState(updatedFormData), + state: getNavigateState(foodTruckFormData), }); }; const handleRegister = (foodTruckId: string) => { - const formData = location.state?.formData; navigate(ROUTES.MENU_REGISTER(foodTruckId), { - state: getNavigateState(formData), + state: getNavigateState(foodTruckFormData), }); }; From 2f282ed90981d1e8fcd7403d2cbe682e2f42f86b Mon Sep 17 00:00:00 2001 From: holdn2 Date: Mon, 5 Jan 2026 16:51:34 +0900 Subject: [PATCH 14/19] =?UTF-8?q?fix:=20=EB=A9=94=EB=89=B4=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80=EB=A1=9C=20=EA=B0=94?= =?UTF-8?q?=EB=8B=A4=20=EB=8F=8C=EC=95=84=EC=98=AC=20=EB=95=8C=EB=8F=84=20?= =?UTF-8?q?state=EB=A5=BC=20=EC=A0=81=EC=A0=88=ED=9E=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=B4=EC=84=9C=20=EC=97=90=EB=9F=AC=EA=B0=80=20?= =?UTF-8?q?=EC=83=9D=EA=B8=B0=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TODO : 서버에 필요한 형식에 맞게 포맷 --- .../hooks/use-food-truck-form.ts | 46 ++++++++++++++++--- src/pages/@owner/menu/hooks/use-menu-edit.ts | 18 ++++---- src/pages/@owner/menu/hooks/use-menu-list.ts | 8 +++- .../@owner/menu/hooks/use-menu-mutations.ts | 23 ++++++++-- .../@owner/menu/hooks/use-menu-register.ts | 11 +++-- 5 files changed, 82 insertions(+), 24 deletions(-) diff --git a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts index ed83abf4..571441e4 100644 --- a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts +++ b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useMemo } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; @@ -12,6 +12,7 @@ import { } from '@pages/@owner/food-truck-form/schemas/food-truck-form.schema'; import { resetFoodTruckFormValue } from '@pages/@owner/food-truck-form/utils/reset-food-truck-form-value'; import { FOOD_TRUCK_ERROR_MESSAGE } from '@pages/@owner/food-truck-form/constants/food-truck'; +import { useMutationFoodTruckForm } from './use-mutation-food-truck-form'; const initialData = { name: '', @@ -43,6 +44,7 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { const { handleSubmit, reset, + setValue, formState: { isValid }, setError, } = methods; @@ -52,13 +54,22 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { // 메뉴 등록 여부를 위한 조회 const { data: menuData } = useMenusQuery(foodTruckIdNumber, '최신순'); + const menus = useMemo( + () => menuData?.pages.flatMap(page => page?.content ?? []) ?? [], + [menuData?.pages] + ); + + // 나의 푸드트럭 정보 업데이트 + const { updateFoodTruckInfo } = useMutationFoodTruckForm(); const isEdit = !!foodTruckDetailData; useEffect(() => { - if (!foodTruckDetailData) return; + if (!foodTruckDetailData) { + setValue('menus', menus && menus.length > 0, { shouldValidate: true }); + return; + } - const menus = menuData?.pages.flatMap(page => page?.content || []); const result = resetFoodTruckFormValue(foodTruckDetailData, menus); if (result.isError) { @@ -66,9 +77,9 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { return; } reset(result.values); - }, [foodTruckDetailData, menuData, reset, toast]); + }, [foodTruckDetailData, menus, toast, reset, setValue]); - const onSubmit = async (formData: FoodTruckFormData) => { + const handleSubmitFoodTruckInfo = async (formData: FoodTruckFormData) => { if (!formData.nameDuplicate) { setError('name', { message: FOOD_TRUCK_ERROR_MESSAGE.nameDuplicate.required, @@ -76,8 +87,29 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { return; } if (isValid && formData) { - // TODO: 푸드트럭 등록 api 호출 + // TODO: 서버에 필요한 형식에 맞게 포맷하여 보내기. 타입 만들어야 함 alert('푸드트럭 등록 제출'); + updateFoodTruckInfo({ + foodTruckId: foodTruckIdNumber, + data: { + name: formData.name, + description: formData.description, + phoneNumber: formData.phoneNumber, + activeTime: formData.activeTime, + timeDiscussRequired: formData.timeDiscussRequired, + foodTruckServiceAreas: formData.regionCodes.map( + region => region.code! + ), + menuCategories: ['MEAL'], + availableQuantity: formData.availableQuantity, + needElectricity: formData.needElectricity, + paymentMethod: formData.paymentMethod, + availableDates: formData.availableDates, + photoUrls: formData.photoUrls, + operatingInfo: formData.operatingInfo, + option: formData.option, + }, + }); } }; @@ -85,7 +117,7 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { // Form methods isEdit, methods, - handleSubmit: handleSubmit(onSubmit), + handleSubmit: handleSubmit(handleSubmitFoodTruckInfo), reset, isFormValid: isValid, }; diff --git a/src/pages/@owner/menu/hooks/use-menu-edit.ts b/src/pages/@owner/menu/hooks/use-menu-edit.ts index f6b722c2..aad2df7e 100644 --- a/src/pages/@owner/menu/hooks/use-menu-edit.ts +++ b/src/pages/@owner/menu/hooks/use-menu-edit.ts @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { ROUTES } from '@/router/constant/routes'; import type { MenuFormData } from '@pages/@owner/menu/hooks/use-form-validation'; import { @@ -7,11 +7,11 @@ import { useDeleteMenuMutation, } from '@pages/@owner/menu/hooks/use-menu-mutations'; -export const useEditMenu = ( - foodTruckId: number, - menuId: number, -) => { +export const useEditMenu = (foodTruckId: number, menuId: number) => { const navigate = useNavigate(); + const location = useLocation(); + + const foodTruckFormData = location.state?.formData; const [isModalOpen, setIsModalOpen] = useState(false); @@ -23,7 +23,7 @@ export const useEditMenu = ( }; const handleCloseModal = () => { - setIsModalOpen(false); + setIsModalOpen(false); }; const handleClickDelete = () => { @@ -36,8 +36,10 @@ export const useEditMenu = ( }; const handleClickBack = () => { - navigate(ROUTES.MENU_LIST(foodTruckId.toString())); - } + navigate(ROUTES.MENU_LIST(foodTruckId.toString()), { + state: { formData: foodTruckFormData }, + }); + }; return { isModalOpen, diff --git a/src/pages/@owner/menu/hooks/use-menu-list.ts b/src/pages/@owner/menu/hooks/use-menu-list.ts index 023bb28c..cb384836 100644 --- a/src/pages/@owner/menu/hooks/use-menu-list.ts +++ b/src/pages/@owner/menu/hooks/use-menu-list.ts @@ -45,8 +45,12 @@ export const useMenuList = (foodTruckId: number) => { // 네비게이션 핸들러 const handleClickBack = () => { + const updatedFormData = { + ...foodTruckFormData, + menus: menus.length > 0, + }; navigate(ROUTES.FOOD_TRUCK_FORM(String(foodTruckId)), { - state: getNavigateState(foodTruckFormData), + state: getNavigateState(updatedFormData), }); }; @@ -64,7 +68,7 @@ export const useMenuList = (foodTruckId: number) => { const selectedMenu = menus.find(menu => menu.menuId === Number(menuId)); navigate(ROUTES.MENU_EDIT(foodTruckId, menuId), { - state: { menuData: selectedMenu }, + state: { menuData: selectedMenu, formData: foodTruckFormData }, }); }; diff --git a/src/pages/@owner/menu/hooks/use-menu-mutations.ts b/src/pages/@owner/menu/hooks/use-menu-mutations.ts index afb7016e..2780852c 100644 --- a/src/pages/@owner/menu/hooks/use-menu-mutations.ts +++ b/src/pages/@owner/menu/hooks/use-menu-mutations.ts @@ -1,5 +1,5 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { ROUTES } from '@/router/constant/routes'; import { postFoodTruckMenu, @@ -18,6 +18,9 @@ export const useRegisterMenuMutation = (foodTruckId: number) => { const queryClient = useQueryClient(); const navigate = useNavigate(); const toast = useToast(); + const location = useLocation(); + + const foodTruckFormData = location.state?.formData; return useMutation({ mutationFn: async (formData: MenuFormData) => { @@ -50,7 +53,9 @@ export const useRegisterMenuMutation = (foodTruckId: number) => { queryClient.invalidateQueries({ queryKey: FOOD_TRUCKS_QUERY_KEY.MENUS.SORTED_LIST(foodTruckId), }); - navigate(ROUTES.MENU_LIST(foodTruckId.toString())); + navigate(ROUTES.MENU_LIST(foodTruckId.toString()), { + state: { formData: foodTruckFormData }, + }); toast.success('메뉴가 등록되었습니다.'); }, onError: () => { @@ -64,6 +69,9 @@ export const useEditMenuMutation = (foodTruckId: number, menuId: number) => { const queryClient = useQueryClient(); const navigate = useNavigate(); const toast = useToast(); + const location = useLocation(); + + const foodTruckFormData = location.state?.formData; return useMutation({ mutationFn: async (formData: MenuFormData) => { @@ -92,7 +100,9 @@ export const useEditMenuMutation = (foodTruckId: number, menuId: number) => { queryClient.invalidateQueries({ queryKey: FOOD_TRUCKS_QUERY_KEY.MENUS.SORTED_LIST(foodTruckId), }); - navigate(ROUTES.MENU_LIST(foodTruckId.toString())); + navigate(ROUTES.MENU_LIST(foodTruckId.toString()), { + state: { formData: foodTruckFormData }, + }); toast.success('메뉴가 수정되었습니다.'); }, onError: () => { @@ -106,6 +116,9 @@ export const useDeleteMenuMutation = (foodTruckId: number, menuId: number) => { const queryClient = useQueryClient(); const navigate = useNavigate(); const toast = useToast(); + const location = useLocation(); + + const foodTruckFormData = location.state?.formData; return useMutation({ mutationFn: () => deleteFoodTruckMenu({ foodTruckId, menuId }), @@ -113,7 +126,9 @@ export const useDeleteMenuMutation = (foodTruckId: number, menuId: number) => { queryClient.invalidateQueries({ queryKey: FOOD_TRUCKS_QUERY_KEY.MENUS.SORTED_LIST(foodTruckId), }); - navigate(ROUTES.MENU_LIST(foodTruckId.toString())); + navigate(ROUTES.MENU_LIST(foodTruckId.toString()), { + state: { formData: foodTruckFormData }, + }); toast.success('메뉴가 삭제되었습니다.'); }, onError: () => { diff --git a/src/pages/@owner/menu/hooks/use-menu-register.ts b/src/pages/@owner/menu/hooks/use-menu-register.ts index b3d5bbf5..f5066a97 100644 --- a/src/pages/@owner/menu/hooks/use-menu-register.ts +++ b/src/pages/@owner/menu/hooks/use-menu-register.ts @@ -1,10 +1,13 @@ -import { useNavigate } from 'react-router-dom'; -import { ROUTES } from '@/router/constant/routes'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { ROUTES } from '@router/constant/routes'; import type { MenuFormData } from '@pages/@owner/menu/hooks/use-form-validation'; import { useRegisterMenuMutation } from '@pages/@owner/menu/hooks/use-menu-mutations'; export const useRegisterMenu = (foodTruckId: number) => { const navigate = useNavigate(); + const location = useLocation(); + + const foodTruckFormData = location.state?.formData; const { mutate: registerMenu } = useRegisterMenuMutation(foodTruckId); @@ -13,7 +16,9 @@ export const useRegisterMenu = (foodTruckId: number) => { }; const handleClickBack = () => { - navigate(ROUTES.MENU_LIST(foodTruckId.toString())); + navigate(ROUTES.MENU_LIST(foodTruckId.toString()), { + state: { formData: foodTruckFormData }, + }); }; return { From 812888c8d2c1730de55a6692dc6d2b30d656407f Mon Sep 17 00:00:00 2001 From: holdn2 Date: Mon, 5 Jan 2026 17:31:58 +0900 Subject: [PATCH 15/19] =?UTF-8?q?feat:=20=EB=82=98=EC=9D=98=ED=91=B8?= =?UTF-8?q?=EB=93=9C=ED=8A=B8=EB=9F=AD=20=EB=93=B1=EB=A1=9D/=EC=88=98?= =?UTF-8?q?=EC=A0=95=20api=20=EC=97=B0=EB=8F=99=20=EC=99=84=EB=A3=8C.=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=EC=99=80=EC=9D=98=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20=ED=95=84=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TODO: handleSubmitFoodTruckInfo 함수 유효성 검증 추가 / 닉네임 중복 확인 관련 알맞게 로직 추가 --- .../@owner/estimate/utils/format-estimate.ts | 27 +++------ src/pages/@owner/food-truck-form/api/index.ts | 23 ++++++-- .../hooks/use-food-truck-form.ts | 55 +++++++------------ .../hooks/use-mutation-food-truck-form.ts | 8 ++- .../utils/format-food-truck-form.ts | 25 +++++++++ src/shared/utils/date/date-formatter.ts | 22 +++++++- 6 files changed, 95 insertions(+), 65 deletions(-) create mode 100644 src/pages/@owner/food-truck-form/utils/format-food-truck-form.ts diff --git a/src/pages/@owner/estimate/utils/format-estimate.ts b/src/pages/@owner/estimate/utils/format-estimate.ts index ebf8648e..ab46f3ba 100644 --- a/src/pages/@owner/estimate/utils/format-estimate.ts +++ b/src/pages/@owner/estimate/utils/format-estimate.ts @@ -2,6 +2,7 @@ import type { CreateReservationRequest, UpdateReservationRequest, } from 'apis/data-contracts'; +import { formatAvailableDatesToString } from '@utils/date'; import type { EstimateFormData } from '@pages/@owner/estimate/schemas/estimate.schema'; // 예약 견적서 작성 요청 body 형식으로 포맷 @@ -11,22 +12,15 @@ export const formatCreateEstimate = ( reservationUserId: number, estimateFormData: EstimateFormData ) => { - const formattedDates = estimateFormData.availableDates - .filter(date => date.startDate) - .map(date => { - if (date.endDate) { - return `${date.startDate} ~ ${date.endDate}`; - } - return `${date.startDate} ~ ${date.startDate}`; - }); - const formattedEstimate: CreateReservationRequest = { foodTruckId, chatRoomId, reservationUserId, address: estimateFormData.location, detailAddress: estimateFormData.detailLocation, - reservationDates: formattedDates, + reservationDates: formatAvailableDatesToString( + estimateFormData.availableDates + ), operationHour: estimateFormData.activeTime, menu: estimateFormData.food, deposit: estimateFormData.price, @@ -39,19 +33,12 @@ export const formatCreateEstimate = ( // 예약 견적서 수정 요청 body 형식으로 포맷 export const formatUpdateEstimate = (estimateFormData: EstimateFormData) => { - const formattedDates = estimateFormData.availableDates - .filter(date => date.startDate) - .map(date => { - if (date.endDate) { - return `${date.startDate} ~ ${date.endDate}`; - } - return `${date.startDate} ~ ${date.startDate}`; - }); - const formattedEstimate: UpdateReservationRequest = { address: estimateFormData.location, detailAddress: estimateFormData.detailLocation, - reservationDates: formattedDates, + reservationDates: formatAvailableDatesToString( + estimateFormData.availableDates + ), operationHour: estimateFormData.activeTime, menu: estimateFormData.food, deposit: estimateFormData.price, diff --git a/src/pages/@owner/food-truck-form/api/index.ts b/src/pages/@owner/food-truck-form/api/index.ts index 287831f7..b9fe0b5f 100644 --- a/src/pages/@owner/food-truck-form/api/index.ts +++ b/src/pages/@owner/food-truck-form/api/index.ts @@ -1,12 +1,25 @@ -import type { - BaseResponseFoodTruckIdResponse, - UpdateFoodTruckInfoRequest, -} from 'apis/data-contracts'; +import type { BaseResponseFoodTruckIdResponse } from 'apis/data-contracts'; import { apiRequest } from '@api/apiRequest'; +export interface UpdateFoodTruckInfoApiRequest { + name: string; + description: string; + phoneNumber: string; + activeTime: string; + timeDiscussRequired: boolean; + foodTruckServiceAreas: number[]; + menuCategories: string[]; + availableQuantity: string; + needElectricity: string; + paymentMethod: string; + availableDates: string[]; + photoUrls: string[]; + operatingInfo?: string; + option?: string; +} export const updateMyFoodTruckInfoApi = async ( foodTruckId: number, - data: UpdateFoodTruckInfoRequest + data: UpdateFoodTruckInfoApiRequest ) => { const response = await apiRequest({ endPoint: `/food-trucks/${foodTruckId}`, diff --git a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts index 571441e4..65b48f43 100644 --- a/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts +++ b/src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts @@ -11,8 +11,9 @@ import { type FoodTruckFormData, } from '@pages/@owner/food-truck-form/schemas/food-truck-form.schema'; import { resetFoodTruckFormValue } from '@pages/@owner/food-truck-form/utils/reset-food-truck-form-value'; -import { FOOD_TRUCK_ERROR_MESSAGE } from '@pages/@owner/food-truck-form/constants/food-truck'; -import { useMutationFoodTruckForm } from './use-mutation-food-truck-form'; +// import { FOOD_TRUCK_ERROR_MESSAGE } from '@pages/@owner/food-truck-form/constants/food-truck'; +import { useMutationFoodTruckForm } from '@pages/@owner/food-truck-form/hooks/use-mutation-food-truck-form'; +import { formatFoodTruckForm } from '@pages/@owner/food-truck-form/utils/format-food-truck-form'; const initialData = { name: '', @@ -46,7 +47,7 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { reset, setValue, formState: { isValid }, - setError, + // setError, } = methods; // 기존 등록 푸드트럭 데이터 조회 @@ -80,37 +81,23 @@ export const useFoodTruckForm = (foodTruckIdNumber: number) => { }, [foodTruckDetailData, menus, toast, reset, setValue]); const handleSubmitFoodTruckInfo = async (formData: FoodTruckFormData) => { - if (!formData.nameDuplicate) { - setError('name', { - message: FOOD_TRUCK_ERROR_MESSAGE.nameDuplicate.required, - }); - return; - } - if (isValid && formData) { - // TODO: 서버에 필요한 형식에 맞게 포맷하여 보내기. 타입 만들어야 함 - alert('푸드트럭 등록 제출'); - updateFoodTruckInfo({ - foodTruckId: foodTruckIdNumber, - data: { - name: formData.name, - description: formData.description, - phoneNumber: formData.phoneNumber, - activeTime: formData.activeTime, - timeDiscussRequired: formData.timeDiscussRequired, - foodTruckServiceAreas: formData.regionCodes.map( - region => region.code! - ), - menuCategories: ['MEAL'], - availableQuantity: formData.availableQuantity, - needElectricity: formData.needElectricity, - paymentMethod: formData.paymentMethod, - availableDates: formData.availableDates, - photoUrls: formData.photoUrls, - operatingInfo: formData.operatingInfo, - option: formData.option, - }, - }); - } + // TODO: 추후 아래 유효성 검증 알맞게 수정해서 추가하기 + // if (!formData.nameDuplicate) { + // setError('name', { + // message: FOOD_TRUCK_ERROR_MESSAGE.nameDuplicate.required, + // }); + // return; + // } + // if (isValid && formData) { + // updateFoodTruckInfo({ + // foodTruckId: foodTruckIdNumber, + // data: formatFoodTruckForm(formData), + // }); + // } + updateFoodTruckInfo({ + foodTruckId: foodTruckIdNumber, + data: formatFoodTruckForm(formData), + }); }; return { diff --git a/src/pages/@owner/food-truck-form/hooks/use-mutation-food-truck-form.ts b/src/pages/@owner/food-truck-form/hooks/use-mutation-food-truck-form.ts index cf1904b0..5f191622 100644 --- a/src/pages/@owner/food-truck-form/hooks/use-mutation-food-truck-form.ts +++ b/src/pages/@owner/food-truck-form/hooks/use-mutation-food-truck-form.ts @@ -1,9 +1,11 @@ import { useNavigate } from 'react-router-dom'; -import type { UpdateFoodTruckInfoRequest } from 'apis/data-contracts'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import useToast from '@hooks/use-toast'; -import { updateMyFoodTruckInfoApi } from '@pages/@owner/food-truck-form/api'; +import { + updateMyFoodTruckInfoApi, + type UpdateFoodTruckInfoApiRequest, +} from '@pages/@owner/food-truck-form/api'; import { ROUTES } from '@router/constant/routes'; import { FOOD_TRUCKS_QUERY_KEY } from '@shared/querykey/food-trucks'; @@ -18,7 +20,7 @@ export const useMutationFoodTruckForm = () => { data, }: { foodTruckId: number; - data: UpdateFoodTruckInfoRequest; + data: UpdateFoodTruckInfoApiRequest; }) => updateMyFoodTruckInfoApi(foodTruckId, data), onSuccess: () => { toast.success('푸드트럭 정보가 업데이트 되었습니다.'); diff --git a/src/pages/@owner/food-truck-form/utils/format-food-truck-form.ts b/src/pages/@owner/food-truck-form/utils/format-food-truck-form.ts new file mode 100644 index 00000000..f84c9388 --- /dev/null +++ b/src/pages/@owner/food-truck-form/utils/format-food-truck-form.ts @@ -0,0 +1,25 @@ +import { formatAvailableDatesToString } from '@utils/date'; + +import type { UpdateFoodTruckInfoApiRequest } from '@pages/@owner/food-truck-form/api'; +import type { FoodTruckFormData } from '@pages/@owner/food-truck-form/schemas/food-truck-form.schema'; + +export const formatFoodTruckForm = ( + formData: FoodTruckFormData +): UpdateFoodTruckInfoApiRequest => { + return { + name: formData.name, + description: formData.description, + phoneNumber: formData.phoneNumber, + activeTime: formData.activeTime, + timeDiscussRequired: formData.timeDiscussRequired, + foodTruckServiceAreas: formData.regionCodes.map(region => region.code!), + menuCategories: formData.menuCategories, + availableQuantity: formData.availableQuantity, + needElectricity: formData.needElectricity, + paymentMethod: formData.paymentMethod, + availableDates: formatAvailableDatesToString(formData.availableDates), + photoUrls: formData.photoUrls, + operatingInfo: formData.operatingInfo, + option: formData.option, + }; +}; diff --git a/src/shared/utils/date/date-formatter.ts b/src/shared/utils/date/date-formatter.ts index 4bb8351f..11060e9f 100644 --- a/src/shared/utils/date/date-formatter.ts +++ b/src/shared/utils/date/date-formatter.ts @@ -66,14 +66,14 @@ export const formatSelectedDateToSchedules = ( }; /** - * string[] 형식의 날짜를 AvailableDates 형식으로 포맷하는 함수 + * string[] 형식의 날짜를 AvailableDate[] 형식으로 포맷하는 함수 * ["2025.09.20 ~ 2025.09.20", "2025.09.25 ~ 2025.09.25"] * -> AvailableDate[] */ export const formatStringDatesToAvailableDates = ( - estimateDates: string[] + dates: string[] ): AvailableDate[] => { - return estimateDates + return dates .map((raw, index) => { // "YYYY.MM.DD ~ YYYY.MM.DD" 또는 "YYYY.MM.DD" 둘 다 대응 const [startRaw, endRaw] = raw.split('~').map(s => s.trim()); @@ -100,3 +100,19 @@ export const formatStringDatesToAvailableDates = ( }) .filter((v): v is AvailableDate => v !== null); }; + +/** + * AvailableDate[] 형식의 날짜를 string[] 형식으로 포맷하는 함수 + */ +export const formatAvailableDatesToString = ( + dates: AvailableDate[] +): string[] => { + return dates + .filter(date => date.startDate) + .map(date => { + if (date.endDate) { + return `${date.startDate} ~ ${date.endDate}`; + } + return `${date.startDate} ~ ${date.startDate}`; + }); +}; From a9816219e7a96bb1827c3b0a1926561567aa1287 Mon Sep 17 00:00:00 2001 From: holdn2 Date: Tue, 6 Jan 2026 10:33:05 +0900 Subject: [PATCH 16/19] =?UTF-8?q?refactor:=20=EC=84=9C=EB=B2=84=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=B0=9B=EB=8A=94=20=EB=82=A0=EC=A7=9C=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=EC=9D=B4=20=EB=8B=AC=EB=9D=BC=EB=8F=84=20=EB=8F=99?= =?UTF-8?q?=EC=9D=BC=ED=95=9C=20=EB=B0=98=ED=99=98=EA=B0=92=EC=9D=84=20?= =?UTF-8?q?=EB=82=B4=EB=8F=84=EB=A1=9D=20=ED=95=A8=EC=88=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/utils/date/date-formatter.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shared/utils/date/date-formatter.ts b/src/shared/utils/date/date-formatter.ts index 11060e9f..4aaae1f6 100644 --- a/src/shared/utils/date/date-formatter.ts +++ b/src/shared/utils/date/date-formatter.ts @@ -75,8 +75,10 @@ export const formatStringDatesToAvailableDates = ( ): AvailableDate[] => { return dates .map((raw, index) => { + // "YYYY-MM-DD ~ YYYY-MM-DD" 형태로 들어왔을 때 변환. 나의 푸드트럭 등록/수정에서 사용 + const normalized = raw.replaceAll('-', '.'); // "YYYY.MM.DD ~ YYYY.MM.DD" 또는 "YYYY.MM.DD" 둘 다 대응 - const [startRaw, endRaw] = raw.split('~').map(s => s.trim()); + const [startRaw, endRaw] = normalized.split('~').map(s => s.trim()); const startDate = startRaw ?? ''; const endDate = endRaw ?? ''; From 328eea51ae8aeda307ab433f2691ebad587847d9 Mon Sep 17 00:00:00 2001 From: holdn2 Date: Tue, 6 Jan 2026 10:57:24 +0900 Subject: [PATCH 17/19] =?UTF-8?q?fix:=20=EC=84=9C=EB=B2=84=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=9A=94=EC=B2=AD=ED=95=9C=EB=8D=B0=EB=A1=9C=20?= =?UTF-8?q?=EC=A7=80=EC=97=AD=EC=BD=94=EB=93=9C=EA=B0=80=20=EC=95=84?= =?UTF-8?q?=EB=8B=8C=20id=EB=A1=9C=20=EB=B3=B4=EB=82=B4=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@owner/food-truck-form/utils/format-food-truck-form.ts | 2 +- src/shared/components/region/components/DepthSection.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/@owner/food-truck-form/utils/format-food-truck-form.ts b/src/pages/@owner/food-truck-form/utils/format-food-truck-form.ts index f84c9388..868f0048 100644 --- a/src/pages/@owner/food-truck-form/utils/format-food-truck-form.ts +++ b/src/pages/@owner/food-truck-form/utils/format-food-truck-form.ts @@ -12,7 +12,7 @@ export const formatFoodTruckForm = ( phoneNumber: formData.phoneNumber, activeTime: formData.activeTime, timeDiscussRequired: formData.timeDiscussRequired, - foodTruckServiceAreas: formData.regionCodes.map(region => region.code!), + foodTruckServiceAreas: formData.regionCodes.map(region => region.id!), menuCategories: formData.menuCategories, availableQuantity: formData.availableQuantity, needElectricity: formData.needElectricity, diff --git a/src/shared/components/region/components/DepthSection.tsx b/src/shared/components/region/components/DepthSection.tsx index 0fd7976e..25ab7773 100644 --- a/src/shared/components/region/components/DepthSection.tsx +++ b/src/shared/components/region/components/DepthSection.tsx @@ -65,6 +65,7 @@ export default function DepthSection({ handleSelectDepth3={() => handleToggleRegion({ name: fullName, + id: item.id, code: item.code, }) } From 527735e3ce3a6d3946e1e055575297eaef102f47 Mon Sep 17 00:00:00 2001 From: holdn2 Date: Tue, 6 Jan 2026 12:28:13 +0900 Subject: [PATCH 18/19] =?UTF-8?q?code=20review:=20formdata=EA=B0=80=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=20=EB=95=8C=EC=9D=98=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/@owner/menu/hooks/use-menu-list.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pages/@owner/menu/hooks/use-menu-list.ts b/src/pages/@owner/menu/hooks/use-menu-list.ts index cb384836..d62a94d4 100644 --- a/src/pages/@owner/menu/hooks/use-menu-list.ts +++ b/src/pages/@owner/menu/hooks/use-menu-list.ts @@ -45,6 +45,10 @@ export const useMenuList = (foodTruckId: number) => { // 네비게이션 핸들러 const handleClickBack = () => { + if (!foodTruckFormData) { + navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); + return; + } const updatedFormData = { ...foodTruckFormData, menus: menus.length > 0, From cb80f29cc8feb1618d54a142b4a38a55d1236091 Mon Sep 17 00:00:00 2001 From: holdn2 Date: Tue, 6 Jan 2026 12:59:38 +0900 Subject: [PATCH 19/19] =?UTF-8?q?feat:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TODO: 푸드트럭 등록/수정 form에 대한 전체적인 유효성 검증 확인 및 수정 --- .../@owner/food-truck-form/hooks/use-basic-info.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pages/@owner/food-truck-form/hooks/use-basic-info.ts b/src/pages/@owner/food-truck-form/hooks/use-basic-info.ts index e983af77..fe49a74f 100644 --- a/src/pages/@owner/food-truck-form/hooks/use-basic-info.ts +++ b/src/pages/@owner/food-truck-form/hooks/use-basic-info.ts @@ -6,6 +6,7 @@ import { CANNOT_UPLOAD_FILE_MB, NOT_ALLOWED_FILE_TYPE } from '@constant/image'; import { formatPhoneNumber } from '@utils/phone-number'; import { FOOD_TRUCK_ERROR_MESSAGE } from '@pages/@owner/food-truck-form/constants/food-truck'; import { useFoodTruckImage } from '@pages/@owner/upload-food-truck-images/hooks/use-food-truck-image'; +import { useFoodTruckName } from '@pages/@owner/food-truck-onboarding/hooks/use-food-truck-name'; //푸드트럭 이름, 한줄소개, 전화번호, 푸드트럭 사진, 운영정보, 기타 필드 export const useBasicInfo = () => { @@ -16,12 +17,15 @@ export const useBasicInfo = () => { setError, } = useFormContext(); + const { handleCheckName, resetVerification } = useFoodTruckName(); + const formData = watch(); const name = watch('name') ?? ''; // 중복체크 버튼을 누를 수 있는 상태: 이름이 있고, 중복체크가 완료되지 않은 경우 const canCheckNameDuplicate = name.trim() !== '' && !formData.nameDuplicate; const updateName = (name: string) => { + resetVerification(); setValue('name', name, { shouldValidate: true }); setValue('nameDuplicate', false, { shouldValidate: true }); }; @@ -30,11 +34,11 @@ export const useBasicInfo = () => { setValue('description', description, { shouldValidate: true }); }; - const checkNameDuplicated = () => { - //TODO: 추후 중복확인 로직 추가 - const isDuplicateSuccess = Math.random() > 0.5; + const checkNameDuplicated = async () => { + const name = formData.name; + const response = await handleCheckName(name); - if (isDuplicateSuccess) { + if (!response?.duplicated) { setValue('nameDuplicate', true, { shouldValidate: true }); } else { setValue('nameDuplicate', false, { shouldValidate: true });