fix(WEB): 견적 개소 목록 테이블 확장 및 견적서 미리보기 개선

LocationListPanel:
- 개소 목록 테이블 컬럼 확장 (층, 부호, 사이즈, 제품, 수량)
- 수정/삭제 버튼 추가
- 테이블 헤더 스타일 변경 (어두운 배경)
- 선택 행 스타일 변경 (blue → orange)

QuotePreviewContent:
- 공급자 정보 테이블 확장 (대표자, FAX, 종목 추가)
- 품종 → 종류 라벨 변경
- 소계/할인율 행 레이아웃 개선

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-27 20:10:48 +09:00
parent a48937ae52
commit 9964ccbc1f
2 changed files with 88 additions and 49 deletions

View File

@@ -9,7 +9,7 @@
"use client";
import { useState, useCallback } from "react";
import { Plus, Upload, Download } from "lucide-react";
import { Plus, Upload, Download, Pencil, Trash2 } from "lucide-react";
import { toast } from "sonner";
import { Button } from "../ui/button";
@@ -455,19 +455,23 @@ export function LocationListPanel({
</div>
</div>
{/* 개소 목록 테이블 (간소화: 부호, 사이즈만) */}
{/* 개소 목록 테이블 */}
<div className="flex-1 overflow-auto">
<Table>
<TableHeader>
<TableRow className="bg-gray-50">
<TableHead className="w-[100px] text-center"></TableHead>
<TableHead className="text-center"></TableHead>
<TableRow className="bg-gray-800 text-white">
<TableHead className="text-center text-white"></TableHead>
<TableHead className="text-center text-white"></TableHead>
<TableHead className="text-center text-white"></TableHead>
<TableHead className="text-center text-white"></TableHead>
<TableHead className="text-center text-white"></TableHead>
{!disabled && <TableHead className="w-[80px] text-center text-white"></TableHead>}
</TableRow>
</TableHeader>
<TableBody>
{locations.length === 0 ? (
<TableRow>
<TableCell colSpan={2} className="text-center text-gray-500 py-8">
<TableCell colSpan={disabled ? 5 : 6} className="text-center text-gray-500 py-8">
</TableCell>
</TableRow>
@@ -476,18 +480,41 @@ export function LocationListPanel({
<TableRow
key={loc.id}
className={`cursor-pointer hover:bg-blue-50 ${
selectedLocationId === loc.id ? "bg-blue-100 border-l-4 border-l-blue-500" : ""
selectedLocationId === loc.id ? "bg-orange-100 border-l-4 border-l-orange-500" : ""
}`}
onClick={() => onSelectLocation(loc.id)}
>
<TableCell className="text-center">
<div className="font-medium text-blue-700">{loc.code}</div>
<div className="text-xs text-gray-500">{loc.productCode}</div>
</TableCell>
<TableCell className="text-center">
<div className="font-medium">{loc.openWidth}X{loc.openHeight}</div>
<div className="text-xs text-gray-500">{loc.floor} · {loc.quantity}</div>
</TableCell>
<TableCell className="text-center">{loc.floor}</TableCell>
<TableCell className="text-center font-medium">{loc.code}</TableCell>
<TableCell className="text-center">{loc.openWidth}X{loc.openHeight}</TableCell>
<TableCell className="text-center">{loc.productCode}</TableCell>
<TableCell className="text-center">{loc.quantity}</TableCell>
{!disabled && (
<TableCell className="text-center">
<div className="flex items-center justify-center gap-1">
<button
onClick={(e) => {
e.stopPropagation();
onSelectLocation(loc.id);
}}
className="p-1 hover:bg-gray-200 rounded"
title="수정"
>
<Pencil className="h-4 w-4 text-gray-600" />
</button>
<button
onClick={(e) => {
e.stopPropagation();
setDeleteTarget(loc.id);
}}
className="p-1 hover:bg-red-100 rounded"
title="삭제"
>
<Trash2 className="h-4 w-4 text-red-500" />
</button>
</div>
</TableCell>
)}
</TableRow>
))
)}

View File

@@ -8,6 +8,7 @@
* - calculation: 산출내역서
*/
import React from 'react';
import type { QuoteFormDataV2 } from './QuoteRegistrationV2';
// 양식 타입
@@ -138,23 +139,29 @@ export function QuotePreviewContent({
<tbody>
<tr>
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1 w-20"></td>
<td className="border-b border-gray-300 px-2 py-1"></td>
<td colSpan={3} className="border-b border-gray-300 px-2 py-1"></td>
</tr>
<tr>
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1"></td>
<td className="border-b border-gray-300 px-2 py-1">123-12-12345</td>
<td className="border-b border-r border-gray-300 px-2 py-1">123-12-12345</td>
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1 w-16"></td>
<td className="border-b border-gray-300 px-2 py-1"></td>
</tr>
<tr>
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1"></td>
<td className="border-b border-gray-300 px-2 py-1"></td>
<td colSpan={3} className="border-b border-gray-300 px-2 py-1"></td>
</tr>
<tr>
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1"></td>
<td className="border-b border-gray-300 px-2 py-1"></td>
<td className="border-b border-r border-gray-300 px-2 py-1"></td>
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1"></td>
<td className="border-b border-gray-300 px-2 py-1">, </td>
</tr>
<tr>
<td className="border-r border-gray-300 bg-gray-50 px-2 py-1">TEL</td>
<td className="px-2 py-1">031-123-1234</td>
<td className="border-r border-gray-300 px-2 py-1">031-123-1234</td>
<td className="border-r border-gray-300 bg-gray-50 px-2 py-1">FAX</td>
<td className="px-2 py-1">02-1234-1234</td>
</tr>
</tbody>
</table>
@@ -170,7 +177,7 @@ export function QuotePreviewContent({
<thead>
<tr className="bg-gray-100 border-b border-gray-400">
<th className="border-r border-gray-300 px-2 py-1">No.</th>
<th className="border-r border-gray-300 px-2 py-1"></th>
<th className="border-r border-gray-300 px-2 py-1"></th>
<th className="border-r border-gray-300 px-2 py-1"></th>
<th className="border-r border-gray-300 px-2 py-1"></th>
<th className="border-r border-gray-300 px-2 py-1" colSpan={2}></th>
@@ -215,55 +222,56 @@ export function QuotePreviewContent({
<tfoot>
{/* 소계 */}
<tr className="border-b border-gray-300">
<td colSpan={6} className="border-r border-gray-300"></td>
<td colSpan={3} className="border-r border-gray-300 px-2 py-1 text-right bg-gray-50">
{vatIncluded ? '공급가액 합계' : '소계'}
<td colSpan={6} className="border-r border-gray-300 px-2 py-1 text-center bg-gray-50">
</td>
<td className="border-r border-gray-300 px-2 py-1 text-center">
{quoteData.locations.reduce((sum, loc) => sum + (loc.quantity || 0), 0)}
</td>
<td className="border-r border-gray-300 px-2 py-1"></td>
<td className="border-r border-gray-300 px-2 py-1"></td>
<td className="px-2 py-1 text-right">{subtotal.toLocaleString()}</td>
</tr>
{/* 할인율 */}
<tr className="border-b border-gray-300">
<td colSpan={6} className="border-r border-gray-300"></td>
<td colSpan={3} className="border-r border-gray-300 px-2 py-1 text-right bg-gray-50">
<td colSpan={9} className="border-r border-gray-300 px-2 py-1 text-center bg-gray-50">
</td>
<td className="px-2 py-1 text-right text-orange-600">
{hasDiscount ? `${discountRate.toFixed(1)}%` : '0.0%'}
<td className="px-2 py-1 text-right">
{hasDiscount ? `${discountRate.toFixed(1)} %` : '0.0 %'}
</td>
</tr>
{/* 할인금액 */}
<tr className="border-b border-gray-300">
<td colSpan={6} className="border-r border-gray-300"></td>
<td colSpan={3} className="border-r border-gray-300 px-2 py-1 text-right bg-gray-50">
<td colSpan={9} className="border-r border-gray-300 px-2 py-1 text-center bg-gray-50">
</td>
<td className="px-2 py-1 text-right text-orange-600">
<td className="px-2 py-1 text-right">
{hasDiscount ? `-${discountAmount.toLocaleString()}` : '0'}
</td>
</tr>
{/* 할인 후 금액 */}
<tr className="border-b border-gray-300">
<td colSpan={9} className="border-r border-gray-300 px-2 py-1 text-center bg-gray-50">
</td>
<td className="px-2 py-1 text-right">{afterDiscount.toLocaleString()}</td>
</tr>
{/* 부가세 포함일 때 추가 행들 */}
{vatIncluded && (
<>
<tr className="border-b border-gray-300">
<td colSpan={6} className="border-r border-gray-300"></td>
<td colSpan={3} className="border-r border-gray-300 px-2 py-1 text-right bg-gray-50">
</td>
<td className="px-2 py-1 text-right">{afterDiscount.toLocaleString()}</td>
</tr>
<tr className="border-b border-gray-300">
<td colSpan={6} className="border-r border-gray-300"></td>
<td colSpan={3} className="border-r border-gray-300 px-2 py-1 text-right bg-gray-50">
<td colSpan={9} className="border-r border-gray-300 px-2 py-1 text-center bg-gray-50">
</td>
<td className="px-2 py-1 text-right">{vat.toLocaleString()}</td>
</tr>
<tr>
<td colSpan={6} className="border-r border-gray-300"></td>
<td colSpan={3} className="border-r border-gray-300 px-2 py-1 text-right bg-gray-50 font-semibold">
<td colSpan={9} className="border-r border-gray-300 px-2 py-1 text-center bg-gray-50 font-semibold">
</td>
<td className="px-2 py-1 text-right font-semibold">{grandTotal.toLocaleString()}</td>
@@ -299,14 +307,14 @@ export function QuotePreviewContent({
<th className="border-r border-gray-300 px-2 py-1"></th>
<th className="border-r border-gray-300 px-2 py-1"></th>
<th className="border-r border-gray-300 px-2 py-1"></th>
<th className="px-2 py-1"></th>
<th className="px-2 py-1"></th>
</tr>
</thead>
<tbody>
{quoteData.locations.map((loc) => (
<>
<React.Fragment key={loc.id}>
{/* 각 개소별 품목 상세 */}
<tr key={`${loc.id}-main`} className="border-b border-gray-300">
<tr className="border-b border-gray-300">
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.symbol || '-'}</td>
<td className="border-r border-gray-300 px-2 py-1"></td>
<td className="border-r border-gray-300 px-2 py-1 text-center"></td>
@@ -319,14 +327,18 @@ export function QuotePreviewContent({
{(loc.totalPrice || 0).toLocaleString()}
</td>
</tr>
</>
</React.Fragment>
))}
{/* 소계 */}
<tr className="bg-gray-50">
<td colSpan={3} className="border-r border-gray-300"></td>
<td colSpan={3} className="border-r border-gray-300 px-2 py-1 text-right font-semibold">
<tr className="bg-gray-50 border-t border-gray-400">
<td colSpan={3} className="border-r border-gray-300 px-2 py-1 text-center font-semibold">
</td>
<td className="border-r border-gray-300 px-2 py-1 text-center">
{quoteData.locations.reduce((sum, loc) => sum + (loc.quantity || 0), 0)}
</td>
<td className="border-r border-gray-300 px-2 py-1"></td>
<td className="border-r border-gray-300 px-2 py-1"></td>
<td className="px-2 py-1 text-right font-semibold">{subtotal.toLocaleString()}</td>
</tr>
</tbody>