Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/pages/@owner/estimate/hooks/use-estimate-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -67,7 +67,7 @@ export const useEstimateForm = (
{
location: estimateData.address ?? '',
detailLocation: estimateData.detailAddress ?? '',
availableDates: formatEstimateDatesToAvailableDates(
availableDates: formatStringDatesToAvailableDates(
estimateData.reservationDates ?? []
),
activeTime: estimateData.operationHour ?? undefined,
Expand Down
25 changes: 14 additions & 11 deletions src/pages/@owner/food-truck-form/FoodTruckForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -12,7 +15,6 @@ import {
FoodTruckOption,
FoodTruckPhoto,
} from '@pages/@owner/food-truck-form/@section/basic-info-section/index';

import {
AvailableQuantity,
NeedElectricity,
Expand All @@ -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() {
Expand All @@ -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,
Expand All @@ -58,25 +59,27 @@ export default function FoodTruckForm() {
handleActiveDateSetValue,
handleActiveDateError,
} = useFoodTruckFormDate(methods);
// 서버에서 활동 가능 지역은 지역코드로 받아야함
const { foodTruckDetailData } = useFoodTruckDetail(foodTruckIdNumber);

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);
return null;
}
Comment on lines +69 to +73
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Hooks 규칙 위반 및 로직 흐름 문제를 수정해주세요.

현재 구조는 다음 문제들이 있습니다:

  1. Hooks 규칙 위반: 유효성 검증(line 69-73)이 hooks 호출(line 43-61) 이후에 위치하여, 조건부로 hooks를 건너뛰게 됩니다. Hooks는 항상 동일한 순서로 호출되어야 합니다.

  2. 로직 흐름 문제: navigate()는 비동기적으로 작동하므로 return null 전에 컴포넌트가 잠깐 렌더링될 수 있으며, 토스트 메시지가 표시되지 않을 수 있습니다.

  3. 사용자 경험: 잘못된 접근 시 빈 화면이 깜빡일 수 있습니다.

🔎 제안하는 수정

방법 1: Early return을 hooks 전에 배치

 export default function FoodTruckForm() {
   const { foodTruckId } = useParams();
-  const foodTruckIdNumber = Number(foodTruckId);
-
   const navigate = useNavigate();
   const location = useLocation();
   const toast = useToast();

+  // Hooks 호출 전에 early return
+  if (!foodTruckId || isNaN(Number(foodTruckId))) {
+    useEffect(() => {
+      toast.error('잘못된 접근입니다.');
+      navigate(ROUTES.FOOD_TRUCK_MANAGEMENT);
+    }, [navigate, toast]);
+    return null;
+  }
+
+  const foodTruckIdNumber = Number(foodTruckId);
+
   const { isEdit, methods, reset, isFormValid, handleSubmit } =
     useFoodTruckForm(foodTruckIdNumber);

   // ... rest of hooks

-  if (!foodTruckId || isNaN(foodTruckIdNumber)) {
-    toast.error('잘못된 접근입니다.');
-    navigate(ROUTES.FOOD_TRUCK_MANAGEMENT);
-    return null;
-  }

방법 2: useEffect로 검증 처리 (더 권장)

 export default function FoodTruckForm() {
   const { foodTruckId } = useParams();
   const foodTruckIdNumber = Number(foodTruckId);
   const navigate = useNavigate();
   const location = useLocation();
   const toast = useToast();

+  // 유효성 검증을 useEffect로 처리
+  useEffect(() => {
+    if (!foodTruckId || isNaN(foodTruckIdNumber)) {
+      toast.error('잘못된 접근입니다.');
+      navigate(ROUTES.FOOD_TRUCK_MANAGEMENT);
+    }
+  }, [foodTruckId, foodTruckIdNumber, navigate, toast]);
+
+  // 잘못된 접근인 경우 로딩 상태 표시
+  if (!foodTruckId || isNaN(foodTruckIdNumber)) {
+    return null; // 또는 <LoadingSpinner /> 등
+  }

   const { isEdit, methods, reset, isFormValid, handleSubmit } =
     useFoodTruckForm(foodTruckIdNumber);

   // ... rest of the component

-  if (!foodTruckId || isNaN(foodTruckIdNumber)) {
-    toast.error('잘못된 접근입니다.');
-    navigate(ROUTES.FOOD_TRUCK_MANAGEMENT);
-    return null;
-  }


const handleNavigateBack = () => {
navigate(ROUTES.FOOD_TRUCK_MANAGEMENT);
};

return (
<FormProvider {...methods}>
<Navigation
centerContent={
foodTruckDetailData ? '나의 푸드트럭 수정' : '나의 푸드트럭 등록'
}
centerContent={isEdit ? '나의 푸드트럭 수정' : '나의 푸드트럭 등록'}
leftIcon={<Icon name='ic_back' />}
handleLeftClick={handleNavigateBack}
/>
Expand All @@ -91,7 +94,7 @@ export default function FoodTruckForm() {
handleActiveTimeSetValue={handleActiveTimeSetValue}
handleTimeDiscussRequiredSetValue={handleTimeDiscussRequiredSetValue}
/>
<RegionSection />
<RegionSection foodTruckId={foodTruckId} />
<MenuCategory />
<AvailableQuantity />
<NeedElectricity />
Expand Down
2 changes: 1 addition & 1 deletion src/pages/@owner/food-truck-form/constants/food-truck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const FOOD_TRUCK_MAX_LENGTH = {
},
availableDates: {
min: 1,
max: 2,
max: 4,
},
photoUrls: {
min: 1,
Expand Down
49 changes: 45 additions & 4 deletions src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
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';
import { useMenusQuery } from '@pages/@owner/menu/hooks/use-menus-query';

const initialData = {
name: '',
Expand All @@ -25,10 +30,10 @@ const initialData = {
menus: false,
};

export const useFoodTruckForm = (prevData?: FoodTruckFormData) => {
export const useFoodTruckForm = (foodTruckIdNumber: number) => {
const methods = useForm<FoodTruckFormData>({
resolver: zodResolver(foodTruckSchema),
defaultValues: prevData ?? initialData,
defaultValues: initialData,
mode: 'onChange',
});

Expand All @@ -39,6 +44,41 @@ 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) {
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,
menus: menuData !== undefined,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

메뉴 존재 여부 체크 로직을 개선해주세요.

menuData !== undefineduseMenusQuery의 반환값이 존재하는지만 체크합니다. useInfiniteQuerydata.pages 배열을 반환하므로, 실제 메뉴 항목이 있는지 확인하려면 페이지 내용을 체크해야 합니다.

🔎 메뉴 존재 여부를 정확히 체크하는 수정 제안
-        menus: menuData !== undefined,
+        menus: menuData?.pages?.[0]?.content?.length > 0,

또는 더 안전하게:

-        menus: menuData?.pages.some(page => page.content?.length > 0) ?? false,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
menus: menuData !== undefined,
menus: menuData?.pages?.[0]?.content?.length > 0,
🤖 Prompt for AI Agents
In src/pages/@owner/food-truck-form/hooks/use-food-truck-form.ts around line 77,
the current menus check uses `menuData !== undefined`, which only verifies the
query returned something but not whether any menu items exist; update it to
inspect `menuData.pages` (e.g., check `menuData?.pages?.some(page => page.items
&& page.items.length > 0)` or `menuData?.pages?.flatMap(p => p.items ??
[]).length > 0`) so the flag truly reflects presence of menu entries and safely
handles undefined/null pages/items.

});
}
}, [foodTruckDetailData, menuData, reset]);

const onSubmit = async (formData: FoodTruckFormData) => {
if (!formData.nameDuplicate) {
setError('name', {
Expand All @@ -47,13 +87,14 @@ export const useFoodTruckForm = (prevData?: FoodTruckFormData) => {
return;
}
if (isValid && formData) {
//TODO: 계좌 등록 제출
// TODO: 푸드트럭 등록 api 호출
alert('푸드트럭 등록 제출');
}
};

return {
// Form methods
isEdit,
methods,
handleSubmit: handleSubmit(onSubmit),
reset,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -34,10 +31,10 @@ export const foodTruckSchema = z.object({
FOOD_TRUCK_ERROR_MESSAGE.phoneNumber.required
),
regionCodes: z.array(z.custom<RegionResponse>()),
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()),
Comment on lines +34 to +37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

타입 안정성 손실을 해결해주세요.

availableQuantity, needElectricity, paymentMethod, menuCategoriesz.nativeEnum()에서 z.string()으로 변경되어 다음 문제가 발생합니다:

  1. 컴파일 타임 타입 체크 손실: 잘못된 값이 런타임까지 발견되지 않음
  2. 허용값 검증 없음: 오타나 잘못된 문자열도 통과
  3. API 계약 불일치 가능성: 백엔드가 예상하지 못한 값 수신 가능

의도적인 변경이라면 다음 중 하나를 권장합니다:

  • z.enum([...]) 사용하여 허용값 명시
  • .refine() 추가로 런타임 검증
  • 백엔드 스키마와 동기화된 공유 타입 정의
🔎 제안하는 개선안 (예시: needElectricity)
+import { NEED_ELECTRICITY } from '@shared/constant/need-electricity';
+
+const needElectricityValues = Object.values(NEED_ELECTRICITY) as [string, ...string[]];
+
 export const foodTruckSchema = z.object({
   // ... other fields
-  needElectricity: z.string(),
+  needElectricity: z.enum(needElectricityValues, {
+    error: () => ({ code: 'custom', message: '올바른 전기 사용 옵션을 선택해주세요.' }),
+  }),

다른 필드들도 유사하게 처리할 수 있습니다.

Committable suggestion skipped: line range outside the PR's diff.

photoUrls: z.array(z.string()).min(1, {
message: FOOD_TRUCK_ERROR_MESSAGE.photoUrls.required,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -19,7 +20,9 @@ export default function useFoodTruckDetail(foodTruckId: number) {
data: foodTruckDetailData,
isPending: isPendingFoodTruckDetail,
isError: isErrorFoodTruckDetail,
} = useQuery<FoodTruckDetailResponse | undefined>(foodTruckDetailQuery(foodTruckId));
} = useQuery<FoodTruckDetailResponse | undefined>(
foodTruckDetailQuery(foodTruckId)
);

const { mutate: updateSaveStatus } =
useUpdateFoodTruckSaveStatus(foodTruckId);
Expand Down
2 changes: 1 addition & 1 deletion src/shared/constant/need-electricity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const NEED_ELECTRICITY = {
REQUIRED: '필요',
NOT_REQUIRED: '필요 없음',
NOT_REQUIRED: '불필요',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for needElectricity usage across the codebase
rg -nP --type=ts --type=tsx -C3 'needElectricity|NEED_ELECTRICITY|need-electricity' -g '!node_modules'

Repository: CHA-CALL/ChaCall-FE

Length of output: 90


🏁 Script executed:

#!/bin/bash
# Search for needElectricity usage across the codebase
# Using glob pattern to include .ts and .tsx files
rg 'needElectricity|NEED_ELECTRICITY|need-electricity' -g '*.ts' -g '*.tsx' -C3

Repository: CHA-CALL/ChaCall-FE

Length of output: 23723


Zod 스키마에서 needElectricity 유효성 검증 누락

food-truck-form.schema.ts에서 needElectricityz.string()으로 정의되어 있어 허용되는 값에 대한 검증이 없습니다. 백엔드 API는 "필요" | "불필요" | "논의 필요" 중 하나만 받아들이므로, 다음과 같이 수정이 필요합니다:

needElectricity: z.enum(['필요', '불필요', '논의 필요'])

또한 예상과 다르게 src/pages/@owner/estimate/@section/basic-info-section/NeedElectricity.tsx에서는 같은 필드가 boolean 타입으로 처리되고 있습니다. 데이터 타입 일관성을 확인해주세요.

🤖 Prompt for AI Agents
src/shared/constant/need-electricity.ts (around line 3) and related files: the
current constant and form/schema usage are inconsistent with the backend enum;
update food-truck-form.schema.ts to validate needElectricity as
z.enum(['필요','불필요','논의 필요']) instead of z.string(), ensure
src/pages/@owner/estimate/@section/basic-info-section/NeedElectricity.tsx stops
treating the field as boolean and instead reads/writes one of the three string
values, and sync the constant file to export exactly those three string values
so all places use the same type/labels.

NEED_DISCUSSION: '논의 필요',
} as const;

Expand Down
4 changes: 2 additions & 2 deletions src/shared/constant/payment-method.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export const PAYMENT_METHOD = {
CARD: '카드',
BANK_TRANSFER: '계좌 이체',
ANY: '아무거나',
BANK_TRANSFER: '계좌이체',
ANY: '무관',
Comment on lines +3 to +4
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find usages of paymentMethod and PAYMENT_METHOD across the codebase
rg -nP --type=ts --type=tsx -C3 'paymentMethod|PAYMENT_METHOD' -g '!node_modules'

Repository: CHA-CALL/ChaCall-FE

Length of output: 90


🏁 Script executed:

#!/bin/bash
# Search for paymentMethod and PAYMENT_METHOD usage without tsx type
rg -nP 'paymentMethod|PAYMENT_METHOD' -C3 -g '!node_modules'

Repository: CHA-CALL/ChaCall-FE

Length of output: 16300


🏁 Script executed:

#!/bin/bash
# Search for old payment method values that may have changed
rg '계좌 이체|아무거나' -g '!node_modules'

Repository: CHA-CALL/ChaCall-FE

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Read the food-truck-form.schema.ts file
head -100 src/pages/@owner/food-truck-form/schemas/food-truck-form.schema.ts

Repository: CHA-CALL/ChaCall-FE

Length of output: 2146


🏁 Script executed:

#!/bin/bash
# Search for any hardcoded payment method comparisons or mappings
rg 'BANK_TRANSFER|CARD|ANY' -C2 src/shared/constant/payment-method.ts

Repository: CHA-CALL/ChaCall-FE

Length of output: 159


기존 데이터와의 호환성을 확인해주세요.

BANK_TRANSFERANY의 문자열 값이 변경되었습니다:

  • '계좌 이체' → '계좌이체' (공백 제거)
  • '아무거나' → '무관' (더 공식적인 표현)

백엔드 API 계약서에는 이미 새로운 값('무관', '계좌이체', '카드')이 정의되어 있어 새 값을 수신하는 데는 문제가 없습니다. 그러나 데이터베이스에 존재하는 기존 푸드트럭 데이터가 여전히 이전 문자열 값을 가지고 있을 수 있으므로 다음을 확인해주세요:

  • 기존 데이터를 조회할 때 이전 값이 반환되는 경우 UI에서 제대로 처리되는지
  • 기존 푸드트럭 수정 시 백엔드에서 반환된 이전 값이 폼에 올바르게 로드되는지
  • 필요한 경우 데이터 마이그레이션 계획

} as const;

export type PaymentMethodKey = keyof typeof PAYMENT_METHOD;
4 changes: 2 additions & 2 deletions src/shared/utils/date/date-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down