152 lines
6.5 KiB
TypeScript
152 lines
6.5 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { Fragment } from 'react';
|
||
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
||
|
|
import {
|
||
|
|
Table,
|
||
|
|
TableBody,
|
||
|
|
TableCell,
|
||
|
|
TableHead,
|
||
|
|
TableHeader,
|
||
|
|
TableRow,
|
||
|
|
} from '@/components/ui/table';
|
||
|
|
import type { ExpenseEstimateData, ExpenseEstimateItem } from './types';
|
||
|
|
|
||
|
|
interface ExpenseEstimateFormProps {
|
||
|
|
data: ExpenseEstimateData;
|
||
|
|
onChange: (data: ExpenseEstimateData) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Mock 데이터 생성
|
||
|
|
const generateMockEstimateItems = (): ExpenseEstimateItem[] => {
|
||
|
|
return [
|
||
|
|
{ id: '1', checked: false, expectedPaymentDate: '2025-11-12', category: '통신 서비스', amount: 1000000, vendor: '회사명', memo: '국민 1234 홍길동' },
|
||
|
|
{ id: '2', checked: false, expectedPaymentDate: '2025-11-12', category: '인건 대행', amount: 1000000, vendor: '회사명', memo: '국민 1234 홍길동' },
|
||
|
|
{ id: '3', checked: false, expectedPaymentDate: '2025-11-12', category: '통신 서비스', amount: 1000000, vendor: '회사명', memo: '국민 1234 홍길동' },
|
||
|
|
{ id: '4', checked: false, expectedPaymentDate: '2025-11-12', category: '인건 대행', amount: 1000000, vendor: '회사명', memo: '국민 1234 홍길동' },
|
||
|
|
// 11월 소계 후
|
||
|
|
{ id: '5', checked: false, expectedPaymentDate: '2025-12-12', category: '기타서비스 12월분', amount: 1000000, vendor: '회사명', memo: '국민 1234 홍길동' },
|
||
|
|
{ id: '6', checked: false, expectedPaymentDate: '2025-12-12', category: '통신 서비스', amount: 1000000, vendor: '회사명', memo: '국민 1234 홍길동' },
|
||
|
|
];
|
||
|
|
};
|
||
|
|
|
||
|
|
export function ExpenseEstimateForm({ data, onChange }: ExpenseEstimateFormProps) {
|
||
|
|
// Mock 데이터 초기화
|
||
|
|
const items = data.items.length > 0 ? data.items : generateMockEstimateItems();
|
||
|
|
|
||
|
|
const formatCurrency = (amount: number) => {
|
||
|
|
return new Intl.NumberFormat('ko-KR').format(amount);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleCheckChange = (id: string, checked: boolean) => {
|
||
|
|
const newItems = items.map((item) =>
|
||
|
|
item.id === id ? { ...item, checked } : item
|
||
|
|
);
|
||
|
|
const totalExpense = newItems.reduce((sum, item) => sum + item.amount, 0);
|
||
|
|
onChange({
|
||
|
|
...data,
|
||
|
|
items: newItems,
|
||
|
|
totalExpense,
|
||
|
|
finalDifference: data.accountBalance - totalExpense,
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
// 월별 그룹핑
|
||
|
|
const groupedByMonth = items.reduce((acc, item) => {
|
||
|
|
const month = item.expectedPaymentDate.substring(0, 7); // YYYY-MM
|
||
|
|
if (!acc[month]) {
|
||
|
|
acc[month] = [];
|
||
|
|
}
|
||
|
|
acc[month].push(item);
|
||
|
|
return acc;
|
||
|
|
}, {} as Record<string, ExpenseEstimateItem[]>);
|
||
|
|
|
||
|
|
const getMonthSubtotal = (monthItems: ExpenseEstimateItem[]) => {
|
||
|
|
return monthItems.reduce((sum, item) => sum + item.amount, 0);
|
||
|
|
};
|
||
|
|
|
||
|
|
const totalExpense = items.reduce((sum, item) => sum + item.amount, 0);
|
||
|
|
const accountBalance = data.accountBalance || 10000000; // Mock 계좌 잔액
|
||
|
|
const finalDifference = accountBalance - totalExpense;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="space-y-6">
|
||
|
|
{/* 지출 예상 내역서 정보 */}
|
||
|
|
<div className="bg-white rounded-lg border p-6">
|
||
|
|
<h3 className="text-lg font-semibold mb-4">지출 예상 내역서 목록</h3>
|
||
|
|
|
||
|
|
<div className="overflow-x-auto">
|
||
|
|
<Table>
|
||
|
|
<TableHeader>
|
||
|
|
<TableRow>
|
||
|
|
<TableHead className="w-[50px] text-center"></TableHead>
|
||
|
|
<TableHead className="min-w-[120px]">예상 지급일</TableHead>
|
||
|
|
<TableHead className="min-w-[150px]">항목</TableHead>
|
||
|
|
<TableHead className="min-w-[120px] text-right">지출금액</TableHead>
|
||
|
|
<TableHead className="min-w-[100px]">거래처</TableHead>
|
||
|
|
<TableHead className="min-w-[150px]">적록</TableHead>
|
||
|
|
</TableRow>
|
||
|
|
</TableHeader>
|
||
|
|
<TableBody>
|
||
|
|
{Object.entries(groupedByMonth).map(([month, monthItems]) => (
|
||
|
|
<Fragment key={month}>
|
||
|
|
{monthItems.map((item) => (
|
||
|
|
<TableRow key={item.id}>
|
||
|
|
<TableCell className="text-center">
|
||
|
|
<Checkbox
|
||
|
|
checked={item.checked}
|
||
|
|
onCheckedChange={(checked) => handleCheckChange(item.id, !!checked)}
|
||
|
|
/>
|
||
|
|
</TableCell>
|
||
|
|
<TableCell>{item.expectedPaymentDate}</TableCell>
|
||
|
|
<TableCell>{item.category}</TableCell>
|
||
|
|
<TableCell className="text-right text-blue-600 font-medium">
|
||
|
|
{formatCurrency(item.amount)}
|
||
|
|
</TableCell>
|
||
|
|
<TableCell>{item.vendor}</TableCell>
|
||
|
|
<TableCell>{item.memo}</TableCell>
|
||
|
|
</TableRow>
|
||
|
|
))}
|
||
|
|
{/* 월별 소계 */}
|
||
|
|
<TableRow className="bg-pink-50">
|
||
|
|
<TableCell colSpan={2} className="font-medium">
|
||
|
|
{month.replace('-', '년 ')}월 계
|
||
|
|
</TableCell>
|
||
|
|
<TableCell></TableCell>
|
||
|
|
<TableCell className="text-right text-red-600 font-bold">
|
||
|
|
{formatCurrency(getMonthSubtotal(monthItems))}
|
||
|
|
</TableCell>
|
||
|
|
<TableCell colSpan={2}></TableCell>
|
||
|
|
</TableRow>
|
||
|
|
</Fragment>
|
||
|
|
))}
|
||
|
|
|
||
|
|
{/* 합계 행들 */}
|
||
|
|
<TableRow className="bg-gray-50 border-t-2">
|
||
|
|
<TableCell colSpan={3} className="font-semibold">지출 합계</TableCell>
|
||
|
|
<TableCell className="text-right text-red-600 font-bold">
|
||
|
|
{formatCurrency(totalExpense)}
|
||
|
|
</TableCell>
|
||
|
|
<TableCell colSpan={2}></TableCell>
|
||
|
|
</TableRow>
|
||
|
|
<TableRow className="bg-gray-50">
|
||
|
|
<TableCell colSpan={3} className="font-semibold">계좌 잔액</TableCell>
|
||
|
|
<TableCell className="text-right font-bold">
|
||
|
|
{formatCurrency(accountBalance)}
|
||
|
|
</TableCell>
|
||
|
|
<TableCell colSpan={2}></TableCell>
|
||
|
|
</TableRow>
|
||
|
|
<TableRow className="bg-gray-50">
|
||
|
|
<TableCell colSpan={3} className="font-semibold">최종 차액</TableCell>
|
||
|
|
<TableCell className={`text-right font-bold ${finalDifference >= 0 ? 'text-blue-600' : 'text-red-600'}`}>
|
||
|
|
{formatCurrency(finalDifference)}
|
||
|
|
</TableCell>
|
||
|
|
<TableCell colSpan={2}></TableCell>
|
||
|
|
</TableRow>
|
||
|
|
</TableBody>
|
||
|
|
</Table>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|