Files
sam-react-prod/src/components/accounting/TaxInvoiceIssuance/TaxInvoiceForm.tsx

216 lines
8.1 KiB
TypeScript
Raw Normal View History

'use client';
import { useState, useCallback } from 'react';
import { format } from 'date-fns';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label';
import { DatePicker } from '@/components/ui/date-picker';
import { Search } from 'lucide-react';
import { toast } from 'sonner';
import { TaxInvoiceItemTable } from './TaxInvoiceItemTable';
import { createEmptyItem, createEmptyBusinessEntity } from './types';
import type { BusinessEntity, TaxInvoiceItem, TaxInvoiceFormData } from './types';
interface TaxInvoiceFormProps {
supplier: BusinessEntity;
onSubmit: (data: TaxInvoiceFormData) => Promise<void>;
onCancel: () => void;
onVendorSearch: () => void;
selectedVendor: BusinessEntity | null;
}
export function TaxInvoiceForm({
supplier,
onSubmit,
onCancel,
onVendorSearch,
selectedVendor,
}: TaxInvoiceFormProps) {
const [writeDate, setWriteDate] = useState(format(new Date(), 'yyyy-MM-dd'));
const [items, setItems] = useState<TaxInvoiceItem[]>([createEmptyItem()]);
const [memo, setMemo] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const receiver = selectedVendor ?? createEmptyBusinessEntity();
const handleSubmit = useCallback(async () => {
if (!selectedVendor) {
toast.error('공급받는자를 선택하세요.');
return;
}
if (items.length === 0) {
toast.error('품목을 하나 이상 추가하세요.');
return;
}
const hasEmptyItem = items.some((item) => !item.itemName.trim());
if (hasEmptyItem) {
toast.error('품목명을 입력하세요.');
return;
}
setIsSubmitting(true);
try {
await onSubmit({
supplier,
receiver,
writeDate,
items,
memo,
});
} finally {
setIsSubmitting(false);
}
}, [supplier, receiver, writeDate, items, memo, selectedVendor, onSubmit]);
const thClass = 'border border-gray-300 bg-gray-50 px-2 py-1.5 text-left font-medium whitespace-nowrap w-[70px] text-xs';
const tdClass = 'border border-gray-300 px-2 py-1.5 text-sm';
return (
<Card>
<CardContent className="space-y-6 pt-6">
{/* 공급자 / 공급받는자 테이블 */}
<div className="overflow-x-auto">
<table className="w-full border-collapse table-fixed text-sm min-w-[800px]">
<colgroup>
{/* 공급자 */}
<col style={{ width: '30px' }} />
<col style={{ width: '70px' }} />
<col />
<col style={{ width: '70px' }} />
<col />
{/* 공급받는자 */}
<col style={{ width: '30px' }} />
<col style={{ width: '70px' }} />
<col />
<col style={{ width: '70px' }} />
<col />
</colgroup>
<tbody>
{/* Row 1: 등록번호 / 종사업장 */}
<tr>
<td rowSpan={6} className="border border-gray-300 bg-gray-100 text-center font-semibold w-8 align-middle">
<div className="[writing-mode:vertical-rl] mx-auto tracking-widest"></div>
</td>
<th className={thClass}></th>
<td className={tdClass}>{supplier.businessNumber || '-'}</td>
<th className={thClass}></th>
<td className={tdClass}></td>
<td rowSpan={6} className="border border-gray-300 bg-gray-100 text-center font-semibold w-8 align-middle">
<div className="[writing-mode:vertical-rl] mx-auto tracking-widest"></div>
</td>
<th className={thClass}></th>
<td className={tdClass}>
<div className="flex items-center gap-2">
<span className="flex-1">{receiver.businessNumber || ''}</span>
<Button type="button" variant="outline" size="sm" onClick={onVendorSearch} className="h-6 text-xs px-2 shrink-0">
<Search className="h-3 w-3 mr-1" />
</Button>
</div>
</td>
<th className={thClass}></th>
<td className={tdClass}></td>
</tr>
{/* Row 2: 상호 / 대표자 */}
<tr>
<th className={thClass}></th>
<td className={tdClass}>{supplier.companyName || ''}</td>
<th className={thClass}></th>
<td className={tdClass}>{supplier.representativeName || ''}</td>
<th className={thClass}></th>
<td className={tdClass}>{receiver.companyName || ''}</td>
<th className={thClass}></th>
<td className={tdClass}>{receiver.representativeName || ''}</td>
</tr>
{/* Row 3: 사업장주소 */}
<tr>
<th className={thClass}></th>
<td className={tdClass} colSpan={3}>{supplier.address || ''}</td>
<th className={thClass}></th>
<td className={tdClass} colSpan={3}>{receiver.address || ''}</td>
</tr>
{/* Row 4: 업태 / 종목 */}
<tr>
<th className={thClass}></th>
<td className={tdClass}>{supplier.businessType || ''}</td>
<th className={thClass}></th>
<td className={tdClass}>{supplier.businessItem || ''}</td>
<th className={thClass}></th>
<td className={tdClass}>{receiver.businessType || ''}</td>
<th className={thClass}></th>
<td className={tdClass}>{receiver.businessItem || ''}</td>
</tr>
{/* Row 5: 담당자 / 연락처 */}
<tr>
<th className={thClass}></th>
<td className={tdClass}>{supplier.contactName || ''}</td>
<th className={thClass}></th>
<td className={tdClass}>{supplier.contactPhone || ''}</td>
<th className={thClass}></th>
<td className={tdClass}>{receiver.contactName || ''}</td>
<th className={thClass}></th>
<td className={tdClass}>{receiver.contactPhone || ''}</td>
</tr>
{/* Row 6: 이메일 */}
<tr>
<th className={thClass}></th>
<td className={tdClass} colSpan={3}>{supplier.contactEmail || ''}</td>
<th className={thClass}></th>
<td className={tdClass} colSpan={3}>
{receiver.contactEmail || <span className="text-muted-foreground text-xs"> </span>}
</td>
</tr>
</tbody>
</table>
</div>
{/* 작성일자 */}
<div className="max-w-xs">
<Label className="mb-1 block"></Label>
<DatePicker
value={writeDate}
onChange={setWriteDate}
/>
</div>
{/* 품목 테이블 */}
<TaxInvoiceItemTable items={items} onItemsChange={setItems} />
{/* 비고 */}
<div>
<Label className="mb-1 block"></Label>
<Textarea
value={memo}
onChange={(e) => setMemo(e.target.value)}
placeholder="비고 사항을 입력하세요"
rows={3}
/>
</div>
{/* 하단 버튼 */}
<div className="flex justify-end gap-2 pt-2 border-t">
<Button variant="outline" onClick={onCancel} disabled={isSubmitting}>
</Button>
<Button onClick={handleSubmit} disabled={isSubmitting}>
{isSubmitting ? '발행 중...' : '세금계산서 발행'}
</Button>
</div>
</CardContent>
</Card>
);
}