"use client"; /** * 세금계산서 작성/수정 폼 * 파일 첨부 기능 포함 */ import { useState, useEffect, useCallback } from "react"; import { format } from "date-fns"; import { Plus, Trash2, Upload, X, FileText, Image, File, Paperclip, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Badge } from "@/components/ui/badge"; import { toast } from "sonner"; import { createTaxInvoice, updateTaxInvoice, getTaxInvoiceById, TaxInvoice, TaxInvoiceAttachment, CreateTaxInvoiceDto, CreateTaxInvoiceItemDto, CostType, costTypeLabels, } from "@/lib/api/taxInvoice"; import { uploadFiles } from "@/lib/api/file"; interface TaxInvoiceFormProps { open: boolean; onClose: () => void; onSave: () => void; invoice?: TaxInvoice | null; } // 품목 초기값 const emptyItem: CreateTaxInvoiceItemDto = { item_date: format(new Date(), "yyyy-MM-dd"), item_name: "", item_spec: "", quantity: 1, unit_price: 0, supply_amount: 0, tax_amount: 0, remarks: "", }; export function TaxInvoiceForm({ open, onClose, onSave, invoice }: TaxInvoiceFormProps) { // 폼 상태 const [formData, setFormData] = useState({ invoice_type: "sales", invoice_date: format(new Date(), "yyyy-MM-dd"), supply_amount: 0, tax_amount: 0, total_amount: 0, items: [{ ...emptyItem }], }); // 첨부파일 상태 const [attachments, setAttachments] = useState([]); const [uploading, setUploading] = useState(false); const [saving, setSaving] = useState(false); const [activeTab, setActiveTab] = useState("basic"); // 수정 모드일 때 데이터 로드 useEffect(() => { if (invoice) { loadInvoiceData(invoice.id); } else { // 새 세금계산서 setFormData({ invoice_type: "sales", invoice_date: format(new Date(), "yyyy-MM-dd"), supply_amount: 0, tax_amount: 0, total_amount: 0, items: [{ ...emptyItem }], }); setAttachments([]); } }, [invoice]); // 세금계산서 데이터 로드 const loadInvoiceData = async (id: string) => { try { const response = await getTaxInvoiceById(id); if (response.success) { const { invoice: inv, items } = response.data; setFormData({ invoice_type: inv.invoice_type, invoice_date: inv.invoice_date?.split("T")[0] || "", supplier_business_no: inv.supplier_business_no, supplier_name: inv.supplier_name, supplier_ceo_name: inv.supplier_ceo_name, supplier_address: inv.supplier_address, supplier_business_type: inv.supplier_business_type, supplier_business_item: inv.supplier_business_item, buyer_business_no: inv.buyer_business_no, buyer_name: inv.buyer_name, buyer_ceo_name: inv.buyer_ceo_name, buyer_address: inv.buyer_address, buyer_email: inv.buyer_email, supply_amount: inv.supply_amount, tax_amount: inv.tax_amount, total_amount: inv.total_amount, remarks: inv.remarks, cost_type: inv.cost_type || undefined, items: items.length > 0 ? items.map((item) => ({ item_date: item.item_date?.split("T")[0] || "", item_name: item.item_name, item_spec: item.item_spec, quantity: item.quantity, unit_price: item.unit_price, supply_amount: item.supply_amount, tax_amount: item.tax_amount, remarks: item.remarks, })) : [{ ...emptyItem }], }); setAttachments(inv.attachments || []); } } catch (error: any) { toast.error("데이터 로드 실패", { description: error.message }); } }; // 필드 변경 const handleChange = (field: keyof CreateTaxInvoiceDto, value: any) => { setFormData((prev) => ({ ...prev, [field]: value })); }; // 품목 변경 const handleItemChange = (index: number, field: keyof CreateTaxInvoiceItemDto, value: any) => { setFormData((prev) => { const items = [...(prev.items || [])]; items[index] = { ...items[index], [field]: value }; // 공급가액 자동 계산 if (field === "quantity" || field === "unit_price") { const qty = field === "quantity" ? value : items[index].quantity; const price = field === "unit_price" ? value : items[index].unit_price; items[index].supply_amount = qty * price; items[index].tax_amount = Math.round(items[index].supply_amount * 0.1); } // 총액 재계산 const totalSupply = items.reduce((sum, item) => sum + (item.supply_amount || 0), 0); const totalTax = items.reduce((sum, item) => sum + (item.tax_amount || 0), 0); return { ...prev, items, supply_amount: totalSupply, tax_amount: totalTax, total_amount: totalSupply + totalTax, }; }); }; // 품목 추가 const handleAddItem = () => { setFormData((prev) => ({ ...prev, items: [...(prev.items || []), { ...emptyItem }], })); }; // 품목 삭제 const handleRemoveItem = (index: number) => { setFormData((prev) => { const items = (prev.items || []).filter((_, i) => i !== index); const totalSupply = items.reduce((sum, item) => sum + (item.supply_amount || 0), 0); const totalTax = items.reduce((sum, item) => sum + (item.tax_amount || 0), 0); return { ...prev, items: items.length > 0 ? items : [{ ...emptyItem }], supply_amount: totalSupply, tax_amount: totalTax, total_amount: totalSupply + totalTax, }; }); }; // 파일 업로드 (화면 관리 파일 업로드 컴포넌트와 동일한 방식 사용) const handleFileUpload = async (e: React.ChangeEvent) => { const files = e.target.files; if (!files || files.length === 0) return; setUploading(true); try { // 화면 관리 파일 업로드 컴포넌트와 동일한 uploadFiles 함수 사용 const response = await uploadFiles({ files: files, tableName: "tax_invoice", fieldName: "attachments", recordId: invoice?.id, docType: "tax-invoice", docTypeName: "세금계산서", }); if (response.success && response.files?.length > 0) { const newAttachments: TaxInvoiceAttachment[] = response.files.map((uploadedFile) => ({ id: uploadedFile.id || `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, file_name: uploadedFile.name, file_path: uploadedFile.serverPath || "", file_size: uploadedFile.size, file_type: uploadedFile.type, uploaded_at: uploadedFile.uploadedAt || new Date().toISOString(), uploaded_by: "", })); setAttachments((prev) => [...prev, ...newAttachments]); toast.success(`${response.files.length}개 파일 업로드 완료`); } } catch (error: any) { toast.error("파일 업로드 실패", { description: error.message }); } finally { setUploading(false); // input 초기화 e.target.value = ""; } }; // 첨부파일 삭제 const handleRemoveAttachment = (id: string) => { setAttachments((prev) => prev.filter((a) => a.id !== id)); }; // 파일 아이콘 const getFileIcon = (fileType: string) => { if (fileType.startsWith("image/")) return ; if (fileType.includes("pdf")) return ; return ; }; // 파일 크기 포맷 const formatFileSize = (bytes: number) => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; }; // 저장 const handleSave = async () => { // 유효성 검사 if (!formData.invoice_date) { toast.error("작성일자를 입력해주세요."); return; } setSaving(true); try { const dataToSave = { ...formData, attachments, }; let response; if (invoice) { response = await updateTaxInvoice(invoice.id, dataToSave); } else { response = await createTaxInvoice(dataToSave); } if (response.success) { toast.success(response.message || "저장되었습니다."); onSave(); } } catch (error: any) { toast.error("저장 실패", { description: error.message }); } finally { setSaving(false); } }; // 금액 포맷 const formatAmount = (amount: number) => { return new Intl.NumberFormat("ko-KR").format(amount); }; return ( !o && onClose()}> {invoice ? "세금계산서 수정" : "세금계산서 작성"} 세금계산서 정보를 입력해주세요.
기본정보 공급자 공급받는자 첨부파일 {attachments.length > 0 && ( {attachments.length} )} {/* 기본정보 탭 */}
handleChange("invoice_date", e.target.value)} className="h-9" />
handleChange("remarks", e.target.value)} className="h-9" placeholder="비고 입력" />
{/* 품목 테이블 */}
품목 내역
일자 품목명 규격 수량 단가 공급가액 세액 {(formData.items || []).map((item, index) => ( handleItemChange(index, "item_date", e.target.value) } className="h-8 text-xs" /> handleItemChange(index, "item_name", e.target.value) } className="h-8 text-xs" placeholder="품목명" /> handleItemChange(index, "item_spec", e.target.value) } className="h-8 text-xs" placeholder="규격" /> handleItemChange(index, "quantity", parseFloat(e.target.value) || 0) } className="h-8 text-right text-xs" min={0} /> handleItemChange( index, "unit_price", parseFloat(e.target.value) || 0 ) } className="h-8 text-right text-xs" min={0} /> {formatAmount(item.supply_amount || 0)} {formatAmount(item.tax_amount || 0)} ))}
{/* 합계 */}
공급가액 {formatAmount(formData.supply_amount || 0)}원
세액 {formatAmount(formData.tax_amount || 0)}원
합계 {formatAmount(formData.total_amount || 0)}원
{/* 공급자 탭 */}
handleChange("supplier_business_no", e.target.value)} className="h-9" placeholder="000-00-00000" />
handleChange("supplier_name", e.target.value)} className="h-9" placeholder="상호명" />
handleChange("supplier_ceo_name", e.target.value)} className="h-9" placeholder="대표자명" />
handleChange("supplier_business_type", e.target.value)} className="h-9" placeholder="업태" />
handleChange("supplier_business_item", e.target.value)} className="h-9" placeholder="종목" />
handleChange("supplier_address", e.target.value)} className="h-9" placeholder="주소" />
{/* 공급받는자 탭 */}
handleChange("buyer_business_no", e.target.value)} className="h-9" placeholder="000-00-00000" />
handleChange("buyer_name", e.target.value)} className="h-9" placeholder="상호명" />
handleChange("buyer_ceo_name", e.target.value)} className="h-9" placeholder="대표자명" />
handleChange("buyer_email", e.target.value)} className="h-9" placeholder="email@example.com" />
handleChange("buyer_address", e.target.value)} className="h-9" placeholder="주소" />
{/* 첨부파일 탭 */} {/* 업로드 영역 */}
{/* 첨부파일 목록 */} {attachments.length > 0 && (
{attachments.map((file) => (
{getFileIcon(file.file_type)}

{file.file_name}

{formatFileSize(file.file_size)}

))}
)} {attachments.length === 0 && (
첨부된 파일이 없습니다.
)}
); }