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..e1754370 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,11 @@ export default function FoodTruckForm() { } }, [location.state, reset]); + if (!foodTruckId || isNaN(foodTruckIdNumber)) { + toast.error('잘못된 접근입니다.'); + navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); + } + const handleNavigateBack = () => { navigate(ROUTES.FOOD_TRUCK_MANAGEMENT); }; @@ -74,9 +78,7 @@ export default function FoodTruckForm() { return ( } handleLeftClick={handleNavigateBack} /> @@ -91,7 +93,7 @@ export default function FoodTruckForm() { handleActiveTimeSetValue={handleActiveTimeSetValue} handleTimeDiscussRequiredSetValue={handleTimeDiscussRequiredSetValue} /> - + 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..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 @@ -1,10 +1,21 @@ -import { zodResolver } from '@hookform/resolvers/zod'; +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'; +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, 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: '', @@ -25,10 +36,11 @@ const initialData = { menus: false, }; -export const useFoodTruckForm = (prevData?: FoodTruckFormData) => { +export const useFoodTruckForm = (foodTruckIdNumber: number) => { + const toast = useToast(); const methods = useForm({ resolver: zodResolver(foodTruckSchema), - defaultValues: prevData ?? initialData, + defaultValues: initialData, mode: 'onChange', }); @@ -39,6 +51,58 @@ export const useFoodTruckForm = (prevData?: FoodTruckFormData) => { setError, } = methods; + // 기존 데이터가 있다면 수정 mode + const [isEdit, setIsEdit] = useState(false); + + // 기존 등록 푸드트럭 데이터 조회 + const { foodTruckDetailData } = useFoodTruckDetail(foodTruckIdNumber); + + // 메뉴 등록 여부를 위한 조회 + const { data: menuData } = useMenusQuery(foodTruckIdNumber, '최신순'); + + 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, + }); + } + }, [foodTruckDetailData, menuData, reset]); + const onSubmit = async (formData: FoodTruckFormData) => { if (!formData.nameDuplicate) { setError('name', { @@ -47,13 +111,14 @@ export const useFoodTruckForm = (prevData?: FoodTruckFormData) => { return; } if (isValid && formData) { - //TODO: 계좌 등록 제출 + // TODO: 푸드트럭 등록 api 호출 alert('푸드트럭 등록 제출'); } }; 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..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 @@ -5,12 +5,12 @@ 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'; +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 @@ -34,10 +34,12 @@ 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.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/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 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; +};