From 6dc91daaca5979b1b4bac416abf3f538e5c15e78 Mon Sep 17 00:00:00 2001 From: kent Date: Wed, 14 Jan 2026 20:04:44 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=ED=92=88=EB=AA=A9=EA=B4=80=EB=A6=AC=20A?= =?UTF-8?q?PI=20=EC=9D=91=EB=8B=B5=20=ED=8C=8C=EC=8B=B1=20=EB=B0=8F=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ApiItem 인터페이스: code → item_code 변경 (API 응답 구조 반영) - transformItem: item_code 필드 사용하도록 수정 - transformItemToApi: item_type → product_type으로 변경 (API 요청 필드명) - getItemList: Laravel 페이지네이션 응답 구조 처리 개선 - getItem: API 응답 구조(success, message, data) 처리 - getCategoryOptions: 페이지네이션 응답 파싱 수정 - createItem: 응답에서 ID 추출 로직 개선 - 디버깅용 console.log 추가 --- .../construction/item-management/actions.ts | 105 +++++++++++++++--- 1 file changed, 87 insertions(+), 18 deletions(-) diff --git a/src/components/business/construction/item-management/actions.ts b/src/components/business/construction/item-management/actions.ts index ebb6f124..896af87b 100644 --- a/src/components/business/construction/item-management/actions.ts +++ b/src/components/business/construction/item-management/actions.ts @@ -88,7 +88,7 @@ function transformOrderItems(options: Record | null | undefined */ interface ApiItem { id: number; - code: string; + item_code: string; // API가 'code' 대신 'item_code'로 반환 (ApiResponse 충돌 방지) name: string; item_type: string | null; category_id: number | null; @@ -104,7 +104,7 @@ interface ApiItem { function transformItem(apiItem: ApiItem): Item { return { id: String(apiItem.id), - itemNumber: apiItem.code || '', + itemNumber: apiItem.item_code || '', itemName: apiItem.name || '', itemType: transformItemType(apiItem.item_type), categoryId: apiItem.category_id ? String(apiItem.category_id) : '', @@ -136,7 +136,7 @@ function transformItemToApi(data: ItemFormData): Record { return { code: data.itemNumber, name: data.itemName, - item_type: transformToBackendItemType(data.itemType), + product_type: transformToBackendItemType(data.itemType), category_id: data.categoryId ? parseInt(data.categoryId, 10) : null, unit: data.unit, is_active: data.status === '사용' || data.status === '승인', @@ -161,6 +161,7 @@ function transformItemToApi(data: ItemFormData): Record { export async function getItemList( params: ItemListParams = {} ): Promise<{ success: boolean; data?: ItemListResponse; error?: string }> { + console.log('📥 [getItemList] 호출됨, params:', params); try { const queryParams: Record = {}; @@ -186,21 +187,59 @@ export async function getItemList( queryParams.active = params.status === '사용' || params.status === '승인' ? '1' : '0'; } + console.log('📤 [getItemList] API 호출:', queryParams); const response = await apiClient.get<{ data: ApiItem[]; meta?: { total: number; current_page: number; per_page: number }; total?: number; current_page?: number; per_page?: number; + last_page?: number; + per_page?: number; }>('/items', { params: queryParams }); + console.log('📥 [getItemList] API 응답:', JSON.stringify(response).slice(0, 500)); - // API 응답 구조 처리 (data 배열 또는 페이지네이션 객체) - const items = Array.isArray(response.data) ? response.data : (response.data as unknown as ApiItem[]); - const meta = response.meta || { - total: response.total || items.length, - current_page: response.current_page || params.page || 1, - per_page: response.per_page || params.size || 20, - }; + // API 응답 구조 처리 + // Laravel 페이지네이션 응답: { current_page, data: [...], last_page, per_page, total, ... } + // data 필드가 배열인 경우와 페이지네이션 객체인 경우 모두 처리 + let items: ApiItem[]; + let meta: { total: number; current_page: number; per_page: number }; + + if (Array.isArray(response.data)) { + // response.data가 직접 배열인 경우 + items = response.data; + meta = response.meta || { + total: response.total || items.length, + current_page: response.current_page || params.page || 1, + per_page: response.per_page || params.size || 20, + }; + } else if (response.data && Array.isArray((response as unknown as { data: { data: ApiItem[] } }).data.data)) { + // response가 { data: { current_page, data: [...], ... } } 형태인 경우 (Laravel 페이지네이션) + const paginatedResponse = response as unknown as { + data: { data: ApiItem[]; current_page: number; total: number; per_page: number }; + }; + items = paginatedResponse.data.data; + meta = { + total: paginatedResponse.data.total || items.length, + current_page: paginatedResponse.data.current_page || params.page || 1, + per_page: paginatedResponse.data.per_page || params.size || 20, + }; + } else if (response.current_page !== undefined && Array.isArray((response as unknown as { data: ApiItem[] }).data)) { + // response가 { current_page, data: [...], total, per_page } 형태인 경우 + items = (response as unknown as { data: ApiItem[] }).data; + meta = { + total: response.total || items.length, + current_page: response.current_page || params.page || 1, + per_page: response.per_page || params.size || 20, + }; + } else { + // 예상치 못한 응답 구조 + console.error('❌ [getItemList] 예상치 못한 응답 구조:', response); + items = []; + meta = { total: 0, current_page: 1, per_page: 20 }; + } + + console.log('📊 [getItemList] 파싱된 items 수:', items.length, ', meta:', meta); // Frontend 필터링 (Backend에서 지원하지 않는 필터) let transformedItems = items.map(transformItem); @@ -248,7 +287,7 @@ export async function getItemList( }, }; } catch (error) { - console.error('품목 목록 조회 오류:', error); + console.error('❌ [getItemList] 오류:', error); return { success: false, error: '품목 목록을 불러오는데 실패했습니다.' }; } } @@ -318,10 +357,23 @@ export async function getCategoryOptions(): Promise<{ }> { try { const response = await apiClient.get<{ - data: { id: number; name: string }[]; + success: boolean; + message: string; + data: { + current_page: number; + data: { id: number; name: string }[]; + total: number; + }; }>('/categories', { params: { size: 100 } }); - const categories = response.data.map((cat) => ({ + // API 응답 구조: { success, message, data: { current_page, data: [...], ... } } + const categoryData = response.data?.data; + if (!Array.isArray(categoryData)) { + console.error('카테고리 응답 구조 오류:', response); + return { success: true, data: [] }; + } + + const categories = categoryData.map((cat) => ({ id: String(cat.id), name: cat.name, })); @@ -343,9 +395,17 @@ export async function getItem(id: string): Promise<{ error?: string; }> { try { - const response = await apiClient.get(`/items/${id}`); + const response = await apiClient.get<{ success: boolean; message: string; data: ApiItem }>(`/items/${id}`); + console.log('📥 [getItem] API 응답:', JSON.stringify(response).slice(0, 300)); - return { success: true, data: transformItemDetail(response) }; + // API 응답 구조: { success, message, data: {...item...} } + const item = response.data; + if (!item) { + console.error('❌ [getItem] 응답에 data가 없음:', response); + return { success: false, error: '품목 정보를 찾을 수 없습니다.' }; + } + + return { success: true, data: transformItemDetail(item) }; } catch (error) { console.error('품목 상세 조회 오류:', error); return { success: false, error: '품목 정보를 불러오는데 실패했습니다.' }; @@ -362,12 +422,21 @@ export async function createItem(data: ItemFormData): Promise<{ error?: string; }> { try { + console.log('📤 [createItem] 입력 데이터:', data); const apiData = transformItemToApi(data); - const response = await apiClient.post<{ id: number }>('/items', apiData); + console.log('📤 [createItem] 변환된 API 데이터:', apiData); + const response = await apiClient.post<{ success: boolean; data: { id: number }; id?: number }>('/items', apiData); + console.log('📥 [createItem] API 응답:', response); - return { success: true, data: { id: String(response.id) } }; + // API 응답 구조: { success, message, data: { id } } 또는 { id } + const itemId = response.data?.id ?? response.id; + if (!itemId) { + console.error('❌ [createItem] 응답에서 ID를 찾을 수 없음:', response); + return { success: false, error: '품목 등록 후 ID를 가져오지 못했습니다.' }; + } + return { success: true, data: { id: String(itemId) } }; } catch (error) { - console.error('품목 등록 오류:', error); + console.error('❌ [createItem] 품목 등록 오류:', error); return { success: false, error: '품목 등록에 실패했습니다.' }; } }