- 모달 컴포넌트에서 Content 분리하여 재사용성 향상 - EstimateDocumentContent, DirectConstructionContent 등 - WorkLogContent, QuotePreviewContent, ReceivingReceiptContent - 파일 입력 공통 UI 컴포넌트 추가 - file-dropzone, file-input, file-list, image-upload - 폼 컴포넌트 코드 정리 및 중복 제거 (-4,056줄) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
195 lines
7.4 KiB
TypeScript
195 lines
7.4 KiB
TypeScript
'use client';
|
|
|
|
/**
|
|
* 작업일지 문서 콘텐츠
|
|
*
|
|
* DocumentViewer와 함께 사용하는 문서 본문 컴포넌트
|
|
* 인쇄 영역만 포함 (모달 헤더/툴바는 DocumentViewer가 처리)
|
|
*
|
|
* 공통 컴포넌트 사용:
|
|
* - DocumentHeader: 로고 + 제목 + 결재라인
|
|
* - InfoTable: 라벨-값 정보 테이블
|
|
* - SectionHeader: 섹션 제목 (작업내역, 특이사항)
|
|
*/
|
|
|
|
import type { WorkOrder, WorkOrderItem } from '../WorkOrders/types';
|
|
import { ITEM_STATUS_LABELS } from '../WorkOrders/types';
|
|
import {
|
|
DocumentHeader,
|
|
InfoTable,
|
|
SectionHeader,
|
|
} from '@/components/document-system';
|
|
|
|
interface WorkLogContentProps {
|
|
data: WorkOrder;
|
|
}
|
|
|
|
// 작업 통계 타입
|
|
interface WorkStats {
|
|
orderQty: number;
|
|
completedQty: number;
|
|
inProgressQty: number;
|
|
waitingQty: number;
|
|
progress: number;
|
|
}
|
|
|
|
// 품목 데이터에서 작업 통계 계산
|
|
function calculateWorkStats(items: WorkOrderItem[]): WorkStats {
|
|
const orderQty = items.length;
|
|
const completedQty = items.filter(i => i.status === 'completed').length;
|
|
const inProgressQty = items.filter(i => i.status === 'in_progress').length;
|
|
const waitingQty = items.filter(i => i.status === 'waiting').length;
|
|
const progress = orderQty > 0 ? Math.round((completedQty / orderQty) * 100) : 0;
|
|
|
|
return {
|
|
orderQty,
|
|
completedQty,
|
|
inProgressQty,
|
|
waitingQty,
|
|
progress,
|
|
};
|
|
}
|
|
|
|
export function WorkLogContent({ data: order }: WorkLogContentProps) {
|
|
const today = new Date().toLocaleDateString('ko-KR', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
}).replace(/\. /g, '-').replace('.', '');
|
|
|
|
const documentNo = `WL-${order.processCode.toUpperCase().slice(0, 3)}`;
|
|
|
|
// 품목 데이터
|
|
const items = order.items || [];
|
|
|
|
// 작업 통계 계산
|
|
const workStats = calculateWorkStats(items);
|
|
|
|
// 주 담당자
|
|
const primaryAssignee = order.assignees?.find(a => a.isPrimary)?.name || order.assignee || '-';
|
|
|
|
// 포맷된 납기일
|
|
const formattedDueDate = order.dueDate !== '-'
|
|
? new Date(order.dueDate).toLocaleDateString('ko-KR', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
}).replace(/\. /g, '-').replace('.', '')
|
|
: '-';
|
|
|
|
// 작성자 날짜 포맷
|
|
const writerDate = new Date().toLocaleDateString('ko-KR', {
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
}).replace('. ', '/').replace('.', '');
|
|
|
|
return (
|
|
<div className="p-6 bg-white">
|
|
{/* 문서 헤더: 로고 + 제목 + 결재라인 (공통 컴포넌트) */}
|
|
<DocumentHeader
|
|
title="작 업 일 지"
|
|
documentCode={documentNo}
|
|
subtitle={`${order.processName} 생산부서`}
|
|
logo={{ text: 'KD', subtext: '정동기업' }}
|
|
approval={{
|
|
type: '4col',
|
|
writer: { name: primaryAssignee, date: writerDate },
|
|
showDepartment: true,
|
|
departmentLabels: {
|
|
writer: '판매/전진',
|
|
reviewer: '생산',
|
|
approver: '품질',
|
|
},
|
|
}}
|
|
/>
|
|
|
|
{/* 기본 정보 테이블 (공통 컴포넌트) */}
|
|
<InfoTable
|
|
className="mb-6"
|
|
rows={[
|
|
[
|
|
{ label: '발주처', value: order.client },
|
|
{ label: '현장명', value: order.projectName },
|
|
],
|
|
[
|
|
{ label: '작업일자', value: today },
|
|
{ label: 'LOT NO.', value: order.lotNo },
|
|
],
|
|
[
|
|
{ label: '납기일', value: formattedDueDate },
|
|
{ label: '작업지시번호', value: order.workOrderNo },
|
|
],
|
|
]}
|
|
/>
|
|
|
|
{/* 품목 테이블 */}
|
|
<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.length > 0 ? (
|
|
items.map((item, index) => (
|
|
<div
|
|
key={item.id}
|
|
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.productName}</div>
|
|
<div className="col-span-2 p-2 text-sm text-center border-r border-gray-300">{item.floorCode}</div>
|
|
<div className="col-span-2 p-2 text-sm text-center border-r border-gray-300">{item.specification}</div>
|
|
<div className="col-span-1 p-2 text-sm text-center border-r border-gray-300">{item.quantity}</div>
|
|
<div className="col-span-2 p-2 text-sm text-center">{ITEM_STATUS_LABELS[item.status]}</div>
|
|
</div>
|
|
))
|
|
) : (
|
|
<div className="p-4 text-center text-muted-foreground text-sm">
|
|
등록된 품목이 없습니다.
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 작업내역 */}
|
|
<div className="border border-gray-300 mb-6">
|
|
{/* 섹션 헤더 (공통 컴포넌트) */}
|
|
<SectionHeader>{order.processName} 작업내역</SectionHeader>
|
|
|
|
{/* 수량 및 진행률 */}
|
|
<div className="grid grid-cols-6 border-b border-gray-300">
|
|
<div className="p-2 text-sm bg-gray-100 border-r border-gray-300 text-center font-medium">지시수량</div>
|
|
<div className="p-2 text-sm border-r border-gray-300 text-center">{workStats.orderQty} EA</div>
|
|
<div className="p-2 text-sm bg-gray-100 border-r border-gray-300 text-center font-medium">완료수량</div>
|
|
<div className="p-2 text-sm border-r border-gray-300 text-center">{workStats.completedQty} EA</div>
|
|
<div className="p-2 text-sm bg-gray-100 border-r border-gray-300 text-center font-medium">진행률</div>
|
|
<div className="p-2 text-sm text-center font-medium text-blue-600">{workStats.progress}%</div>
|
|
</div>
|
|
|
|
{/* 상세 상태 */}
|
|
<div className="grid grid-cols-6">
|
|
<div className="p-2 text-sm bg-gray-100 border-r border-gray-300 text-center font-medium">대기</div>
|
|
<div className="p-2 text-sm border-r border-gray-300 text-center">{workStats.waitingQty} EA</div>
|
|
<div className="p-2 text-sm bg-gray-100 border-r border-gray-300 text-center font-medium">작업중</div>
|
|
<div className="p-2 text-sm border-r border-gray-300 text-center">{workStats.inProgressQty} EA</div>
|
|
<div className="p-2 text-sm bg-gray-100 border-r border-gray-300 text-center font-medium">완료</div>
|
|
<div className="p-2 text-sm text-center">{workStats.completedQty} EA</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 특이사항 (공통 컴포넌트) */}
|
|
<div className="border border-gray-300">
|
|
<SectionHeader>특이사항</SectionHeader>
|
|
<div className="p-4 min-h-[60px] text-sm">
|
|
{order.note || '-'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|