Files
sam-react-prod/src/components/quotes/QuoteTransactionModal.tsx
유병철 a38996b751 refactor(WEB): V2 파일 통합, store 구조 정리 및 대시보드 개선
- V2 컴포넌트를 원본에 통합 후 V2 파일 삭제 (InspectionModal, BillDetail, ContractDocumentModal, LaborDetailClient, PricingDetailClient, QuoteRegistration)
- store → stores 디렉토리 이동 및 favoritesStore 추가
- dashboard_type3~5 추가 및 기존 대시보드 차트/훅 분리
- Sidebar 리팩토링 및 HeaderFavoritesBar 추가
- DashboardSwitcher 컴포넌트 추가
- 백업 파일(.v1-backup) 및 불필요 코드 정리
- InspectionPreviewModal 레이아웃 개선

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 15:09:51 +09:00

324 lines
15 KiB
TypeScript

'use client';
/**
* 거래명세서 보기 모달
*
* 견적 데이터를 거래명세서 양식으로 표시
* - 결재라인
* - 수요자/공급자 정보
* - 품목내역
* - 금액 계산 (소계, 할인율, 할인금액, 할인 후 금액, 부가세, 합계)
* - 증명 문구 + 인감
*/
import { DocumentViewer } from '@/components/document-system';
import type { QuoteFormDataV2 } from './QuoteRegistration';
interface QuoteTransactionModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
quoteData: QuoteFormDataV2 | null;
/** 할인율 (%) */
discountRate?: number;
/** 할인금액 (원) */
discountAmount?: number;
}
export function QuoteTransactionModal({
open,
onOpenChange,
quoteData,
discountRate = 0,
discountAmount = 0,
}: QuoteTransactionModalProps) {
if (!quoteData) return null;
// locations 배열 (undefined 방어)
const locations = quoteData.locations || [];
// 소계 (할인 전 금액)
const subtotal = locations.reduce((sum, loc) => sum + (loc.totalPrice || 0), 0);
// 할인 적용 후 금액
const afterDiscount = subtotal - discountAmount;
// 부가세
const vat = Math.round(afterDiscount * 0.1);
// 부가세 포함 여부
const vatIncluded = quoteData.vatType === 'included';
// 총 금액 (부가세 포함 여부에 따라)
const grandTotal = vatIncluded ? afterDiscount + vat : afterDiscount;
// 할인 적용 여부
const hasDiscount = discountAmount > 0;
// 오늘 날짜
const today = new Date().toISOString().split('T')[0];
return (
<DocumentViewer
title="거래명세서"
preset="inspection"
open={open}
onOpenChange={onOpenChange}
>
<div className="max-w-[210mm] mx-auto bg-white p-6 text-sm">
{/* 헤더: 제목 + 결재란 */}
<div className="flex justify-between items-start mb-4">
{/* 왼쪽: 제목 */}
<div className="flex-1">
<h1 className="text-2xl font-bold text-center tracking-[0.5em] mb-1">
</h1>
<p className="text-xs text-gray-500 text-center">
: {quoteData.quoteNumber || 'ABC123'} | : {quoteData.registrationDate || today}
</p>
</div>
{/* 오른쪽: 결재란 */}
<div className="border border-gray-400">
<table className="text-xs">
<thead>
<tr className="bg-gray-100">
<th className="border-r border-gray-400 px-3 py-1"></th>
<th className="border-r border-gray-400 px-3 py-1"></th>
<th className="border-r border-gray-400 px-3 py-1"></th>
<th className="px-3 py-1"></th>
</tr>
</thead>
<tbody>
<tr>
<td className="border-t border-r border-gray-400 px-3 py-3 text-center"></td>
<td className="border-t border-r border-gray-400 px-3 py-3 text-center"></td>
<td className="border-t border-r border-gray-400 px-3 py-3 text-center"></td>
<td className="border-t border-gray-400 px-3 py-3 text-center"></td>
</tr>
<tr>
<td className="border-t border-r border-gray-400 px-2 py-1 text-center text-[10px] text-gray-500"></td>
<td className="border-t border-r border-gray-400 px-2 py-1 text-center text-[10px] text-gray-500"></td>
<td className="border-t border-r border-gray-400 px-2 py-1 text-center text-[10px] text-gray-500"></td>
<td className="border-t border-gray-400 px-2 py-1 text-center text-[10px] text-gray-500"></td>
</tr>
</tbody>
</table>
</div>
</div>
{/* 수요자 / 공급자 정보 (좌우 배치) */}
<div className="grid grid-cols-2 gap-4 mb-4">
{/* 수요자 */}
<div className="border border-gray-400">
<div className="bg-gray-200 px-2 py-1 font-semibold text-center border-b border-gray-400">
</div>
<table className="w-full text-xs">
<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">{quoteData.clientName || '-'}</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">{locations[0]?.productCode || '-'}</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">{quoteData.siteName || '-'}</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">{quoteData.manager || '-'}</td>
</tr>
<tr>
<td className="border-r border-gray-300 bg-gray-50 px-2 py-1"></td>
<td className="px-2 py-1">{quoteData.contact || '-'}</td>
</tr>
</tbody>
</table>
</div>
{/* 공급자 */}
<div className="border border-gray-400">
<div className="bg-gray-200 px-2 py-1 font-semibold text-center border-b border-gray-400">
</div>
<table className="w-full text-xs">
<tbody>
<tr>
<td className="border-b border-r border-gray-300 bg-gray-50 px-2 py-1 w-20"></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-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 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-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="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>
</div>
</div>
{/* 내역 테이블 */}
<div className="border border-gray-400 mb-4">
<div className="bg-gray-800 text-white px-2 py-1 font-semibold text-center">
</div>
<table className="w-full text-xs">
<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" colSpan={2}></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="px-2 py-1"></th>
</tr>
<tr className="bg-gray-50 border-b border-gray-300 text-[10px] text-gray-500">
<th className="border-r border-gray-300 px-2 py-0.5"></th>
<th className="border-r border-gray-300 px-2 py-0.5"></th>
<th className="border-r border-gray-300 px-2 py-0.5"></th>
<th className="border-r border-gray-300 px-2 py-0.5"></th>
<th className="border-r border-gray-300 px-1 py-0.5"></th>
<th className="border-r border-gray-300 px-1 py-0.5"></th>
<th className="border-r border-gray-300 px-2 py-0.5"></th>
<th className="border-r border-gray-300 px-2 py-0.5"></th>
<th className="border-r border-gray-300 px-2 py-0.5"></th>
<th className="px-2 py-0.5"></th>
</tr>
</thead>
<tbody>
{locations.length > 0 ? (
locations.map((loc, index) => (
<tr key={loc.id} className="border-b border-gray-300">
<td className="border-r border-gray-300 px-2 py-1 text-center">{index + 1}</td>
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.floor || '-'}</td>
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.code || '-'}</td>
<td className="border-r border-gray-300 px-2 py-1">{loc.productCode || '-'}</td>
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.openWidth || 0}</td>
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.openHeight || 0}</td>
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.quantity || 1}</td>
<td className="border-r border-gray-300 px-2 py-1 text-center">SET</td>
<td className="border-r border-gray-300 px-2 py-1 text-right">
{(loc.unitPrice || 0).toLocaleString()}
</td>
<td className="px-2 py-1 text-right">
{(loc.totalPrice || 0).toLocaleString()}
</td>
</tr>
))
) : (
<tr className="border-b border-gray-300">
<td colSpan={10} className="p-4 text-center text-gray-400">
</td>
</tr>
)}
</tbody>
<tfoot>
{/* 소계 */}
<tr className="border-b border-gray-300">
<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">
{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={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">
{hasDiscount ? `${discountRate.toFixed(1)} %` : '0.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">
{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={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={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>
</tr>
</>
)}
</tfoot>
</table>
</div>
{/* 합계금액 박스 */}
<div className="border-2 border-gray-800 p-3 mb-4 flex justify-between items-center">
<span className="font-semibold text-red-600">
({vatIncluded ? '부가세 포함' : '부가세 별도'})
</span>
<span className="text-xl font-bold">
{grandTotal.toLocaleString()}
</span>
</div>
{/* 증명 문구 + 인감 */}
<div className="text-center py-4">
<p className="text-sm mb-2"> .</p>
<div className="flex justify-end items-center gap-2 pr-8">
<span className="border border-red-400 rounded-full w-10 h-10 flex items-center justify-center text-red-400 text-xs">
</span>
</div>
</div>
</div>
</DocumentViewer>
);
}