'use client'; import { useState, useCallback, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { Plus, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { DatePicker } from '@/components/ui/date-picker'; import { Label } from '@/components/ui/label'; import { CurrencyInput } from '@/components/ui/currency-input'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { toast } from 'sonner'; import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate'; import { billConfig } from './billConfig'; import type { BillRecord, BillType, BillStatus, InstallmentRecord } from './types'; import { BILL_TYPE_OPTIONS, getBillStatusOptions, } from './types'; import { getBill, createBill, updateBill, deleteBill, getClients } from './actions'; // ===== Props ===== interface BillDetailProps { billId: string; mode: 'view' | 'edit' | 'new'; } // ===== 거래처 타입 ===== interface ClientOption { id: string; name: string; } export function BillDetail({ billId, mode }: BillDetailProps) { const router = useRouter(); const isViewMode = mode === 'view'; const isNewMode = mode === 'new'; // ===== 로딩 상태 ===== const [isLoading, setIsLoading] = useState(false); // ===== 거래처 목록 ===== const [clients, setClients] = useState([]); // ===== 폼 상태 ===== const [billNumber, setBillNumber] = useState(''); const [billType, setBillType] = useState('received'); const [vendorId, setVendorId] = useState(''); const [amount, setAmount] = useState(0); const [issueDate, setIssueDate] = useState(''); const [maturityDate, setMaturityDate] = useState(''); const [status, setStatus] = useState('stored'); const [note, setNote] = useState(''); const [installments, setInstallments] = useState([]); // ===== 거래처 목록 로드 ===== useEffect(() => { async function loadClients() { const result = await getClients(); if (result.success && result.data) { setClients(result.data.map(c => ({ id: String(c.id), name: c.name }))); } } loadClients(); }, []); // ===== 데이터 로드 ===== useEffect(() => { async function loadBill() { if (!billId || billId === 'new') return; setIsLoading(true); const result = await getBill(billId); setIsLoading(false); if (result.success && result.data) { const data = result.data; setBillNumber(data.billNumber); setBillType(data.billType); setVendorId(data.vendorId); setAmount(data.amount); setIssueDate(data.issueDate); setMaturityDate(data.maturityDate); setStatus(data.status); setNote(data.note); setInstallments(data.installments); } else { toast.error(result.error || '어음 정보를 불러올 수 없습니다.'); router.push('/ko/accounting/bills'); } } loadBill(); }, [billId, router]); // ===== 저장 핸들러 ===== const handleSubmit = useCallback(async (): Promise<{ success: boolean; error?: string }> => { // 유효성 검사 if (!billNumber.trim()) { toast.error('어음번호를 입력해주세요.'); return { success: false, error: '어음번호를 입력해주세요.' }; } if (!vendorId) { toast.error('거래처를 선택해주세요.'); return { success: false, error: '거래처를 선택해주세요.' }; } if (amount <= 0) { toast.error('금액을 입력해주세요.'); return { success: false, error: '금액을 입력해주세요.' }; } if (!issueDate) { toast.error('발행일을 입력해주세요.'); return { success: false, error: '발행일을 입력해주세요.' }; } if (!maturityDate) { toast.error('만기일을 입력해주세요.'); return { success: false, error: '만기일을 입력해주세요.' }; } // 차수 유효성 검사 for (let i = 0; i < installments.length; i++) { const inst = installments[i]; if (!inst.date) { const errorMsg = `차수 ${i + 1}번의 일자를 입력해주세요.`; toast.error(errorMsg); return { success: false, error: errorMsg }; } if (inst.amount <= 0) { const errorMsg = `차수 ${i + 1}번의 금액을 입력해주세요.`; toast.error(errorMsg); return { success: false, error: errorMsg }; } } const billData: Partial = { billNumber, billType, vendorId, vendorName: clients.find(c => c.id === vendorId)?.name || '', amount, issueDate, maturityDate, status, note, installments, }; let result; if (isNewMode) { result = await createBill(billData); } else { result = await updateBill(billId, billData); } if (result.success) { toast.success(isNewMode ? '어음이 등록되었습니다.' : '어음이 수정되었습니다.'); if (isNewMode) { router.push('/ko/accounting/bills'); } else { router.push(`/ko/accounting/bills/${billId}?mode=view`); } return { success: true }; } else { toast.error(result.error || '저장에 실패했습니다.'); return { success: false, error: result.error || '저장에 실패했습니다.' }; } }, [billId, billNumber, billType, vendorId, amount, issueDate, maturityDate, status, note, installments, router, isNewMode, clients]); // ===== 삭제 핸들러 ===== const handleDelete = useCallback(async (): Promise<{ success: boolean; error?: string }> => { const result = await deleteBill(billId); if (result.success) { toast.success('어음이 삭제되었습니다.'); router.push('/ko/accounting/bills'); return { success: true }; } else { toast.error(result.error || '삭제에 실패했습니다.'); return { success: false, error: result.error || '삭제에 실패했습니다.' }; } }, [billId, router]); // ===== 차수 추가 ===== const handleAddInstallment = useCallback(() => { const newInstallment: InstallmentRecord = { id: `inst-${Date.now()}`, date: '', amount: 0, note: '', }; setInstallments(prev => [...prev, newInstallment]); }, []); // ===== 차수 삭제 ===== const handleRemoveInstallment = useCallback((id: string) => { setInstallments(prev => prev.filter(inst => inst.id !== id)); }, []); // ===== 차수 업데이트 ===== const handleUpdateInstallment = useCallback((id: string, field: keyof InstallmentRecord, value: string | number) => { setInstallments(prev => prev.map(inst => inst.id === id ? { ...inst, [field]: value } : inst )); }, []); // ===== 상태 옵션 (구분에 따라 변경) ===== const statusOptions = getBillStatusOptions(billType); // ===== 폼 콘텐츠 렌더링 ===== const renderFormContent = () => ( <> {/* 기본 정보 섹션 */} 기본 정보
{/* 어음번호 */}
setBillNumber(e.target.value)} placeholder="어음번호를 입력해주세요" disabled={isViewMode} />
{/* 구분 */}
{/* 거래처 */}
{/* 금액 */}
setAmount(value ?? 0)} placeholder="금액을 입력해주세요" disabled={isViewMode} />
{/* 발행일 */}
{/* 만기일 */}
{/* 상태 */}
{/* 비고 */}
setNote(e.target.value)} placeholder="비고를 입력해주세요" disabled={isViewMode} />
{/* 차수 관리 섹션 */} * 차수 관리 {!isViewMode && ( )} No 일자 금액 비고 {!isViewMode && 삭제} {installments.length === 0 ? ( 등록된 차수가 없습니다 ) : ( installments.map((inst, index) => ( {index + 1} handleUpdateInstallment(inst.id, 'date', date)} disabled={isViewMode} /> handleUpdateInstallment(inst.id, 'amount', value ?? 0)} disabled={isViewMode} className="w-full" /> handleUpdateInstallment(inst.id, 'note', e.target.value)} disabled={isViewMode} className="w-full" /> {!isViewMode && ( )} )) )}
); // ===== 템플릿 모드 및 동적 설정 ===== // IntegratedDetailTemplate: create → "{title} 등록", view → "{title}", edit → "{title} 수정" // view 모드에서 "어음 상세"로 표시하려면 직접 설정 필요 const templateMode = isNewMode ? 'create' : mode; const dynamicConfig = { ...billConfig, title: isViewMode ? '어음 상세' : '어음', actions: { ...billConfig.actions, submitLabel: isNewMode ? '등록' : '저장', }, }; return ( renderFormContent()} renderForm={() => renderFormContent()} /> ); }