LocationListPanel: - 개소 목록 테이블 컬럼 확장 (층, 부호, 사이즈, 제품, 수량) - 수정/삭제 버튼 추가 - 테이블 헤더 스타일 변경 (어두운 배경) - 선택 행 스타일 변경 (blue → orange) QuotePreviewContent: - 공급자 정보 테이블 확장 (대표자, FAX, 종목 추가) - 품종 → 종류 라벨 변경 - 소계/할인율 행 레이아웃 개선 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
384 lines
18 KiB
TypeScript
384 lines
18 KiB
TypeScript
'use client';
|
|
|
|
/**
|
|
* 견적서 문서 콘텐츠
|
|
*
|
|
* 양식 타입:
|
|
* - vendor: 업체발송용 (부가세 별도/포함)
|
|
* - calculation: 산출내역서
|
|
*/
|
|
|
|
import React from 'react';
|
|
import type { QuoteFormDataV2 } from './QuoteRegistrationV2';
|
|
|
|
// 양식 타입
|
|
type TemplateType = 'vendor' | 'calculation';
|
|
|
|
interface QuotePreviewContentProps {
|
|
data: QuoteFormDataV2;
|
|
/** 양식 타입 (기본: vendor) */
|
|
templateType?: TemplateType;
|
|
/** 부가세 포함 여부 (업체발송용에서만 사용) */
|
|
vatIncluded?: boolean;
|
|
/** 할인율 (%) */
|
|
discountRate?: number;
|
|
/** 할인금액 (원) */
|
|
discountAmount?: number;
|
|
}
|
|
|
|
export function QuotePreviewContent({
|
|
data: quoteData,
|
|
templateType = 'vendor',
|
|
vatIncluded = false,
|
|
discountRate = 0,
|
|
discountAmount = 0,
|
|
}: QuotePreviewContentProps) {
|
|
// 소계 (할인 전 금액)
|
|
const subtotal = quoteData.locations.reduce(
|
|
(sum, loc) => sum + (loc.totalPrice || 0),
|
|
0
|
|
);
|
|
|
|
// 할인 적용 후 금액
|
|
const afterDiscount = subtotal - discountAmount;
|
|
|
|
// 부가세
|
|
const vat = Math.round(afterDiscount * 0.1);
|
|
|
|
// 총 견적금액 (부가세 포함 여부에 따라)
|
|
const grandTotal = vatIncluded ? afterDiscount + vat : afterDiscount;
|
|
|
|
// 할인 적용 여부
|
|
const hasDiscount = discountAmount > 0;
|
|
|
|
// 산출내역서 여부
|
|
const isCalculation = templateType === 'calculation';
|
|
|
|
return (
|
|
<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.id || 'ABC123'} | 작성일자: {quoteData.registrationDate || '-'}
|
|
</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">{quoteData.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>
|
|
{quoteData.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.symbol || '-'}</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}</td>
|
|
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.openHeight}</td>
|
|
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.quantity}</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>
|
|
))}
|
|
</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">
|
|
{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={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>
|
|
|
|
{/* 산출내역서일 경우 세부 산출내역서 테이블 추가 */}
|
|
{isCalculation && (
|
|
<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">부호</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">단가</th>
|
|
<th className="px-2 py-1">합계</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{quoteData.locations.map((loc) => (
|
|
<React.Fragment key={loc.id}>
|
|
{/* 각 개소별 품목 상세 */}
|
|
<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>
|
|
<td className="border-r border-gray-300 px-2 py-1 text-center">{loc.quantity}</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>
|
|
</React.Fragment>
|
|
))}
|
|
{/* 소계 */}
|
|
<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>
|
|
</table>
|
|
</div>
|
|
)}
|
|
|
|
{/* 비고 */}
|
|
<div className="border border-gray-400 mb-4">
|
|
<div className="grid grid-cols-[80px_1fr]">
|
|
<div className="bg-gray-200 px-2 py-2 font-semibold text-center border-r border-gray-400">
|
|
비고
|
|
</div>
|
|
<div className="px-3 py-2 text-xs">
|
|
<p>※ 해당 견적서의 유효기간은 <span className="text-red-600 font-semibold">발행일 기준 1개월</span> 입니다.</p>
|
|
<p>※ 견적금액의 50%를 입금하시면 생산가 진행합니다.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 결제방법 / 담당자 */}
|
|
<div className="border border-gray-400">
|
|
<table className="w-full text-xs">
|
|
<tbody>
|
|
<tr className="border-b border-gray-300">
|
|
<td className="bg-gray-200 px-2 py-1 w-20 text-center border-r border-gray-300 font-semibold">결제방법</td>
|
|
<td className="px-2 py-1 border-r border-gray-300">계좌이체</td>
|
|
<td className="bg-gray-200 px-2 py-1 w-20 text-center border-r border-gray-300 font-semibold">계좌정보</td>
|
|
<td className="px-2 py-1">국민은행 12312132132</td>
|
|
</tr>
|
|
<tr>
|
|
<td className="bg-gray-200 px-2 py-1 text-center border-r border-gray-300 font-semibold">담당자</td>
|
|
<td className="px-2 py-1 border-r border-gray-300">홍길동 과장</td>
|
|
<td className="bg-gray-200 px-2 py-1 text-center border-r border-gray-300 font-semibold">연락처</td>
|
|
<td className="px-2 py-1">010-1234-1234</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|