Files
sam-react-prod/src/components/approval/DocumentCreate/ExpenseEstimateForm.tsx
유병철 71352923c8 refactor(WEB): 회계/차량/결재 등 코드 중복 제거 및 공통 훅 추출
- useAccountingListPage, useDateRange 공통 훅 추출
- accounting/shared/ 공통 컴포넌트 분리
- 회계 모듈(입금/출금/매출/매입/청구 등) 중복 로직 통합
- 차량관리 page.tsx 패턴 간소화
- 건설/결재/자재/출하/단가 등 날짜 관련 코드 공통화
- 코드 중복 제거 체크리스트 문서 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 19:01:41 +09:00

164 lines
6.2 KiB
TypeScript

'use client';
import { Fragment } from 'react';
import { ContentSkeleton } from '@/components/ui/skeleton';
import { Checkbox } from '@/components/ui/checkbox';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import type { ExpenseEstimateData, ExpenseEstimateItem } from './types';
import { formatNumber as formatCurrency } from '@/lib/utils/amount';
interface ExpenseEstimateFormProps {
data: ExpenseEstimateData;
onChange: (data: ExpenseEstimateData) => void;
isLoading?: boolean;
}
export function ExpenseEstimateForm({ data, onChange, isLoading }: ExpenseEstimateFormProps) {
const items = data.items;
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;
// 로딩 상태
if (isLoading) {
return (
<div className="space-y-6">
<div className="bg-white rounded-lg border p-6">
<h3 className="text-lg font-semibold mb-4"> </h3>
<ContentSkeleton type="table" rows={5} />
</div>
</div>
);
}
// 빈 상태
if (items.length === 0) {
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="flex flex-col items-center justify-center py-12 text-muted-foreground">
<p> .</p>
<p className="text-sm mt-1"> .</p>
</div>
</div>
</div>
);
}
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>
);
}