feat(WEB): 입력 컴포넌트 공통화 및 UI 개선
- 숫자/통화/전화번호/사업자번호 등 특수 입력 컴포넌트 추가 - MobileCard 컴포넌트 통합 (ListMobileCard 제거) - IntegratedListTemplateV2 페이지네이션 버그 수정 (NaN 이슈) - IntegratedDetailTemplate 타이틀 중복 수정 - 문서 시스템 컴포넌트 추가 - 헤더 벨 아이콘 포커스 스타일 개선 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,4 +59,4 @@ export default function OrderDetailPage({ params }: OrderDetailPageProps) {
|
||||
}
|
||||
|
||||
return <OrderDetailForm mode={mode} orderId={id} initialData={data} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,4 +60,4 @@ export default function ContractDetailPage({ params }: ContractDetailPageProps)
|
||||
initialData={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,4 +62,4 @@ export default function HandoverReportDetailPage({ params }: HandoverReportDetai
|
||||
initialData={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,411 @@
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
import { DocumentViewer } from '@/components/document-system';
|
||||
import { Document, DocumentItem } from '../types';
|
||||
import { MOCK_ORDER_DATA, MOCK_WORK_ORDER, MOCK_SHIPMENT_DETAIL } from '../mockData';
|
||||
|
||||
// 기존 문서 컴포넌트 import
|
||||
import { DeliveryConfirmation } from '@/components/outbound/ShipmentManagement/documents/DeliveryConfirmation';
|
||||
import { ShippingSlip } from '@/components/outbound/ShipmentManagement/documents/ShippingSlip';
|
||||
|
||||
// 품질검사 문서 컴포넌트 import
|
||||
import {
|
||||
ImportInspectionDocument,
|
||||
ProductInspectionDocument,
|
||||
ScreenInspectionDocument,
|
||||
BendingInspectionDocument,
|
||||
SlatInspectionDocument,
|
||||
JointbarInspectionDocument,
|
||||
QualityDocumentUploader,
|
||||
} from './documents';
|
||||
|
||||
interface InspectionModalV2Props {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
document: Document | null;
|
||||
documentItem: DocumentItem | null;
|
||||
}
|
||||
|
||||
// 문서 타입별 정보
|
||||
const DOCUMENT_INFO: Record<string, { label: string; hasTemplate: boolean; color: string }> = {
|
||||
import: { label: '수입검사 성적서', hasTemplate: true, color: 'text-green-600' },
|
||||
order: { label: '수주서', hasTemplate: true, color: 'text-blue-600' },
|
||||
log: { label: '작업일지', hasTemplate: true, color: 'text-orange-500' },
|
||||
report: { label: '중간검사 성적서', hasTemplate: true, color: 'text-blue-500' },
|
||||
confirmation: { label: '납품확인서', hasTemplate: true, color: 'text-red-500' },
|
||||
shipping: { label: '출고증', hasTemplate: true, color: 'text-gray-600' },
|
||||
product: { label: '제품검사 성적서', hasTemplate: true, color: 'text-green-500' },
|
||||
quality: { label: '품질관리서', hasTemplate: false, color: 'text-purple-600' },
|
||||
};
|
||||
|
||||
// Placeholder 컴포넌트 (양식 대기 문서용)
|
||||
const PlaceholderDocument = ({ docType, docItem }: { docType: string; docItem: DocumentItem | null }) => {
|
||||
const info = DOCUMENT_INFO[docType] || { label: '문서', hasTemplate: false, color: 'text-gray-600' };
|
||||
|
||||
return (
|
||||
<div className="bg-white shadow-sm p-16 w-full h-full rounded flex flex-col items-center justify-center text-center">
|
||||
<div className="w-16 h-16 text-amber-500 mb-4 mx-auto">
|
||||
<AlertCircle className="w-full h-full" />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold text-gray-800 mb-2">{info.label}</h2>
|
||||
<p className="text-gray-500 text-sm mb-2">{docItem?.title || '문서'}</p>
|
||||
{docItem?.date && (
|
||||
<p className="text-gray-400 text-xs mb-2">{docItem.date}</p>
|
||||
)}
|
||||
{docItem?.code && (
|
||||
<p className="text-xs text-green-600 font-mono bg-green-50 px-2 py-1 rounded mb-4">
|
||||
로트 번호: {docItem.code}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 p-4 bg-amber-50 rounded-lg border border-amber-200">
|
||||
<p className="text-amber-700 text-sm font-medium">양식 준비 중</p>
|
||||
<p className="text-amber-600 text-xs mt-1">디자인 파일이 필요합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 수주서 문서 컴포넌트 (간소화 버전)
|
||||
const OrderDocument = () => {
|
||||
const data = MOCK_ORDER_DATA;
|
||||
|
||||
return (
|
||||
<div className="bg-white p-8 w-full text-sm shadow-sm">
|
||||
{/* 헤더 */}
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-2xl font-bold">KD</div>
|
||||
<div className="text-xs">경동기업</div>
|
||||
</div>
|
||||
<div className="text-2xl font-bold tracking-[0.5rem]">수 주 서</div>
|
||||
<table className="text-xs border-collapse">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border px-2 py-1 bg-gray-100" rowSpan={3}>
|
||||
<div className="flex flex-col items-center">
|
||||
<span>결</span><span>재</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="border px-2 py-1 bg-gray-100 text-center w-16">작성</td>
|
||||
<td className="border px-2 py-1 bg-gray-100 text-center w-16">검토</td>
|
||||
<td className="border px-2 py-1 bg-gray-100 text-center w-16">승인</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border px-2 py-1 h-10"></td>
|
||||
<td className="border px-2 py-1 h-10"></td>
|
||||
<td className="border px-2 py-1 h-10"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border px-2 py-1 text-center bg-gray-50">판매</td>
|
||||
<td className="border px-2 py-1 text-center bg-gray-50">생산</td>
|
||||
<td className="border px-2 py-1 text-center bg-gray-50">품질</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 기본 정보 */}
|
||||
<table className="w-full border-collapse mb-6 text-xs">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border px-3 py-2 bg-gray-100 w-24">LOT NO.</td>
|
||||
<td className="border px-3 py-2">{data.lotNumber}</td>
|
||||
<td className="border px-3 py-2 bg-gray-100 w-24">수주일</td>
|
||||
<td className="border px-3 py-2">{data.orderDate}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border px-3 py-2 bg-gray-100">발주처</td>
|
||||
<td className="border px-3 py-2">{data.client}</td>
|
||||
<td className="border px-3 py-2 bg-gray-100">현장명</td>
|
||||
<td className="border px-3 py-2">{data.siteName}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border px-3 py-2 bg-gray-100">담당자</td>
|
||||
<td className="border px-3 py-2">{data.manager}</td>
|
||||
<td className="border px-3 py-2 bg-gray-100">연락처</td>
|
||||
<td className="border px-3 py-2">{data.managerContact}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border px-3 py-2 bg-gray-100">납기요청일</td>
|
||||
<td className="border px-3 py-2">{data.deliveryRequestDate}</td>
|
||||
<td className="border px-3 py-2 bg-gray-100">출고예정일</td>
|
||||
<td className="border px-3 py-2">{data.expectedShipDate}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border px-3 py-2 bg-gray-100">배송방법</td>
|
||||
<td className="border px-3 py-2">{data.deliveryMethod}</td>
|
||||
<td className="border px-3 py-2 bg-gray-100">배송지</td>
|
||||
<td className="border px-3 py-2">{data.address}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* 품목 테이블 */}
|
||||
<table className="w-full border-collapse mb-6 text-xs">
|
||||
<thead>
|
||||
<tr className="bg-gray-100">
|
||||
<th className="border px-2 py-2 w-10">No</th>
|
||||
<th className="border px-2 py-2">품목명</th>
|
||||
<th className="border px-2 py-2 w-24">규격</th>
|
||||
<th className="border px-2 py-2 w-12">단위</th>
|
||||
<th className="border px-2 py-2 w-12">수량</th>
|
||||
<th className="border px-2 py-2 w-20">단가</th>
|
||||
<th className="border px-2 py-2 w-24">금액</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.items.map((item, index) => (
|
||||
<tr key={item.id}>
|
||||
<td className="border px-2 py-2 text-center">{index + 1}</td>
|
||||
<td className="border px-2 py-2">{item.name}</td>
|
||||
<td className="border px-2 py-2 text-center">{item.specification}</td>
|
||||
<td className="border px-2 py-2 text-center">{item.unit}</td>
|
||||
<td className="border px-2 py-2 text-center">{item.quantity}</td>
|
||||
<td className="border px-2 py-2 text-right">{item.unitPrice?.toLocaleString()}</td>
|
||||
<td className="border px-2 py-2 text-right">{item.amount?.toLocaleString()}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colSpan={5} className="border px-2 py-2 text-right bg-gray-50">소계</td>
|
||||
<td colSpan={2} className="border px-2 py-2 text-right">{data.subtotal.toLocaleString()}원</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan={5} className="border px-2 py-2 text-right bg-gray-50">할인 ({data.discountRate}%)</td>
|
||||
<td colSpan={2} className="border px-2 py-2 text-right text-red-600">-{(data.subtotal * data.discountRate / 100).toLocaleString()}원</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan={5} className="border px-2 py-2 text-right bg-gray-100 font-bold">총액</td>
|
||||
<td colSpan={2} className="border px-2 py-2 text-right font-bold text-blue-600">{data.totalAmount.toLocaleString()}원</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
{/* 비고 */}
|
||||
{data.remarks && (
|
||||
<div className="border p-4">
|
||||
<h3 className="font-medium mb-2 text-xs">비고</h3>
|
||||
<p className="text-xs text-gray-600">{data.remarks}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 작업일지 문서 컴포넌트 (간소화 버전)
|
||||
const WorkLogDocument = () => {
|
||||
const order = MOCK_WORK_ORDER;
|
||||
const today = new Date().toLocaleDateString('ko-KR').replace(/\. /g, '-').replace('.', '');
|
||||
const documentNo = `WL-${order.process.toUpperCase().slice(0, 3)}`;
|
||||
const lotNo = `KD-TS-${new Date().toISOString().slice(2, 10).replace(/-/g, '')}-01`;
|
||||
|
||||
const items = [
|
||||
{ no: 1, name: order.productName, location: '1층/A-01', spec: '3000×2500', qty: 1, status: '완료' },
|
||||
{ no: 2, name: order.productName, location: '2층/A-02', spec: '3000×2500', qty: 1, status: '작업중' },
|
||||
{ no: 3, name: order.productName, location: '3층/A-03', spec: '-', qty: 1, status: '대기' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="bg-white p-8 w-full text-sm shadow-sm">
|
||||
{/* 헤더 */}
|
||||
<div className="flex justify-between items-start mb-6 border border-gray-300">
|
||||
<div className="w-24 border-r border-gray-300 flex flex-col items-center justify-center p-3">
|
||||
<span className="text-2xl font-bold">KD</span>
|
||||
<span className="text-xs text-gray-500">경동기업</span>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col items-center justify-center p-3 border-r border-gray-300">
|
||||
<h1 className="text-xl font-bold tracking-widest mb-1">작 업 일 지</h1>
|
||||
<p className="text-xs text-gray-500">{documentNo}</p>
|
||||
<p className="text-sm font-medium mt-1">스크린 생산부서</p>
|
||||
</div>
|
||||
<table className="text-xs shrink-0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowSpan={3} className="w-8 text-center font-medium bg-gray-100 border-r border-b border-gray-300">
|
||||
<div className="flex flex-col items-center"><span>결</span><span>재</span></div>
|
||||
</td>
|
||||
<td className="w-16 p-2 text-center font-medium bg-gray-100 border-r border-b border-gray-300">작성</td>
|
||||
<td className="w-16 p-2 text-center font-medium bg-gray-100 border-r border-b border-gray-300">검토</td>
|
||||
<td className="w-16 p-2 text-center font-medium bg-gray-100 border-b border-gray-300">승인</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="w-16 p-2 text-center border-r border-b border-gray-300">
|
||||
<div>{order.assignees[0] || '-'}</div>
|
||||
</td>
|
||||
<td className="w-16 p-2 text-center border-r border-b border-gray-300"></td>
|
||||
<td className="w-16 p-2 text-center border-b border-gray-300"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="w-16 p-2 text-center bg-gray-50 border-r border-gray-300">판매</td>
|
||||
<td className="w-16 p-2 text-center bg-gray-50 border-r border-gray-300">생산</td>
|
||||
<td className="w-16 p-2 text-center bg-gray-50">품질</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 기본 정보 */}
|
||||
<div className="border border-gray-300 mb-6">
|
||||
<div className="grid grid-cols-2 border-b border-gray-300">
|
||||
<div className="flex border-r border-gray-300">
|
||||
<div className="w-24 bg-gray-100 p-3 text-sm font-medium border-r border-gray-300">발주처</div>
|
||||
<div className="flex-1 p-3 text-sm">{order.client}</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="w-24 bg-gray-100 p-3 text-sm font-medium border-r border-gray-300">현장명</div>
|
||||
<div className="flex-1 p-3 text-sm">{order.projectName}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 border-b border-gray-300">
|
||||
<div className="flex border-r border-gray-300">
|
||||
<div className="w-24 bg-gray-100 p-3 text-sm font-medium border-r border-gray-300">작업일자</div>
|
||||
<div className="flex-1 p-3 text-sm">{today}</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="w-24 bg-gray-100 p-3 text-sm font-medium border-r border-gray-300">LOT NO.</div>
|
||||
<div className="flex-1 p-3 text-sm">{lotNo}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2">
|
||||
<div className="flex border-r border-gray-300">
|
||||
<div className="w-24 bg-gray-100 p-3 text-sm font-medium border-r border-gray-300">납기일</div>
|
||||
<div className="flex-1 p-3 text-sm">{order.dueDate}</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className="w-24 bg-gray-100 p-3 text-sm font-medium border-r border-gray-300">지시수량</div>
|
||||
<div className="flex-1 p-3 text-sm">{order.quantity} EA</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 품목 테이블 */}
|
||||
<div className="border border-gray-300 mb-6">
|
||||
<div className="grid grid-cols-12 border-b border-gray-300 bg-gray-100">
|
||||
<div className="col-span-1 p-2 text-sm font-medium text-center border-r border-gray-300">No</div>
|
||||
<div className="col-span-4 p-2 text-sm font-medium text-center border-r border-gray-300">품목명</div>
|
||||
<div className="col-span-2 p-2 text-sm font-medium text-center border-r border-gray-300">출/부호</div>
|
||||
<div className="col-span-2 p-2 text-sm font-medium text-center border-r border-gray-300">규격</div>
|
||||
<div className="col-span-1 p-2 text-sm font-medium text-center border-r border-gray-300">수량</div>
|
||||
<div className="col-span-2 p-2 text-sm font-medium text-center">상태</div>
|
||||
</div>
|
||||
{items.map((item, index) => (
|
||||
<div key={item.no} className={`grid grid-cols-12 ${index < items.length - 1 ? 'border-b border-gray-300' : ''}`}>
|
||||
<div className="col-span-1 p-2 text-sm text-center border-r border-gray-300">{item.no}</div>
|
||||
<div className="col-span-4 p-2 text-sm border-r border-gray-300">{item.name}</div>
|
||||
<div className="col-span-2 p-2 text-sm text-center border-r border-gray-300">{item.location}</div>
|
||||
<div className="col-span-2 p-2 text-sm text-center border-r border-gray-300">{item.spec}</div>
|
||||
<div className="col-span-1 p-2 text-sm text-center border-r border-gray-300">{item.qty}</div>
|
||||
<div className="col-span-2 p-2 text-sm text-center">
|
||||
<span className={`px-2 py-0.5 rounded text-xs ${
|
||||
item.status === '완료' ? 'bg-green-100 text-green-700' :
|
||||
item.status === '작업중' ? 'bg-blue-100 text-blue-700' :
|
||||
'bg-gray-100 text-gray-700'
|
||||
}`}>{item.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 특이사항 */}
|
||||
<div className="border border-gray-300">
|
||||
<div className="bg-gray-800 text-white p-2.5 text-sm font-medium text-center">특이사항</div>
|
||||
<div className="p-4 min-h-[60px] text-sm">{order.instruction || '-'}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* InspectionModal V2
|
||||
* - DocumentViewer 시스템 사용
|
||||
* - 기존 문서 렌더링 로직 유지
|
||||
*/
|
||||
export const InspectionModalV2 = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
document: doc,
|
||||
documentItem,
|
||||
}: InspectionModalV2Props) => {
|
||||
if (!doc) return null;
|
||||
|
||||
const docInfo = DOCUMENT_INFO[doc.type] || { label: doc.title, hasTemplate: false, color: 'text-gray-600' };
|
||||
const subtitle = documentItem
|
||||
? `${docInfo.label} - ${documentItem.date}${documentItem.code ? ` 로트: ${documentItem.code}` : ''}`
|
||||
: docInfo.label;
|
||||
|
||||
// 품질관리서 PDF 업로드 핸들러
|
||||
const handleQualityFileUpload = (file: File) => {
|
||||
console.log('[InspectionModalV2] 품질관리서 PDF 업로드:', file.name);
|
||||
};
|
||||
|
||||
const handleQualityFileDelete = () => {
|
||||
console.log('[InspectionModalV2] 품질관리서 PDF 삭제');
|
||||
};
|
||||
|
||||
// 중간검사 성적서 서브타입에 따른 렌더링
|
||||
const renderReportDocument = () => {
|
||||
const subType = documentItem?.subType;
|
||||
switch (subType) {
|
||||
case 'screen':
|
||||
return <ScreenInspectionDocument />;
|
||||
case 'bending':
|
||||
return <BendingInspectionDocument />;
|
||||
case 'slat':
|
||||
return <SlatInspectionDocument />;
|
||||
case 'jointbar':
|
||||
return <JointbarInspectionDocument />;
|
||||
default:
|
||||
return <ScreenInspectionDocument />;
|
||||
}
|
||||
};
|
||||
|
||||
// 문서 타입에 따른 컨텐츠 렌더링
|
||||
const renderDocumentContent = () => {
|
||||
switch (doc.type) {
|
||||
case 'order':
|
||||
return <OrderDocument />;
|
||||
case 'log':
|
||||
return <WorkLogDocument />;
|
||||
case 'confirmation':
|
||||
return <DeliveryConfirmation data={MOCK_SHIPMENT_DETAIL} />;
|
||||
case 'shipping':
|
||||
return <ShippingSlip data={MOCK_SHIPMENT_DETAIL} />;
|
||||
case 'import':
|
||||
return <ImportInspectionDocument />;
|
||||
case 'product':
|
||||
return <ProductInspectionDocument />;
|
||||
case 'report':
|
||||
return renderReportDocument();
|
||||
case 'quality':
|
||||
return (
|
||||
<QualityDocumentUploader
|
||||
onFileUpload={handleQualityFileUpload}
|
||||
onFileDelete={handleQualityFileDelete}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <PlaceholderDocument docType={doc.type} docItem={documentItem} />;
|
||||
}
|
||||
};
|
||||
|
||||
// 다운로드 핸들러 (TODO: 실제 구현)
|
||||
const handleDownload = () => {
|
||||
console.log('[InspectionModalV2] 다운로드 요청:', doc.type);
|
||||
};
|
||||
|
||||
return (
|
||||
<DocumentViewer
|
||||
title={doc.title}
|
||||
subtitle={subtitle}
|
||||
preset="inspection"
|
||||
open={isOpen}
|
||||
onOpenChange={(open) => !open && onClose()}
|
||||
onDownload={handleDownload}
|
||||
>
|
||||
{renderDocumentContent()}
|
||||
</DocumentViewer>
|
||||
);
|
||||
};
|
||||
@@ -6,7 +6,8 @@ import { Filters } from './components/Filters';
|
||||
import { ReportList } from './components/ReportList';
|
||||
import { RouteList } from './components/RouteList';
|
||||
import { DocumentList } from './components/DocumentList';
|
||||
import { InspectionModal } from './components/InspectionModal';
|
||||
// import { InspectionModal } from './components/InspectionModal';
|
||||
import { InspectionModalV2 as InspectionModal } from './components/InspectionModalV2';
|
||||
import { DayTabs } from './components/DayTabs';
|
||||
import { Day1ChecklistPanel } from './components/Day1ChecklistPanel';
|
||||
import { Day1DocumentSection } from './components/Day1DocumentSection';
|
||||
|
||||
@@ -45,7 +45,7 @@ import {
|
||||
TableCell,
|
||||
} from "@/components/ui/table";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { ListMobileCard, InfoField } from "@/components/organisms/ListMobileCard";
|
||||
import { ListMobileCard, InfoField } from "@/components/organisms/MobileCard";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useState, useEffect, useCallback } from "react";
|
||||
import { useRouter, useParams } from "next/navigation";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { PhoneInput } from "@/components/ui/phone-input";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
@@ -403,10 +404,10 @@ export default function OrderEditPage() {
|
||||
<Label>
|
||||
수신처 연락처 <span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
<PhoneInput
|
||||
value={form.receiverContact}
|
||||
onChange={(e) =>
|
||||
setForm({ ...form, receiverContact: e.target.value })
|
||||
onChange={(value) =>
|
||||
setForm({ ...form, receiverContact: value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
} from "@/components/ui/table";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { formatAmount, formatAmountManwon } from "@/utils/formatAmount";
|
||||
import { ListMobileCard, InfoField } from "@/components/organisms/ListMobileCard";
|
||||
import { ListMobileCard, InfoField } from "@/components/organisms/MobileCard";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
|
||||
@@ -47,7 +47,7 @@ import {
|
||||
type TableColumn,
|
||||
} from "@/components/templates/UniversalListPage";
|
||||
import { BadgeSm } from "@/components/atoms/BadgeSm";
|
||||
import { ListMobileCard, InfoField } from "@/components/organisms/ListMobileCard";
|
||||
import { ListMobileCard, InfoField } from "@/components/organisms/MobileCard";
|
||||
|
||||
// 생산지시 상태 타입
|
||||
type ProductionOrderStatus =
|
||||
|
||||
@@ -37,22 +37,13 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { DocumentViewer } from "@/components/document-system";
|
||||
import {
|
||||
FileText,
|
||||
Edit,
|
||||
List,
|
||||
Printer,
|
||||
FileOutput,
|
||||
Download,
|
||||
Mail,
|
||||
MessageCircle,
|
||||
X,
|
||||
FileCheck,
|
||||
Package,
|
||||
ChevronDown,
|
||||
@@ -596,267 +587,83 @@ export default function QuoteDetailPage() {
|
||||
)}
|
||||
|
||||
{/* 견적서 다이얼로그 */}
|
||||
<Dialog open={isQuoteDocumentOpen} onOpenChange={setIsQuoteDocumentOpen}>
|
||||
<DialogContent className="max-w-[95vw] md:max-w-[800px] lg:max-w-[900px] h-[95vh] flex flex-col p-0">
|
||||
<DialogHeader className="flex-shrink-0 px-6 py-4 border-b">
|
||||
<DialogTitle>견적서</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 버튼 영역 */}
|
||||
<div className="flex-shrink-0 px-6 py-3 border-b bg-muted/30 flex flex-wrap gap-2 justify-between items-center">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
className="bg-red-600 hover:bg-red-700"
|
||||
onClick={() => {
|
||||
toast.info('인쇄 대화상자에서 "PDF로 저장" 옵션을 선택하세요.');
|
||||
window.print();
|
||||
}}
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
PDF
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
onClick={handleSendEmail}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<Mail className="w-4 h-4 mr-2" />
|
||||
이메일
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
className="bg-purple-600 hover:bg-purple-700"
|
||||
onClick={() => toast.info("팩스 전송 기능은 준비 중입니다.")}
|
||||
>
|
||||
<FileOutput className="w-4 h-4 mr-2" />
|
||||
팩스
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
className="bg-yellow-500 hover:bg-yellow-600"
|
||||
onClick={handleSendKakao}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<MessageCircle className="w-4 h-4 mr-2" />
|
||||
카카오톡
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={() => window.print()}>
|
||||
<Printer className="w-4 h-4 mr-2" />
|
||||
인쇄
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setIsQuoteDocumentOpen(false)}
|
||||
>
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
닫기
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 문서 영역 */}
|
||||
<div className="flex-1 overflow-y-auto bg-gray-100 p-4">
|
||||
<div className="max-w-[210mm] mx-auto bg-white shadow-lg rounded-lg p-8">
|
||||
<QuoteDocument quote={quote} companyInfo={companyInfo} />
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<DocumentViewer
|
||||
title="견적서"
|
||||
preset="quote"
|
||||
open={isQuoteDocumentOpen}
|
||||
onOpenChange={setIsQuoteDocumentOpen}
|
||||
onPdf={() => {
|
||||
toast.info('인쇄 대화상자에서 "PDF로 저장" 옵션을 선택하세요.');
|
||||
window.print();
|
||||
}}
|
||||
onEmail={handleSendEmail}
|
||||
onFax={() => toast.info("팩스 전송 기능은 준비 중입니다.")}
|
||||
onKakao={handleSendKakao}
|
||||
>
|
||||
<QuoteDocument quote={quote} companyInfo={companyInfo} />
|
||||
</DocumentViewer>
|
||||
|
||||
{/* 산출내역서 다이얼로그 */}
|
||||
<Dialog
|
||||
<DocumentViewer
|
||||
title="산출내역서"
|
||||
preset="quote"
|
||||
open={isCalculationReportOpen}
|
||||
onOpenChange={setIsCalculationReportOpen}
|
||||
>
|
||||
<DialogContent className="max-w-[95vw] md:max-w-[800px] lg:max-w-[900px] h-[95vh] flex flex-col p-0 overflow-hidden">
|
||||
<DialogHeader className="flex-shrink-0 px-6 py-4 border-b">
|
||||
<DialogTitle>산출내역서</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 버튼 영역 */}
|
||||
<div className="flex-shrink-0 px-6 py-3 border-b bg-muted/30 flex flex-wrap gap-2 justify-between items-center">
|
||||
<div className="flex gap-4 flex-wrap items-center">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
className="bg-red-600 hover:bg-red-700"
|
||||
onClick={() => {
|
||||
toast.info('인쇄 대화상자에서 "PDF로 저장" 옵션을 선택하세요.');
|
||||
window.print();
|
||||
}}
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
PDF
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
onClick={handleSendEmail}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<Mail className="w-4 h-4 mr-2" />
|
||||
이메일
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
className="bg-purple-600 hover:bg-purple-700"
|
||||
onClick={() => toast.info("팩스 전송 기능은 준비 중입니다.")}
|
||||
>
|
||||
<FileOutput className="w-4 h-4 mr-2" />
|
||||
팩스
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
className="bg-yellow-500 hover:bg-yellow-600"
|
||||
onClick={handleSendKakao}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<MessageCircle className="w-4 h-4 mr-2" />
|
||||
카카오톡
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => window.print()}
|
||||
>
|
||||
<Printer className="w-4 h-4 mr-2" />
|
||||
인쇄
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 표시 옵션 체크박스 */}
|
||||
<div className="flex gap-4 pl-4 border-l">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showDetailedBreakdown}
|
||||
onChange={(e) => setShowDetailedBreakdown(e.target.checked)}
|
||||
className="w-4 h-4 rounded border-gray-300"
|
||||
/>
|
||||
<span className="text-sm">산출내역서</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showMaterialList}
|
||||
onChange={(e) => setShowMaterialList(e.target.checked)}
|
||||
className="w-4 h-4 rounded border-gray-300"
|
||||
/>
|
||||
<span className="text-sm">소요자재 내역</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setIsCalculationReportOpen(false)}
|
||||
>
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
닫기
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 문서 영역 */}
|
||||
<div className="flex-1 overflow-y-auto bg-gray-100 p-4">
|
||||
<div className="max-w-[210mm] mx-auto bg-white shadow-lg rounded-lg p-8">
|
||||
<QuoteCalculationReport
|
||||
quote={quote}
|
||||
companyInfo={companyInfo}
|
||||
documentType="견적산출내역서"
|
||||
showDetailedBreakdown={showDetailedBreakdown}
|
||||
showMaterialList={showMaterialList}
|
||||
onPdf={() => {
|
||||
toast.info('인쇄 대화상자에서 "PDF로 저장" 옵션을 선택하세요.');
|
||||
window.print();
|
||||
}}
|
||||
onEmail={handleSendEmail}
|
||||
onFax={() => toast.info("팩스 전송 기능은 준비 중입니다.")}
|
||||
onKakao={handleSendKakao}
|
||||
toolbarExtra={
|
||||
<>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showDetailedBreakdown}
|
||||
onChange={(e) => setShowDetailedBreakdown(e.target.checked)}
|
||||
className="w-4 h-4 rounded border-gray-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<span className="text-sm">산출내역서</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showMaterialList}
|
||||
onChange={(e) => setShowMaterialList(e.target.checked)}
|
||||
className="w-4 h-4 rounded border-gray-300"
|
||||
/>
|
||||
<span className="text-sm">소요자재 내역</span>
|
||||
</label>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<QuoteCalculationReport
|
||||
quote={quote}
|
||||
companyInfo={companyInfo}
|
||||
documentType="견적산출내역서"
|
||||
showDetailedBreakdown={showDetailedBreakdown}
|
||||
showMaterialList={showMaterialList}
|
||||
/>
|
||||
</DocumentViewer>
|
||||
|
||||
{/* 발주서 다이얼로그 */}
|
||||
<Dialog open={isPurchaseOrderOpen} onOpenChange={setIsPurchaseOrderOpen}>
|
||||
<DialogContent className="max-w-[95vw] md:max-w-[800px] lg:max-w-[900px] h-[95vh] flex flex-col p-0 overflow-hidden">
|
||||
<DialogHeader className="flex-shrink-0 px-6 py-4 border-b">
|
||||
<DialogTitle>발주서</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{/* 버튼 영역 */}
|
||||
<div className="flex-shrink-0 px-6 py-3 border-b bg-muted/30 flex flex-wrap gap-2 justify-between items-center">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
className="bg-red-600 hover:bg-red-700"
|
||||
onClick={() => {
|
||||
toast.info('인쇄 대화상자에서 "PDF로 저장" 옵션을 선택하세요.');
|
||||
window.print();
|
||||
}}
|
||||
>
|
||||
<Download className="w-4 h-4 mr-2" />
|
||||
PDF
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
onClick={handleSendEmail}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<Mail className="w-4 h-4 mr-2" />
|
||||
이메일
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
className="bg-purple-600 hover:bg-purple-700"
|
||||
onClick={() => toast.info("팩스 전송 기능은 준비 중입니다.")}
|
||||
>
|
||||
<FileOutput className="w-4 h-4 mr-2" />
|
||||
팩스
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="default"
|
||||
className="bg-yellow-500 hover:bg-yellow-600"
|
||||
onClick={handleSendKakao}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<MessageCircle className="w-4 h-4 mr-2" />
|
||||
카카오톡
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={() => window.print()}>
|
||||
<Printer className="w-4 h-4 mr-2" />
|
||||
인쇄
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setIsPurchaseOrderOpen(false)}
|
||||
>
|
||||
<X className="w-4 h-4 mr-2" />
|
||||
닫기
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 문서 영역 */}
|
||||
<div className="flex-1 overflow-y-auto bg-gray-100 p-4">
|
||||
<div className="max-w-[210mm] mx-auto bg-white shadow-lg rounded-lg p-8">
|
||||
<PurchaseOrderDocument quote={quote} companyInfo={companyInfo} />
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<DocumentViewer
|
||||
title="발주서"
|
||||
preset="quote"
|
||||
open={isPurchaseOrderOpen}
|
||||
onOpenChange={setIsPurchaseOrderOpen}
|
||||
onPdf={() => {
|
||||
toast.info('인쇄 대화상자에서 "PDF로 저장" 옵션을 선택하세요.');
|
||||
window.print();
|
||||
}}
|
||||
onEmail={handleSendEmail}
|
||||
onFax={() => toast.info("팩스 전송 기능은 준비 중입니다.")}
|
||||
onKakao={handleSendKakao}
|
||||
>
|
||||
<PurchaseOrderDocument quote={quote} companyInfo={companyInfo} />
|
||||
</DocumentViewer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user