"use client"; /** * 세금계산서 상세 보기 컴포넌트 * PDF 출력 및 첨부파일 다운로드 기능 포함 */ import { useState, useEffect, useRef } from "react"; import { format } from "date-fns"; import { ko } from "date-fns/locale"; import { Printer, Download, FileText, Image, File, Loader2, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Separator } from "@/components/ui/separator"; import { toast } from "sonner"; import { getTaxInvoiceById, TaxInvoice, TaxInvoiceItem, TaxInvoiceAttachment, } from "@/lib/api/taxInvoice"; import { apiClient } from "@/lib/api/client"; interface TaxInvoiceDetailProps { open: boolean; onClose: () => void; invoiceId: string; } // 상태 라벨 const statusLabels: Record = { draft: "임시저장", issued: "발행완료", sent: "전송완료", cancelled: "취소됨", }; // 상태 색상 const statusColors: Record = { draft: "bg-gray-100 text-gray-800", issued: "bg-green-100 text-green-800", sent: "bg-blue-100 text-blue-800", cancelled: "bg-red-100 text-red-800", }; export function TaxInvoiceDetail({ open, onClose, invoiceId }: TaxInvoiceDetailProps) { const [invoice, setInvoice] = useState(null); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [pdfLoading, setPdfLoading] = useState(false); const printRef = useRef(null); // 데이터 로드 useEffect(() => { if (open && invoiceId) { loadData(); } }, [open, invoiceId]); const loadData = async () => { setLoading(true); try { const response = await getTaxInvoiceById(invoiceId); if (response.success) { setInvoice(response.data.invoice); setItems(response.data.items); } } catch (error: any) { toast.error("데이터 로드 실패", { description: error.message }); } finally { setLoading(false); } }; // 금액 포맷 const formatAmount = (amount: number) => { return new Intl.NumberFormat("ko-KR").format(amount); }; // 날짜 포맷 const formatDate = (dateString: string | null) => { if (!dateString) return "-"; try { return format(new Date(dateString), "yyyy년 MM월 dd일", { locale: ko }); } catch { return dateString; } }; // 파일 미리보기 URL 생성 (objid 기반) - 이미지용 const getFilePreviewUrl = (attachment: TaxInvoiceAttachment) => { // objid가 숫자형이면 API를 통해 미리보기 if (attachment.id && !attachment.id.includes("-")) { // apiClient의 baseURL 사용 const baseURL = apiClient.defaults.baseURL || ""; return `${baseURL}/files/preview/${attachment.id}`; } return attachment.file_path; }; // 공통 인쇄용 HTML 생성 함수 const generatePrintHtml = (autoPrint: boolean = false) => { if (!invoice) return ""; const invoiceTypeText = invoice.invoice_type === "sales" ? "매출" : "매입"; const itemsHtml = items.map((item, index) => ` ${index + 1} ${item.item_date?.split("T")[0] || "-"} ${item.item_name} ${item.item_spec || "-"} ${item.quantity} ${formatAmount(item.unit_price)} ${formatAmount(item.supply_amount)} ${formatAmount(item.tax_amount)} `).join(""); return ` 세금계산서_${invoice.invoice_number}

세금계산서 (${invoiceTypeText})

계산서번호: ${invoice.invoice_number}
${statusLabels[invoice.invoice_status]}

공급자

사업자번호${invoice.supplier_business_no || "-"}
상호${invoice.supplier_name || "-"}
대표자${invoice.supplier_ceo_name || "-"}
업태/종목${invoice.supplier_business_type || "-"} / ${invoice.supplier_business_item || "-"}
주소${invoice.supplier_address || "-"}

공급받는자

사업자번호${invoice.buyer_business_no || "-"}
상호${invoice.buyer_name || "-"}
대표자${invoice.buyer_ceo_name || "-"}
이메일${invoice.buyer_email || "-"}
주소${invoice.buyer_address || "-"}

품목 내역

