Files
sam-react-prod/src/components/business/construction/handover-report/modals/HandoverReportDocumentModal.tsx
byeongcheolryu 387672b5b2 refactor(WEB): URL 경로 juil → construction 변경
- /juil/ 경로를 /construction/으로 변경
- 컴포넌트 폴더명 juil → construction 변경
- 컴포넌트명 Juil* → Construction* 변경
- 테스트 URL 페이지 경로 업데이트
- claudedocs 문서 경로 업데이트

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-08 17:13:22 +09:00

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>
);
}