/** * 견적 금액 요약 패널 * * - 개소별 합계 (왼쪽) - 클릭하여 상세 확인 * - 상세별 합계 (오른쪽) - 선택 개소의 카테고리별 금액 및 품목 상세 * - 스크롤 가능한 상세 영역 */ "use client"; import { useMemo } from "react"; import { Coins } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "../ui/card"; import type { LocationItem } from "./QuoteRegistrationV2"; // ============================================================================= // 목데이터 - 상세별 합계 (공정별 + 품목 상세) // ============================================================================= interface DetailItem { name: string; quantity: number; unitPrice: number; totalPrice: number; } interface DetailCategory { label: string; count: number; amount: number; items: DetailItem[]; } const MOCK_DETAIL_TOTALS: DetailCategory[] = [ { label: "본체 (스크린/슬랫)", count: 1, amount: 1061676, items: [ { name: "실리카 스크린", quantity: 1, unitPrice: 1061676, totalPrice: 1061676 }, ] }, { label: "절곡품 - 가이드레일", count: 2, amount: 116556, items: [ { name: "벽면형 마감재", quantity: 2, unitPrice: 42024, totalPrice: 84048 }, { name: "본체 가이드 레일", quantity: 2, unitPrice: 16254, totalPrice: 32508 }, ] }, { label: "절곡품 - 케이스", count: 1, amount: 30348, items: [ { name: "전면부 케이스", quantity: 1, unitPrice: 30348, totalPrice: 30348 }, ] }, { label: "절곡품 - 하단마감재", count: 1, amount: 15420, items: [ { name: "하단 하우징", quantity: 1, unitPrice: 15420, totalPrice: 15420 }, ] }, { label: "모터 & 제어기", count: 2, amount: 400000, items: [ { name: "직류 모터", quantity: 1, unitPrice: 250000, totalPrice: 250000 }, { name: "제어기", quantity: 1, unitPrice: 150000, totalPrice: 150000 }, ] }, { label: "부자재", count: 2, amount: 21200, items: [ { name: "각파이프 25mm", quantity: 2, unitPrice: 8500, totalPrice: 17000 }, { name: "플랫바 20mm", quantity: 1, unitPrice: 4200, totalPrice: 4200 }, ] }, ]; // ============================================================================= // Props // ============================================================================= interface QuoteSummaryPanelProps { locations: LocationItem[]; selectedLocationId: string | null; onSelectLocation: (id: string) => void; } // ============================================================================= // 컴포넌트 // ============================================================================= export function QuoteSummaryPanel({ locations, selectedLocationId, onSelectLocation, }: QuoteSummaryPanelProps) { // --------------------------------------------------------------------------- // 계산된 값 // --------------------------------------------------------------------------- // 선택된 개소 const selectedLocation = useMemo(() => { return locations.find((loc) => loc.id === selectedLocationId) || null; }, [locations, selectedLocationId]); // 총 금액 const totalAmount = useMemo(() => { return locations.reduce((sum, loc) => sum + (loc.totalPrice || 0), 0); }, [locations]); // 개소별 합계 const locationTotals = useMemo(() => { return locations.map((loc) => ({ id: loc.id, label: `${loc.floor} / ${loc.code}`, productCode: loc.productCode, quantity: loc.quantity, unitPrice: loc.unitPrice || 0, totalPrice: loc.totalPrice || 0, })); }, [locations]); // 선택 개소의 상세별 합계 (공정별) - 목데이터 포함 const detailTotals = useMemo((): DetailCategory[] => { // bomResult가 없으면 목데이터 사용 if (!selectedLocation?.bomResult?.subtotals) { return selectedLocation ? MOCK_DETAIL_TOTALS : []; } const subtotals = selectedLocation.bomResult.subtotals; const groupedItems = selectedLocation.bomResult.grouped_items; const result: DetailCategory[] = []; Object.entries(subtotals).forEach(([key, value]) => { if (typeof value === "object" && value !== null) { // grouped_items에서 items 가져오기 (subtotals에는 items가 없을 수 있음) const groupItemsRaw = groupedItems?.[key]?.items || value.items || []; // DetailItem 형식으로 변환 const groupItems: DetailItem[] = (groupItemsRaw as Array<{ item_name?: string; name?: string; quantity?: number; unit_price?: number; total_price?: number; }>).map((item) => ({ name: item.item_name || item.name || "", quantity: item.quantity || 0, unitPrice: item.unit_price || 0, totalPrice: item.total_price || 0, })); result.push({ label: value.name || key, count: value.count || 0, amount: value.subtotal || 0, items: groupItems, }); } else if (typeof value === "number") { result.push({ label: key, count: 0, amount: value, items: [], }); } }); return result; }, [selectedLocation]); // --------------------------------------------------------------------------- // 렌더링 // --------------------------------------------------------------------------- return ( 견적 금액 요약 {/* 좌우 분할 */}
{/* 왼쪽: 개소별 합계 */}
📍

개소별 합계

{locations.length === 0 ? (

개소를 추가해주세요

) : (
{locationTotals.map((loc) => (
onSelectLocation(loc.id)} >

{loc.label}

{loc.productCode} × {loc.quantity}

상세소계

{loc.totalPrice.toLocaleString()}

{loc.unitPrice > 0 && (

수량 적용: {(loc.unitPrice * loc.quantity).toLocaleString()}

)}
))}
)}
{/* 오른쪽: 상세별 합계 */}

상세별 합계 {selectedLocation && ( ({selectedLocation.floor} / {selectedLocation.code}) )}

{!selectedLocation ? (

개소를 선택해주세요

) : detailTotals.length === 0 ? (

견적 산출 후 상세 금액이 표시됩니다

) : (
{detailTotals.map((category, index) => (
{/* 카테고리 헤더 */}
{category.label}
({category.count}개) {category.amount.toLocaleString()}
{/* 품목 상세 목록 */}
{category.items.map((item, itemIndex) => (
{item.name}

수량: {item.quantity} × 단가: {item.unitPrice.toLocaleString()}

{item.totalPrice.toLocaleString()}
))}
))}
)}
{/* 하단 바: 총 개소 수, 예상 견적금액, 견적 상태 */}

총 개소 수

{locations.length}

예상 견적금액

{totalAmount.toLocaleString()}

견적 상태

작성중
); }