${itemsHtml || ''}
No 일자 품목명 규격 수량 단가 공급가액 세액
품목 내역이 없습니다.
공급가액${formatAmount(invoice.supply_amount)}원
세액${formatAmount(invoice.tax_amount)}원
합계금액${formatAmount(invoice.total_amount)}원
${invoice.remarks ? `
비고: ${invoice.remarks}
` : ""} ${invoice.attachments && invoice.attachments.length > 0 ? `

첨부파일 (${invoice.attachments.length}개)

    ${invoice.attachments.map(file => `
  • 📄 ${file.file_name}
  • `).join("")}
` : ""}
${autoPrint ? `` : ""} `; }; // 인쇄 const handlePrint = () => { if (!invoice) return; const printWindow = window.open("", "_blank"); if (!printWindow) { toast.error("팝업이 차단되었습니다. 팝업 차단을 해제해주세요."); return; } printWindow.document.write(generatePrintHtml(true)); printWindow.document.close(); }; // PDF 다운로드 (인쇄 다이얼로그 사용) const handleDownloadPdf = async () => { if (!invoice) return; setPdfLoading(true); try { const printWindow = window.open("", "_blank"); if (!printWindow) { toast.error("팝업이 차단되었습니다. 팝업 차단을 해제해주세요."); return; } printWindow.document.write(generatePrintHtml(true)); printWindow.document.close(); toast.success("PDF 인쇄 창이 열렸습니다. 'PDF로 저장'을 선택하세요."); } catch (error: any) { console.error("PDF 생성 오류:", error); toast.error("PDF 생성 실패", { description: error.message }); } finally { setPdfLoading(false); } }; // 파일 아이콘 const getFileIcon = (fileType: string) => { if (fileType.startsWith("image/")) return ; if (fileType.includes("pdf")) return ; return ; }; // 파일 다운로드 (인증 토큰 포함) const handleDownload = async (attachment: TaxInvoiceAttachment) => { try { // objid가 숫자형이면 API를 통해 다운로드 if (attachment.id && !attachment.id.includes("-")) { const response = await apiClient.get(`/files/download/${attachment.id}`, { responseType: "blob", }); // Blob으로 다운로드 const blob = new Blob([response.data]); const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = attachment.file_name; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); } else { // 직접 경로로 다운로드 window.open(attachment.file_path, "_blank"); } } catch (error: any) { toast.error("파일 다운로드 실패", { description: error.message }); } }; if (loading) { return ( !o && onClose()}> 세금계산서 상세
로딩 중...
); } if (!invoice) { return null; } return ( !o && onClose()}> 세금계산서 상세
{/* 헤더 */}

{invoice.invoice_type === "sales" ? "세금계산서 (매출)" : "세금계산서 (매입)"}

계산서번호: {invoice.invoice_number}

{statusLabels[invoice.invoice_status]}
{/* 공급자 / 공급받는자 정보 */}
{/* 공급자 */}

공급자

사업자번호 {invoice.supplier_business_no || "-"}
상호 {invoice.supplier_name || "-"}
대표자 {invoice.supplier_ceo_name || "-"}
업태/종목 {invoice.supplier_business_type || "-"} / {invoice.supplier_business_item || "-"}
주소 {invoice.supplier_address || "-"}
{/* 공급받는자 */}

공급받는자

사업자번호 {invoice.buyer_business_no || "-"}
상호 {invoice.buyer_name || "-"}
대표자 {invoice.buyer_ceo_name || "-"}
이메일 {invoice.buyer_email || "-"}
주소 {invoice.buyer_address || "-"}
{/* 품목 내역 */}

품목 내역

No 일자 품목명 규격 수량 단가 공급가액 세액 {items.length > 0 ? ( items.map((item, index) => ( {index + 1} {item.item_date?.split("T")[0] || "-"} {item.item_name} {item.item_spec || "-"} {item.quantity} {formatAmount(item.unit_price)} {formatAmount(item.supply_amount)} {formatAmount(item.tax_amount)} )) ) : ( 품목 내역이 없습니다. )}
{/* 합계 */}
공급가액 {formatAmount(invoice.supply_amount)}원
세액 {formatAmount(invoice.tax_amount)}원
합계금액 {formatAmount(invoice.total_amount)}원
{/* 비고 */} {invoice.remarks && (

비고

{invoice.remarks}

)} {/* 날짜 정보 */}
작성일: {formatDate(invoice.invoice_date)} {invoice.issue_date && 발행일: {formatDate(invoice.issue_date)}}
{/* 첨부파일 */} {invoice.attachments && invoice.attachments.length > 0 && (

첨부파일 ({invoice.attachments.length}개)

{/* 이미지 미리보기 */} {invoice.attachments.some((f) => f.file_type?.startsWith("image/")) && (
{invoice.attachments .filter((f) => f.file_type?.startsWith("image/")) .map((file) => (
{file.file_name} { (e.target as HTMLImageElement).style.display = "none"; }} />

{file.file_name}

))}
)} {/* 기타 파일 목록 */} {invoice.attachments.some((f) => !f.file_type?.startsWith("image/")) && (
{invoice.attachments .filter((f) => !f.file_type?.startsWith("image/")) .map((file) => (
{getFileIcon(file.file_type)} {file.file_name}
))}
)}
)}
); }