- /juil/ 경로를 /construction/으로 변경 - 컴포넌트 폴더명 juil → construction 변경 - 컴포넌트명 Juil* → Construction* 변경 - 테스트 URL 페이지 경로 업데이트 - claudedocs 문서 경로 업데이트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
308 lines
14 KiB
TypeScript
308 lines
14 KiB
TypeScript
'use client';
|
|
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogTitle,
|
|
VisuallyHidden,
|
|
} from '@/components/ui/dialog';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Edit,
|
|
Trash2,
|
|
Printer,
|
|
X,
|
|
} from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
import { useRouter } from 'next/navigation';
|
|
import { printArea } from '@/lib/print-utils';
|
|
import type { HandoverReportDetail } from '../types';
|
|
|
|
// 금액 포맷팅
|
|
function formatAmount(amount: number | undefined | null): string {
|
|
if (amount === undefined || amount === null) return '0';
|
|
return new Intl.NumberFormat('ko-KR').format(amount);
|
|
}
|
|
|
|
// 날짜 포맷팅 (년월)
|
|
function formatYearMonth(dateStr: string | null): string {
|
|
if (!dateStr) return '-';
|
|
const date = new Date(dateStr);
|
|
return `${date.getFullYear()}년 ${date.getMonth() + 1}월`;
|
|
}
|
|
|
|
// 날짜 포맷팅 (전체)
|
|
function formatDate(dateStr: string | null): string {
|
|
if (!dateStr) return '-';
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleDateString('ko-KR', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
});
|
|
}
|
|
|
|
interface HandoverReportDocumentModalProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
report: HandoverReportDetail;
|
|
}
|
|
|
|
export function HandoverReportDocumentModal({
|
|
open,
|
|
onOpenChange,
|
|
report,
|
|
}: HandoverReportDocumentModalProps) {
|
|
const router = useRouter();
|
|
|
|
// 수정
|
|
const handleEdit = () => {
|
|
onOpenChange(false);
|
|
router.push(`/ko/construction/project/contract/handover-report/${report.id}/edit`);
|
|
};
|
|
|
|
// 삭제
|
|
const handleDelete = () => {
|
|
toast.info('삭제 기능은 준비 중입니다.');
|
|
};
|
|
|
|
// 인쇄
|
|
const handlePrint = () => {
|
|
printArea({ title: '인수인계보고서 인쇄' });
|
|
};
|
|
|
|
// 계약 ITEM 행 수 계산 (최소 1행)
|
|
const contractItemsCount = Math.max(report.contractItems.length, 1);
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent className="max-w-[95vw] md:max-w-[800px] lg:max-w-[900px] h-[95vh] p-0 flex flex-col gap-0 overflow-hidden [&>button]:hidden">
|
|
<VisuallyHidden>
|
|
<DialogTitle>인수인계보고서 상세</DialogTitle>
|
|
</VisuallyHidden>
|
|
|
|
{/* 헤더 영역 - 고정 (인쇄 시 숨김) */}
|
|
<div className="print-hidden flex-shrink-0 flex items-center justify-between px-6 py-4 border-b bg-white">
|
|
<h2 className="text-lg font-semibold">인수인계보고서 상세</h2>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() => onOpenChange(false)}
|
|
className="h-8 w-8"
|
|
>
|
|
<X className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
|
|
{/* 버튼 영역 - 고정 (인쇄 시 숨김) */}
|
|
<div className="print-hidden flex-shrink-0 flex items-center gap-2 px-6 py-3 bg-muted/30 border-b">
|
|
<Button variant="outline" size="sm" onClick={handleEdit}>
|
|
<Edit className="h-4 w-4 mr-1" />
|
|
수정
|
|
</Button>
|
|
<Button variant="outline" size="sm" onClick={handleDelete}>
|
|
<Trash2 className="h-4 w-4 mr-1" />
|
|
삭제
|
|
</Button>
|
|
<Button variant="outline" size="sm" onClick={handlePrint}>
|
|
<Printer className="h-4 w-4 mr-1" />
|
|
인쇄
|
|
</Button>
|
|
</div>
|
|
|
|
{/* 문서 영역 - 스크롤 (인쇄 시 이 영역만 출력) */}
|
|
<div className="print-area flex-1 overflow-y-auto bg-gray-100 p-6">
|
|
<div className="max-w-[210mm] mx-auto bg-white shadow-lg rounded-lg p-8">
|
|
{/* 상단: 제목 + 결재란 */}
|
|
<div className="flex justify-between items-start mb-6">
|
|
{/* 좌측: 제목 및 문서정보 */}
|
|
<div>
|
|
<h1 className="text-2xl font-bold mb-2">인수인계보고서</h1>
|
|
<div className="text-sm text-gray-600">
|
|
문서번호: {report.reportNumber} | 작성일자: {formatDate(report.createdAt)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 우측: 결재란 */}
|
|
<table className="border-collapse border border-gray-300 text-sm">
|
|
<tbody>
|
|
<tr>
|
|
<th rowSpan={3} className="border border-gray-300 px-2 py-1 bg-gray-50 text-center w-8 align-middle">
|
|
<span className="writing-vertical">결<br />재</span>
|
|
</th>
|
|
<th className="border border-gray-300 px-3 py-1 bg-gray-50 text-center w-16">작성</th>
|
|
<th className="border border-gray-300 px-3 py-1 bg-gray-50 text-center w-16">승인</th>
|
|
<th className="border border-gray-300 px-3 py-1 bg-gray-50 text-center w-16">승인</th>
|
|
<th className="border border-gray-300 px-3 py-1 bg-gray-50 text-center w-16">승인</th>
|
|
</tr>
|
|
<tr>
|
|
<td className="border border-gray-300 px-3 py-2 text-center h-10">홍길동</td>
|
|
<td className="border border-gray-300 px-3 py-2 text-center h-10">이름</td>
|
|
<td className="border border-gray-300 px-3 py-2 text-center h-10">이름</td>
|
|
<td className="border border-gray-300 px-3 py-2 text-center h-10">이름</td>
|
|
</tr>
|
|
<tr>
|
|
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500">부서명</td>
|
|
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500">부서명</td>
|
|
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500">부서명</td>
|
|
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500">부서명</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* 통합 테이블 - 기획서 구조 100% 반영 */}
|
|
<table className="w-full border-collapse border border-gray-300 text-sm">
|
|
<tbody>
|
|
{/* 현장명 */}
|
|
<tr>
|
|
<th className="border border-gray-300 px-4 py-3 bg-gray-50 text-left w-40 font-medium">현장명</th>
|
|
<td className="border border-gray-300 px-4 py-3">{report.siteName || '-'}</td>
|
|
</tr>
|
|
|
|
{/* 거래처 */}
|
|
<tr>
|
|
<th className="border border-gray-300 px-4 py-3 bg-gray-50 text-left font-medium">거래처</th>
|
|
<td className="border border-gray-300 px-4 py-3">{report.partnerName || '-'}</td>
|
|
</tr>
|
|
|
|
{/* 준공 */}
|
|
<tr>
|
|
<th className="border border-gray-300 px-4 py-3 bg-gray-50 text-left font-medium">준공</th>
|
|
<td className="border border-gray-300 px-4 py-3">{formatYearMonth(report.completionDate)}</td>
|
|
</tr>
|
|
|
|
{/* 계약금액 (공급가액) */}
|
|
<tr>
|
|
<th className="border border-gray-300 px-4 py-3 bg-gray-50 text-left font-medium">계약금액 (공급가액)</th>
|
|
<td className="border border-gray-300 px-4 py-3">₩ {formatAmount(report.contractAmount)}</td>
|
|
</tr>
|
|
|
|
{/* 계약 ITEM - 기획서: 구분, 수량, 비고 3컬럼 */}
|
|
<tr>
|
|
<th className="border border-gray-300 px-4 py-3 bg-gray-50 text-left font-medium align-middle">계약 ITEM</th>
|
|
<td className="border border-gray-300 p-0">
|
|
<table className="w-full border-collapse text-sm">
|
|
<thead>
|
|
<tr className="bg-gray-50">
|
|
<th className="border-b border-gray-300 px-4 py-2 text-left font-medium">구분</th>
|
|
<th className="border-b border-l border-gray-300 px-4 py-2 text-center font-medium w-24">수량</th>
|
|
<th className="border-b border-l border-gray-300 px-4 py-2 text-left font-medium">비고</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{report.contractItems.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={3} className="px-4 py-3 text-center text-gray-400">-</td>
|
|
</tr>
|
|
) : (
|
|
report.contractItems.map((item, idx) => (
|
|
<tr key={item.id}>
|
|
<td className={`px-4 py-2 ${idx > 0 ? 'border-t border-gray-300' : ''}`}>
|
|
{item.name || '-'}
|
|
</td>
|
|
<td className={`px-4 py-2 text-center border-l border-gray-300 ${idx > 0 ? 'border-t' : ''}`}>
|
|
{formatAmount(item.quantity)}
|
|
</td>
|
|
<td className={`px-4 py-2 border-l border-gray-300 ${idx > 0 ? 'border-t' : ''}`}>
|
|
{item.remark || '-'}
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
|
|
{/* 집행유무 - 기획서: 2차 배관 유무, 도장 & 코킹 유무 + 금액 */}
|
|
<tr>
|
|
<th className="border border-gray-300 px-4 py-3 bg-gray-50 text-left font-medium align-middle">집행유무</th>
|
|
<td className="border border-gray-300 p-0">
|
|
<table className="w-full border-collapse text-sm">
|
|
<tbody>
|
|
<tr>
|
|
<th className="px-4 py-2 bg-gray-50 text-left font-medium w-40">2차 배관 유무</th>
|
|
<td className="px-4 py-2">
|
|
{report.hasSecondaryPiping
|
|
? `포함 (${formatAmount(report.secondaryPipingAmount)})`
|
|
: '미포함'}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th className="border-t border-gray-300 px-4 py-2 bg-gray-50 text-left font-medium">도장 & 코킹 유무</th>
|
|
<td className="border-t border-gray-300 px-4 py-2">
|
|
{report.hasCoating
|
|
? `포함 (${formatAmount(report.coatingAmount)})`
|
|
: '미포함'}
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
|
|
{/* 장비 외 실행금액 - 기획서: 운반비, 양중장비, 공과금 */}
|
|
<tr>
|
|
<th className="border border-gray-300 px-4 py-3 bg-gray-50 text-left font-medium align-middle">장비 외 실행금액</th>
|
|
<td className="border border-gray-300 px-4 py-3">
|
|
<div className="space-y-1">
|
|
<div>운반비 : {formatAmount(report.externalEquipmentCost?.shippingCost)}</div>
|
|
<div>양중장비 : {formatAmount(report.externalEquipmentCost?.highAltitudeWork)}</div>
|
|
<div>공과금 : {formatAmount(report.externalEquipmentCost?.publicExpense)}</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
|
|
{/* 특이사항 - 기획서: 내용 단일 필드 */}
|
|
<tr>
|
|
<th className="border border-gray-300 px-4 py-3 bg-gray-50 text-left font-medium align-middle">특이사항</th>
|
|
<td className="border border-gray-300 px-4 py-3 whitespace-pre-wrap">
|
|
{report.specialNotes || '-'}
|
|
</td>
|
|
</tr>
|
|
|
|
{/* 공사 담당자 - 기획서: 성명, 서명, 미이행 사유 3컬럼 */}
|
|
<tr>
|
|
<th className="border border-gray-300 px-4 py-3 bg-gray-50 text-left font-medium align-middle">공사 담당자</th>
|
|
<td className="border border-gray-300 p-0">
|
|
<table className="w-full border-collapse text-sm">
|
|
<thead>
|
|
<tr className="bg-gray-50">
|
|
<th className="border-b border-gray-300 px-4 py-2 text-center font-medium w-28">성명</th>
|
|
<th className="border-b border-l border-gray-300 px-4 py-2 text-center font-medium">서명</th>
|
|
<th className="border-b border-l border-gray-300 px-4 py-2 text-center font-medium w-32">미이행 사유</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{report.constructionManagers.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={3} className="px-4 py-3 text-center text-gray-400">-</td>
|
|
</tr>
|
|
) : (
|
|
report.constructionManagers.map((manager, idx) => (
|
|
<tr key={manager.id}>
|
|
<td className={`px-4 py-2 text-center ${idx > 0 ? 'border-t border-gray-300' : ''}`}>
|
|
{manager.name || '-'}
|
|
</td>
|
|
<td className={`px-4 py-2 text-center border-l border-gray-300 ${idx > 0 ? 'border-t' : ''}`}>
|
|
{manager.signature || ''}
|
|
</td>
|
|
<td className={`px-4 py-2 text-center border-l border-gray-300 ${idx > 0 ? 'border-t' : ''}`}>
|
|
{manager.nonPerformanceReason || ''}
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
} |