- LocationDetailPanel: 6개 탭 구현 (본체, 가이드레일, 케이스, 하단마감재, 모터&제어기, 부자재) - 각 탭별 다른 테이블 컬럼 구조 적용 - QuoteSummaryPanel: 개소별/상세별 합계 패널 개선 - QuotePreviewModal: EstimateDocumentModal 패턴 적용 (헤더+버튼 영역 분리) - Input value → defaultValue 변경으로 React 경고 해결 - 팩스/카카오톡 버튼 제거 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
623 lines
27 KiB
TypeScript
623 lines
27 KiB
TypeScript
/**
|
||
* 선택 개소 상세 정보 패널
|
||
*
|
||
* - 제품 정보 (제품명, 오픈사이즈, 제작사이즈, 산출중량, 산출면적, 수량)
|
||
* - 필수 설정 (가이드레일, 전원, 제어기)
|
||
* - 탭: 본체(스크린/슬랫), 절곡품-가이드레일, 절곡품-케이스, 절곡품-하단마감재, 모터&제어기, 부자재
|
||
* - 탭별 품목 테이블 (각 탭마다 다른 컬럼 구조)
|
||
*/
|
||
|
||
"use client";
|
||
|
||
import { useState, useMemo } from "react";
|
||
import { Package, Settings, Plus, Trash2 } from "lucide-react";
|
||
|
||
import { Badge } from "../ui/badge";
|
||
import { Button } from "../ui/button";
|
||
import { Input } from "../ui/input";
|
||
import {
|
||
Select,
|
||
SelectContent,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from "../ui/select";
|
||
import {
|
||
Table,
|
||
TableBody,
|
||
TableCell,
|
||
TableHead,
|
||
TableHeader,
|
||
TableRow,
|
||
} from "../ui/table";
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
||
import { ItemSearchModal } from "./ItemSearchModal";
|
||
|
||
// 납품길이 옵션
|
||
const DELIVERY_LENGTH_OPTIONS = [
|
||
{ value: "3000", label: "3000" },
|
||
{ value: "4000", label: "4000" },
|
||
{ value: "5000", label: "5000" },
|
||
{ value: "6000", label: "6000" },
|
||
];
|
||
|
||
// 목데이터 - 탭별 품목 아이템 (각 탭마다 다른 구조)
|
||
const MOCK_BOM_ITEMS = {
|
||
// 본체 (스크린/슬랫): 품목명, 제작사이즈, 수량, 작업
|
||
body: [
|
||
{ id: "b1", item_name: "실리카 스크린", manufacture_size: "5280*3280", quantity: 1, unit: "EA", total_price: 1061676 },
|
||
],
|
||
// 절곡품 - 가이드레일: 품목명, 재질, 규격, 납품길이, 수량, 작업
|
||
"guide-rail": [
|
||
{ id: "g1", item_name: "벽면형 마감재", material: "알루미늄", spec: "50mm", delivery_length: "4000", quantity: 2, unit: "EA", total_price: 84048 },
|
||
{ id: "g2", item_name: "본체 가이드 레일", material: "스틸", spec: "20mm", delivery_length: "4000", quantity: 2, unit: "EA", total_price: 32508 },
|
||
],
|
||
// 절곡품 - 케이스: 품목명, 재질, 규격, 납품길이, 수량, 작업
|
||
case: [
|
||
{ id: "c1", item_name: "전면부 케이스", material: "알루미늄", spec: "30mm", delivery_length: "4000", quantity: 1, unit: "EA", total_price: 30348 },
|
||
],
|
||
// 절곡품 - 하단마감재: 품목명, 재질, 규격, 납품길이, 수량, 작업
|
||
bottom: [
|
||
{ id: "bt1", item_name: "하단 하우징", material: "스틸", spec: "40mm", delivery_length: "4000", quantity: 1, unit: "EA", total_price: 15420 },
|
||
],
|
||
// 모터 & 제어기: 품목명, 유형, 사양, 수량, 작업
|
||
motor: [
|
||
{ id: "m1", item_name: "직류 모터", type: "220V", spec: "1/2HP", quantity: 1, unit: "EA", total_price: 250000 },
|
||
{ id: "m2", item_name: "제어기", type: "디지털", spec: "", quantity: 1, unit: "EA", total_price: 150000 },
|
||
],
|
||
// 부자재: 품목명, 규격, 납품길이, 수량, 작업
|
||
accessory: [
|
||
{ id: "a1", item_name: "각파이프 25mm", spec: "25*25*2.0t", delivery_length: "4000", quantity: 2, unit: "EA", total_price: 17000 },
|
||
{ id: "a2", item_name: "플랫바 20mm", spec: "20*3.0t", delivery_length: "4000", quantity: 1, unit: "EA", total_price: 4200 },
|
||
],
|
||
};
|
||
|
||
import type { LocationItem } from "./QuoteRegistrationV2";
|
||
import type { FinishedGoods } from "./actions";
|
||
|
||
// =============================================================================
|
||
// 상수
|
||
// =============================================================================
|
||
|
||
// 가이드레일 설치 유형
|
||
const GUIDE_RAIL_TYPES = [
|
||
{ value: "wall", label: "벽면형" },
|
||
{ value: "floor", label: "측면형" },
|
||
];
|
||
|
||
// 모터 전원
|
||
const MOTOR_POWERS = [
|
||
{ value: "single", label: "단상(220V)" },
|
||
{ value: "three", label: "삼상(380V)" },
|
||
];
|
||
|
||
// 연동제어기
|
||
const CONTROLLERS = [
|
||
{ value: "basic", label: "단독" },
|
||
{ value: "smart", label: "연동" },
|
||
{ value: "premium", label: "매립형-뒷박스포함" },
|
||
];
|
||
|
||
// 탭 정의 (6개)
|
||
const DETAIL_TABS = [
|
||
{ value: "body", label: "본체 (스크린/슬랫)" },
|
||
{ value: "guide-rail", label: "절곡품 - 가이드레일" },
|
||
{ value: "case", label: "절곡품 - 케이스" },
|
||
{ value: "bottom", label: "절곡품 - 하단마감재" },
|
||
{ value: "motor", label: "모터 & 제어기" },
|
||
{ value: "accessory", label: "부자재" },
|
||
];
|
||
|
||
// =============================================================================
|
||
// Props
|
||
// =============================================================================
|
||
|
||
interface LocationDetailPanelProps {
|
||
location: LocationItem | null;
|
||
onUpdateLocation: (locationId: string, updates: Partial<LocationItem>) => void;
|
||
finishedGoods: FinishedGoods[];
|
||
disabled?: boolean;
|
||
}
|
||
|
||
// =============================================================================
|
||
// 컴포넌트
|
||
// =============================================================================
|
||
|
||
export function LocationDetailPanel({
|
||
location,
|
||
onUpdateLocation,
|
||
finishedGoods,
|
||
disabled = false,
|
||
}: LocationDetailPanelProps) {
|
||
// ---------------------------------------------------------------------------
|
||
// 상태
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const [activeTab, setActiveTab] = useState("body");
|
||
const [itemSearchOpen, setItemSearchOpen] = useState(false);
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 계산된 값
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// 제품 정보
|
||
const product = useMemo(() => {
|
||
if (!location?.productCode) return null;
|
||
return finishedGoods.find((fg) => fg.item_code === location.productCode);
|
||
}, [location?.productCode, finishedGoods]);
|
||
|
||
// BOM 아이템을 탭별로 분류 (목데이터 사용)
|
||
const bomItemsByTab = useMemo(() => {
|
||
// bomResult가 없으면 목데이터 사용
|
||
if (!location?.bomResult?.items) {
|
||
return MOCK_BOM_ITEMS;
|
||
}
|
||
|
||
const items = location.bomResult.items;
|
||
const result: Record<string, typeof items> = {
|
||
body: [],
|
||
"guide-rail": [],
|
||
case: [],
|
||
bottom: [],
|
||
};
|
||
|
||
items.forEach((item) => {
|
||
const processGroup = item.process_group?.toLowerCase() || "";
|
||
|
||
if (processGroup.includes("본체") || processGroup.includes("스크린") || processGroup.includes("슬랫")) {
|
||
result.body.push(item);
|
||
} else if (processGroup.includes("가이드") || processGroup.includes("레일")) {
|
||
result["guide-rail"].push(item);
|
||
} else if (processGroup.includes("케이스")) {
|
||
result.case.push(item);
|
||
} else if (processGroup.includes("하단") || processGroup.includes("마감")) {
|
||
result.bottom.push(item);
|
||
} else {
|
||
// 기타 항목은 본체에 포함
|
||
result.body.push(item);
|
||
}
|
||
});
|
||
|
||
return result;
|
||
}, [location?.bomResult?.items]);
|
||
|
||
// 탭별 소계
|
||
const tabSubtotals = useMemo(() => {
|
||
const result: Record<string, number> = {};
|
||
Object.entries(bomItemsByTab).forEach(([tab, items]) => {
|
||
result[tab] = items.reduce((sum, item) => sum + (item.total_price || 0), 0);
|
||
});
|
||
return result;
|
||
}, [bomItemsByTab]);
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 핸들러
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const handleFieldChange = (field: keyof LocationItem, value: string | number) => {
|
||
if (!location || disabled) return;
|
||
onUpdateLocation(location.id, { [field]: value });
|
||
};
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 렌더링: 빈 상태
|
||
// ---------------------------------------------------------------------------
|
||
|
||
if (!location) {
|
||
return (
|
||
<div className="flex flex-col items-center justify-center h-full bg-gray-50 text-gray-500">
|
||
<Package className="h-12 w-12 mb-4 text-gray-300" />
|
||
<p className="text-lg font-medium">개소를 선택해주세요</p>
|
||
<p className="text-sm">왼쪽 목록에서 개소를 선택하면 상세 정보가 표시됩니다</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 렌더링: 상세 정보
|
||
// ---------------------------------------------------------------------------
|
||
|
||
return (
|
||
<div className="flex flex-col h-full overflow-hidden">
|
||
{/* 헤더 */}
|
||
<div className="bg-white px-4 py-3 border-b">
|
||
<div className="flex items-center justify-between">
|
||
<h3 className="text-lg font-bold">
|
||
{location.floor} / {location.code}
|
||
</h3>
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-sm text-gray-500">제품명:</span>
|
||
<Badge variant="outline" className="bg-blue-50 text-blue-700 border-blue-200">
|
||
{location.productCode}
|
||
</Badge>
|
||
{location.bomResult && (
|
||
<Badge variant="default" className="bg-green-600">
|
||
산출완료
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 제품 정보 */}
|
||
<div className="bg-gray-50 px-4 py-3 border-b space-y-3">
|
||
{/* 오픈사이즈 */}
|
||
<div className="flex items-center gap-4">
|
||
<span className="text-sm text-gray-600 w-20">오픈사이즈</span>
|
||
<div className="flex items-center gap-2">
|
||
<Input
|
||
type="number"
|
||
value={location.openWidth}
|
||
onChange={(e) => handleFieldChange("openWidth", parseFloat(e.target.value) || 0)}
|
||
disabled={disabled}
|
||
className="w-24 h-8 text-center font-bold"
|
||
/>
|
||
<span className="text-gray-400">×</span>
|
||
<Input
|
||
type="number"
|
||
value={location.openHeight}
|
||
onChange={(e) => handleFieldChange("openHeight", parseFloat(e.target.value) || 0)}
|
||
disabled={disabled}
|
||
className="w-24 h-8 text-center font-bold"
|
||
/>
|
||
{!disabled && (
|
||
<Badge variant="secondary" className="text-xs">수정</Badge>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 제작사이즈, 산출중량, 산출면적, 수량 */}
|
||
<div className="grid grid-cols-4 gap-4 text-sm">
|
||
<div>
|
||
<span className="text-gray-500">제작사이즈</span>
|
||
<p className="font-semibold">
|
||
{location.manufactureWidth || location.openWidth + 280} × {location.manufactureHeight || location.openHeight + 280}
|
||
</p>
|
||
</div>
|
||
<div>
|
||
<span className="text-gray-500">산출중량</span>
|
||
<p className="font-semibold">{location.weight?.toFixed(1) || "-"} <span className="text-xs text-gray-400">kg</span></p>
|
||
</div>
|
||
<div>
|
||
<span className="text-gray-500">산출면적</span>
|
||
<p className="font-semibold">{location.area?.toFixed(1) || "-"} <span className="text-xs text-gray-400">m²</span></p>
|
||
</div>
|
||
<div>
|
||
<span className="text-gray-500">수량</span>
|
||
<Input
|
||
type="number"
|
||
min="1"
|
||
value={location.quantity}
|
||
onChange={(e) => handleFieldChange("quantity", parseInt(e.target.value) || 1)}
|
||
disabled={disabled}
|
||
className="w-24 h-7 text-center font-semibold"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 필수 설정 (읽기 전용) */}
|
||
<div className="bg-white px-4 py-3 border-b">
|
||
<h4 className="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-1">
|
||
<Settings className="h-4 w-4" />
|
||
필수 설정
|
||
</h4>
|
||
<div className="grid grid-cols-3 gap-3">
|
||
<div>
|
||
<label className="text-xs text-gray-500 flex items-center gap-1 mb-1">
|
||
🔧 가이드레일
|
||
</label>
|
||
<Badge variant="outline" className="bg-gray-50 text-gray-700 border-gray-300 px-3 py-1.5">
|
||
{GUIDE_RAIL_TYPES.find(t => t.value === location.guideRailType)?.label || location.guideRailType}
|
||
</Badge>
|
||
</div>
|
||
<div>
|
||
<label className="text-xs text-gray-500 flex items-center gap-1 mb-1">
|
||
⚡ 전원
|
||
</label>
|
||
<Badge variant="outline" className="bg-gray-50 text-gray-700 border-gray-300 px-3 py-1.5">
|
||
{MOTOR_POWERS.find(p => p.value === location.motorPower)?.label || location.motorPower}
|
||
</Badge>
|
||
</div>
|
||
<div>
|
||
<label className="text-xs text-gray-500 flex items-center gap-1 mb-1">
|
||
📦 제어기
|
||
</label>
|
||
<Badge variant="outline" className="bg-gray-50 text-gray-700 border-gray-300 px-3 py-1.5">
|
||
{CONTROLLERS.find(c => c.value === location.controller)?.label || location.controller}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 탭 및 품목 테이블 */}
|
||
<div className="flex-1 overflow-hidden flex flex-col">
|
||
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex flex-col h-full">
|
||
{/* 탭 목록 - 스크롤 가능 */}
|
||
<div className="border-b bg-white overflow-x-auto">
|
||
<TabsList className="w-max min-w-full justify-start rounded-none bg-transparent h-auto p-0">
|
||
{DETAIL_TABS.map((tab) => (
|
||
<TabsTrigger
|
||
key={tab.value}
|
||
value={tab.value}
|
||
className="rounded-none border-b-2 border-transparent data-[state=active]:border-blue-500 data-[state=active]:bg-blue-50 data-[state=active]:text-blue-700 px-4 py-2 text-sm whitespace-nowrap"
|
||
>
|
||
{tab.label}
|
||
</TabsTrigger>
|
||
))}
|
||
</TabsList>
|
||
</div>
|
||
|
||
{/* 본체 (스크린/슬랫) 탭 */}
|
||
<TabsContent value="body" className="flex-1 overflow-auto m-0 p-0">
|
||
<div className="bg-amber-50 border border-amber-200 rounded-lg m-4">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow className="bg-amber-100/50">
|
||
<TableHead className="font-semibold">품목명</TableHead>
|
||
<TableHead className="text-center font-semibold">제작사이즈</TableHead>
|
||
<TableHead className="text-center font-semibold w-24">수량</TableHead>
|
||
<TableHead className="text-center font-semibold w-20">작업</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{bomItemsByTab.body.map((item: any) => (
|
||
<TableRow key={item.id} className="bg-white">
|
||
<TableCell className="font-medium">{item.item_name}</TableCell>
|
||
<TableCell className="text-center text-gray-600">{item.manufacture_size || "-"}</TableCell>
|
||
<TableCell className="text-center">
|
||
<Input
|
||
type="number"
|
||
defaultValue={item.quantity}
|
||
className="w-16 h-8 text-center"
|
||
min={1}
|
||
readOnly={disabled}
|
||
/>
|
||
</TableCell>
|
||
<TableCell className="text-center">
|
||
<Button variant="ghost" size="sm" className="text-red-500 hover:text-red-700 hover:bg-red-50 p-1" disabled={disabled}>
|
||
<Trash2 className="h-4 w-4" />
|
||
</Button>
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
|
||
{/* 품목 추가 버튼 + 안내 */}
|
||
<div className="p-3 flex items-center justify-between border-t border-amber-200">
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="bg-blue-100 border-blue-300 text-blue-700 hover:bg-blue-200"
|
||
onClick={() => setItemSearchOpen(true)}
|
||
disabled={disabled}
|
||
>
|
||
<Plus className="h-4 w-4 mr-1" />
|
||
품목 추가
|
||
</Button>
|
||
<span className="text-sm text-gray-500 flex items-center gap-1">
|
||
💡 금액은 아래 견적금액요약에서 확인하세요
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</TabsContent>
|
||
|
||
{/* 절곡품 - 가이드레일, 케이스, 하단마감재 탭 */}
|
||
{["guide-rail", "case", "bottom"].map((tabValue) => (
|
||
<TabsContent key={tabValue} value={tabValue} className="flex-1 overflow-auto m-0 p-0">
|
||
<div className="bg-amber-50 border border-amber-200 rounded-lg m-4">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow className="bg-amber-100/50">
|
||
<TableHead className="font-semibold">품목명</TableHead>
|
||
<TableHead className="text-center font-semibold">재질</TableHead>
|
||
<TableHead className="text-center font-semibold">규격</TableHead>
|
||
<TableHead className="text-center font-semibold w-28">납품길이</TableHead>
|
||
<TableHead className="text-center font-semibold w-24">수량</TableHead>
|
||
<TableHead className="text-center font-semibold w-20">작업</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{bomItemsByTab[tabValue]?.map((item: any) => (
|
||
<TableRow key={item.id} className="bg-white">
|
||
<TableCell className="font-medium">{item.item_name}</TableCell>
|
||
<TableCell className="text-center text-gray-600">{item.material || "-"}</TableCell>
|
||
<TableCell className="text-center text-gray-600">{item.spec || "-"}</TableCell>
|
||
<TableCell className="text-center">
|
||
<Select defaultValue={item.delivery_length} disabled={disabled}>
|
||
<SelectTrigger className="w-24 h-8">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{DELIVERY_LENGTH_OPTIONS.map((opt) => (
|
||
<SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</TableCell>
|
||
<TableCell className="text-center">
|
||
<Input
|
||
type="number"
|
||
defaultValue={item.quantity}
|
||
className="w-16 h-8 text-center"
|
||
min={1}
|
||
readOnly={disabled}
|
||
/>
|
||
</TableCell>
|
||
<TableCell className="text-center">
|
||
<Button variant="ghost" size="sm" className="text-red-500 hover:text-red-700 hover:bg-red-50 p-1" disabled={disabled}>
|
||
<Trash2 className="h-4 w-4" />
|
||
</Button>
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
|
||
{/* 품목 추가 버튼 + 안내 */}
|
||
<div className="p-3 flex items-center justify-between border-t border-amber-200">
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="bg-blue-100 border-blue-300 text-blue-700 hover:bg-blue-200"
|
||
onClick={() => setItemSearchOpen(true)}
|
||
disabled={disabled}
|
||
>
|
||
<Plus className="h-4 w-4 mr-1" />
|
||
품목 추가
|
||
</Button>
|
||
<span className="text-sm text-gray-500 flex items-center gap-1">
|
||
💡 금액은 아래 견적금액요약에서 확인하세요
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</TabsContent>
|
||
))}
|
||
|
||
{/* 모터 & 제어기 탭 */}
|
||
<TabsContent value="motor" className="flex-1 overflow-auto m-0 p-0">
|
||
<div className="bg-amber-50 border border-amber-200 rounded-lg m-4">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow className="bg-amber-100/50">
|
||
<TableHead className="font-semibold">품목명</TableHead>
|
||
<TableHead className="text-center font-semibold">유형</TableHead>
|
||
<TableHead className="text-center font-semibold">사양</TableHead>
|
||
<TableHead className="text-center font-semibold w-24">수량</TableHead>
|
||
<TableHead className="text-center font-semibold w-20">작업</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{bomItemsByTab.motor?.map((item: any) => (
|
||
<TableRow key={item.id} className="bg-white">
|
||
<TableCell className="font-medium">{item.item_name}</TableCell>
|
||
<TableCell className="text-center text-gray-600">{item.type || "-"}</TableCell>
|
||
<TableCell className="text-center text-gray-600">{item.spec || "-"}</TableCell>
|
||
<TableCell className="text-center">
|
||
<Input
|
||
type="number"
|
||
defaultValue={item.quantity}
|
||
className="w-16 h-8 text-center"
|
||
min={1}
|
||
readOnly={disabled}
|
||
/>
|
||
</TableCell>
|
||
<TableCell className="text-center">
|
||
<Button variant="ghost" size="sm" className="text-red-500 hover:text-red-700 hover:bg-red-50 p-1" disabled={disabled}>
|
||
<Trash2 className="h-4 w-4" />
|
||
</Button>
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
|
||
{/* 품목 추가 버튼 + 안내 */}
|
||
<div className="p-3 flex items-center justify-between border-t border-amber-200">
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="bg-blue-100 border-blue-300 text-blue-700 hover:bg-blue-200"
|
||
onClick={() => setItemSearchOpen(true)}
|
||
disabled={disabled}
|
||
>
|
||
<Plus className="h-4 w-4 mr-1" />
|
||
품목 추가
|
||
</Button>
|
||
<span className="text-sm text-gray-500 flex items-center gap-1">
|
||
💡 금액은 아래 견적금액요약에서 확인하세요
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</TabsContent>
|
||
|
||
{/* 부자재 탭 */}
|
||
<TabsContent value="accessory" className="flex-1 overflow-auto m-0 p-0">
|
||
<div className="bg-amber-50 border border-amber-200 rounded-lg m-4">
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow className="bg-amber-100/50">
|
||
<TableHead className="font-semibold">품목명</TableHead>
|
||
<TableHead className="text-center font-semibold">규격</TableHead>
|
||
<TableHead className="text-center font-semibold w-28">납품길이</TableHead>
|
||
<TableHead className="text-center font-semibold w-24">수량</TableHead>
|
||
<TableHead className="text-center font-semibold w-20">작업</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{bomItemsByTab.accessory?.map((item: any) => (
|
||
<TableRow key={item.id} className="bg-white">
|
||
<TableCell className="font-medium">{item.item_name}</TableCell>
|
||
<TableCell className="text-center text-gray-600">{item.spec || "-"}</TableCell>
|
||
<TableCell className="text-center">
|
||
<Select defaultValue={item.delivery_length} disabled={disabled}>
|
||
<SelectTrigger className="w-24 h-8">
|
||
<SelectValue />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{DELIVERY_LENGTH_OPTIONS.map((opt) => (
|
||
<SelectItem key={opt.value} value={opt.value}>{opt.label}</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</TableCell>
|
||
<TableCell className="text-center">
|
||
<Input
|
||
type="number"
|
||
defaultValue={item.quantity}
|
||
className="w-16 h-8 text-center"
|
||
min={1}
|
||
readOnly={disabled}
|
||
/>
|
||
</TableCell>
|
||
<TableCell className="text-center">
|
||
<Button variant="ghost" size="sm" className="text-red-500 hover:text-red-700 hover:bg-red-50 p-1" disabled={disabled}>
|
||
<Trash2 className="h-4 w-4" />
|
||
</Button>
|
||
</TableCell>
|
||
</TableRow>
|
||
))}
|
||
</TableBody>
|
||
</Table>
|
||
|
||
{/* 품목 추가 버튼 + 안내 */}
|
||
<div className="p-3 flex items-center justify-between border-t border-amber-200">
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
className="bg-blue-100 border-blue-300 text-blue-700 hover:bg-blue-200"
|
||
onClick={() => setItemSearchOpen(true)}
|
||
disabled={disabled}
|
||
>
|
||
<Plus className="h-4 w-4 mr-1" />
|
||
품목 추가
|
||
</Button>
|
||
<span className="text-sm text-gray-500 flex items-center gap-1">
|
||
💡 금액은 아래 견적금액요약에서 확인하세요
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</TabsContent>
|
||
</Tabs>
|
||
</div>
|
||
|
||
{/* 금액 안내 */}
|
||
{!location.bomResult && (
|
||
<div className="bg-blue-50 px-4 py-2 border-t border-blue-200 text-center text-sm text-blue-700">
|
||
💡 금액은 아래 견적금액요약에서 확인하세요
|
||
</div>
|
||
)}
|
||
|
||
{/* 품목 검색 모달 */}
|
||
<ItemSearchModal
|
||
open={itemSearchOpen}
|
||
onOpenChange={setItemSearchOpen}
|
||
onSelectItem={(item) => {
|
||
console.log(`[테스트] 품목 선택: ${item.code} - ${item.name} (탭: ${activeTab})`);
|
||
}}
|
||
tabLabel={DETAIL_TABS.find((t) => t.value === activeTab)?.label}
|
||
/>
|
||
</div>
|
||
);
|
||
} |