From 32a1ed2de7ca637e75a556c77d4c2d82b6b280aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B6=8C=ED=98=81=EC=84=B1?= Date: Thu, 29 Jan 2026 08:05:08 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B2=AC=EC=A0=81=20=ED=92=88=EB=AA=A9?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EC=8B=9C=20=EB=8B=A8=EA=B0=80=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fetchItemPrices 클라이언트 API 함수 추가 (items.ts) - 품목 선택 시 /api/proxy/quotes/items/prices를 통해 단가 조회 - 조회된 단가로 견적금액요약에 즉시 반영 --- src/components/quotes/LocationDetailPanel.tsx | 27 +++++++-- src/lib/api/items.ts | 55 +++++++++++++++++++ 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/components/quotes/LocationDetailPanel.tsx b/src/components/quotes/LocationDetailPanel.tsx index a8287819..0b913639 100644 --- a/src/components/quotes/LocationDetailPanel.tsx +++ b/src/components/quotes/LocationDetailPanel.tsx @@ -12,6 +12,7 @@ import { useState, useMemo, useEffect } from "react"; import { Package, Settings, Plus, Trash2, Loader2, Calculator, Save } from "lucide-react"; import { getItemCategoryTree, type ItemCategoryNode } from "./actions"; +import { fetchItemPrices } from "@/lib/api/items"; import { Badge } from "../ui/badge"; import { Button } from "../ui/button"; @@ -724,21 +725,32 @@ export function LocationDetailPanel({ { + onSelectItem={async (item) => { if (!location) return; const currentTab = detailTabs.find((t) => t.value === activeTab); const categoryCode = activeTab; const categoryLabel = currentTab?.label || activeTab; + // 단가 조회 (클라이언트 API 호출) + let unitPrice = 0; + try { + const priceResult = await fetchItemPrices([item.code]); + unitPrice = priceResult[item.code]?.unit_price || 0; + } catch (error) { + console.error('[품목 추가] 단가 조회 실패:', error); + } + + const totalPrice = unitPrice * 1; // quantity = 1 + const newItem: BomCalculationResultItem & { category_code?: string; is_manual?: boolean } = { item_code: item.code, item_name: item.name, specification: item.specification || "", unit: "EA", quantity: 1, - unit_price: 0, - total_price: 0, + unit_price: unitPrice, + total_price: totalPrice, process_group: categoryLabel, category_code: categoryCode, is_manual: true, @@ -789,8 +801,13 @@ export function LocationDetailPanel({ grand_total: updatedGrandTotal, }; - onUpdateLocation(location.id, { bomResult: updatedBomResult }); - console.log(`[품목 추가] ${item.code} - ${item.name} → ${categoryLabel} (${categoryCode})`); + // unitPrice, totalPrice도 함께 업데이트 + onUpdateLocation(location.id, { + bomResult: updatedBomResult, + unitPrice: updatedGrandTotal, + totalPrice: updatedGrandTotal * location.quantity, + }); + console.log(`[품목 추가] ${item.code} - ${item.name} → ${categoryLabel} (${categoryCode}), 단가: ${unitPrice}`); }} tabLabel={detailTabs.find((t) => t.value === activeTab)?.label} /> diff --git a/src/lib/api/items.ts b/src/lib/api/items.ts index 090748ea..8c89ce5b 100644 --- a/src/lib/api/items.ts +++ b/src/lib/api/items.ts @@ -759,4 +759,59 @@ export async function revalidateItems(): Promise { const { revalidatePath } = await import('next/cache'); revalidatePath('/items'); } +} + +// ===== 품목 단가 조회 ===== + +/** 품목 단가 조회 결과 */ +export interface ItemPriceResult { + item_code: string; + unit_price: number; +} + +/** + * 품목 단가 조회 (견적 품목 추가 시 사용) + * + * @param itemCodes - 조회할 품목 코드 배열 + * @returns 품목 코드별 단가 정보 + * + * @example + * const prices = await fetchItemPrices(['N71807', 'N71808']); + * console.log(prices['N71807']?.unit_price); // 1320000 + */ +export async function fetchItemPrices( + itemCodes: string[] +): Promise> { + if (!itemCodes.length) { + return {}; + } + + try { + // 프록시 경유: /api/proxy/quotes/items/prices → /api/v1/quotes/items/prices + const response = await fetch('/api/proxy/quotes/items/prices', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ item_codes: itemCodes }), + credentials: 'include', + }); + + if (!response.ok) { + console.error('[fetchItemPrices] API 에러:', response.status); + return {}; + } + + const result = await response.json(); + console.log('[fetchItemPrices] API 응답:', result); + + if (result.success && result.data) { + return result.data as Record; + } + + return {}; + } catch (error) { + console.error('[fetchItemPrices] 에러:', error); + return {}; + } } \ No newline at end of file