feat: 전자결재 시스템 구현 (기안함, 결재함, 참조함, 문서상세)
- 기안함(DraftBox): 문서 목록, 상신/삭제, 문서작성 연결 - 결재함(ApprovalBox): 결재 대기 문서 목록, 문서상세 모달 연결 - 참조함(ReferenceBox): 참조 문서 목록, 열람/미열람 처리 - 문서작성(DocumentCreate): 품의서, 지출결의서, 지출예상내역서 폼 - 문서상세(DocumentDetail): 공유 모달, 결재선 박스, 3종 문서 뷰어 - 테이블 번호 컬럼 추가 (1번부터 시작) - sonner toast 적용 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
152
src/components/approval/DocumentCreate/ExpenseEstimateForm.tsx
Normal file
152
src/components/approval/DocumentCreate/ExpenseEstimateForm.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user