feat:은행거래+전표 통합 테이블 (입금/출금/잔액/분개 통합, 기간 빠른선택)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
김보곤
2026-02-11 14:58:55 +09:00
parent 389faf4948
commit ffd2e8f4dd

View File

@@ -893,156 +893,363 @@ className="px-2.5 py-1 text-xs font-medium bg-amber-100 text-amber-700 rounded-f
};
// ============================================================
// JournalEntryList (전표 목록 - 수동/은행거래 통합)
// JournalEntryList (은행거래 + 수동전표 통합 목록)
// ============================================================
const JournalEntryList = ({ refreshKey, onEdit }) => {
const [entries, setEntries] = useState([]);
const JournalEntryList = ({ accountCodes, tradingPartners, onPartnerAdded, refreshKey, onEdit }) => {
const [transactions, setTransactions] = useState([]);
const [journalEntries, setJournalEntries] = useState([]);
const [loading, setLoading] = useState(false);
const [accounts, setAccounts] = useState([]);
const [journalStats, setJournalStats] = useState({});
const [dateRange, setDateRange] = useState({
start: getKoreanDate(-30),
end: getKoreanDate(),
start: getKoreanDate(-30).replace(/-/g, ''),
end: getKoreanDate().replace(/-/g, ''),
});
const [sourceFilter, setSourceFilter] = useState('all');
const [selectedAccount, setSelectedAccount] = useState('');
const [viewFilter, setViewFilter] = useState('all'); // all, bank, manual, unjournaled
const fetchEntries = async () => {
// 분개 모달 상태 (은행거래 → 전표 생성)
const [showJournalModal, setShowJournalModal] = useState(false);
const [modalTransaction, setModalTransaction] = useState(null);
const setMonthRange = (offset) => {
const now = new Date();
const target = new Date(now.getFullYear(), now.getMonth() + offset, 1);
const start = target.toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' }).replace(/-/g, '');
const endDate = offset === 0
? getKoreanDate().replace(/-/g, '')
: new Date(target.getFullYear(), target.getMonth() + 1, 0).toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' }).replace(/-/g, '');
setDateRange({ start, end: endDate });
};
const fetchData = async () => {
setLoading(true);
try {
const params = new URLSearchParams({ start_date: dateRange.start, end_date: dateRange.end });
const res = await fetch(`/finance/journal-entries/list?${params}`);
const data = await res.json();
if (data.success) {
setEntries(data.data || []);
const bankParams = new URLSearchParams({ startDate: dateRange.start, endDate: dateRange.end });
if (selectedAccount) bankParams.set('accountNum', selectedAccount);
const journalParams = new URLSearchParams({
start_date: formatDate(dateRange.start),
end_date: formatDate(dateRange.end),
});
const [bankRes, journalRes] = await Promise.all([
fetch(`/finance/journal-entries/bank-transactions?${bankParams}`),
fetch(`/finance/journal-entries/list?${journalParams}`),
]);
const bankData = await bankRes.json();
const journalData = await journalRes.json();
if (bankData.success) {
setTransactions(bankData.data?.logs || []);
setJournalStats(bankData.data?.journalStats || {});
if (bankData.data?.accounts) setAccounts(bankData.data.accounts);
}
if (journalData.success) {
setJournalEntries(journalData.data || []);
}
} catch (err) {
console.error('전표 목록 조회 실패:', err);
console.error('데이터 조회 실패:', err);
notify('데이터 조회 중 오류가 발생했습니다.', 'error');
} finally {
setLoading(false);
}
};
useEffect(() => { fetchEntries(); }, [refreshKey]);
useEffect(() => { fetchData(); }, [refreshKey]);
const manualCount = entries.filter(e => !e.source_type || e.source_type === 'manual').length;
const bankCount = entries.filter(e => e.source_type === 'bank_transaction').length;
// 전표 맵 생성 (journalId → entry)
const journalMap = {};
journalEntries.forEach(je => { journalMap[je.id] = je; });
const filtered = entries.filter(e => {
if (sourceFilter === 'manual') return !e.source_type || e.source_type === 'manual';
if (sourceFilter === 'bank') return e.source_type === 'bank_transaction';
// 통합 행 생성
const bankLinkedJournalIds = new Set();
const rows = [];
// 1) 은행거래 행
transactions.forEach(tx => {
const je = tx.journalId ? journalMap[tx.journalId] : null;
if (tx.journalId) bankLinkedJournalIds.add(tx.journalId);
rows.push({
type: 'bank',
key: `bank-${tx.uniqueKey}`,
date: tx.transDate,
time: tx.transTime,
description: tx.summary || '-',
deposit: parseFloat(tx.deposit) || 0,
withdraw: parseFloat(tx.withdraw) || 0,
balance: tx.balance,
hasJournal: tx.hasJournal,
journalId: tx.journalId,
entryNo: tx.journalEntryNo || (je && je.entry_no) || null,
lines: je ? je.lines : [],
totalDebit: je ? je.total_debit : 0,
totalCredit: je ? je.total_credit : 0,
bankTx: tx,
sortKey: tx.transDate + (tx.transTime || '000000'),
});
});
// 2) 수동 전표 행 (은행거래에 연결되지 않은 것만)
journalEntries.forEach(je => {
if (bankLinkedJournalIds.has(je.id)) return;
if (je.source_type === 'bank_transaction') return;
rows.push({
type: 'manual',
key: `manual-${je.id}`,
date: je.entry_date.replace(/-/g, ''),
time: '',
description: je.description || '-',
deposit: 0,
withdraw: 0,
balance: null,
hasJournal: true,
journalId: je.id,
entryNo: je.entry_no,
lines: je.lines || [],
totalDebit: je.total_debit,
totalCredit: je.total_credit,
bankTx: null,
sortKey: je.entry_date.replace(/-/g, '') + '999999',
});
});
// 최신순 정렬
rows.sort((a, b) => b.sortKey.localeCompare(a.sortKey));
// 필터 적용
const filtered = rows.filter(r => {
if (viewFilter === 'bank') return r.type === 'bank';
if (viewFilter === 'manual') return r.type === 'manual';
if (viewFilter === 'unjournaled') return r.type === 'bank' && !r.hasJournal;
return true;
});
const totalDebit = filtered.reduce((s, e) => s + (e.total_debit || 0), 0);
const totalCredit = filtered.reduce((s, e) => s + (e.total_credit || 0), 0);
// 통계
const bankRows = rows.filter(r => r.type === 'bank');
const manualRows = rows.filter(r => r.type === 'manual');
const depositSum = bankRows.reduce((s, r) => s + r.deposit, 0);
const withdrawSum = bankRows.reduce((s, r) => s + r.withdraw, 0);
const totalDebit = filtered.reduce((s, r) => s + (r.totalDebit || 0), 0);
const totalCredit = filtered.reduce((s, r) => s + (r.totalCredit || 0), 0);
const setMonthRange = (offset) => {
const now = new Date();
const target = new Date(now.getFullYear(), now.getMonth() + offset, 1);
const start = target.toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' });
const end = offset === 0
? getKoreanDate()
: new Date(target.getFullYear(), target.getMonth() + 1, 0).toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' });
setDateRange({ start, end });
const handleJournal = (tx) => {
setModalTransaction(tx);
setShowJournalModal(true);
};
const handleJournalSaved = () => {
setShowJournalModal(false);
setModalTransaction(null);
fetchData();
};
const handleJournalDeleted = () => {
setShowJournalModal(false);
setModalTransaction(null);
fetchData();
};
return (
<div className="mt-6">
<div className="bg-white rounded-xl shadow-sm border border-stone-100 overflow-hidden">
<div className="px-5 py-4 border-b border-stone-100">
<div className="flex flex-wrap items-center gap-3">
<h2 className="text-lg font-bold text-stone-800 flex items-center gap-2">
<FileText className="w-5 h-5 text-emerald-600" /> 전표 목록
</h2>
<div className="flex items-center gap-1">
{[
['all', '전체', entries.length, 'bg-emerald-600'],
['manual', '수동', manualCount, 'bg-purple-600'],
['bank', '은행거래', bankCount, 'bg-blue-600'],
].map(([val, label, count, activeBg]) => (
<button key={val} onClick={() => setSourceFilter(val)}
className={`px-3 py-1 text-xs rounded-full font-medium transition-colors ${sourceFilter === val
? `${activeBg} text-white`
: 'bg-stone-100 text-stone-500 hover:bg-stone-200'}`}>
{label} <span className="font-bold">{count}</span>
</button>
))}
</div>
<div className="flex items-center gap-2 ml-auto">
<input type="date" value={dateRange.start} onChange={(e) => setDateRange(prev => ({ ...prev, start: e.target.value }))}
className="rounded-lg border border-stone-200 px-2.5 py-1.5 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
<span className="text-stone-400">~</span>
<input type="date" value={dateRange.end} onChange={(e) => setDateRange(prev => ({ ...prev, end: e.target.value }))}
className="rounded-lg border border-stone-200 px-2.5 py-1.5 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
<button onClick={() => setMonthRange(0)} className="px-2.5 py-1.5 text-xs bg-emerald-50 text-emerald-600 rounded-lg hover:bg-emerald-100 font-medium">이번달</button>
<button onClick={() => setMonthRange(-1)} className="px-2.5 py-1.5 text-xs bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 font-medium">지난달</button>
<button onClick={fetchEntries} disabled={loading}
className="px-3 py-1.5 text-xs bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 font-medium flex items-center gap-1.5 disabled:opacity-50">
<Search className="w-3.5 h-3.5" /> 조회
<div>
{/* 필터 바 */}
<div className="bg-white rounded-xl shadow-sm border border-stone-100 p-4 mb-4">
<div className="flex flex-wrap items-center gap-3">
<label className="text-sm text-stone-500 font-medium">기간</label>
<input type="date" value={formatDate(dateRange.start)} onChange={(e) => setDateRange(prev => ({ ...prev, start: e.target.value.replace(/-/g, '') }))}
className="rounded-lg border border-stone-200 px-3 py-1.5 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
<span className="text-stone-400">~</span>
<input type="date" value={formatDate(dateRange.end)} onChange={(e) => setDateRange(prev => ({ ...prev, end: e.target.value.replace(/-/g, '') }))}
className="rounded-lg border border-stone-200 px-3 py-1.5 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
<div className="flex items-center gap-1.5">
<button onClick={() => setMonthRange(0)} className="px-2.5 py-1.5 text-xs bg-emerald-50 text-emerald-600 rounded-lg hover:bg-emerald-100 transition-colors font-medium">이번달</button>
<button onClick={() => setMonthRange(-1)} className="px-2.5 py-1.5 text-xs bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">지난달</button>
<button onClick={() => setMonthRange(-2)} className="px-2.5 py-1.5 text-xs bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">D-2</button>
<button onClick={() => setMonthRange(-3)} className="px-2.5 py-1.5 text-xs bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">D-3</button>
<button onClick={() => setMonthRange(-4)} className="px-2.5 py-1.5 text-xs bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">D-4</button>
<button onClick={() => setMonthRange(-5)} className="px-2.5 py-1.5 text-xs bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">D-5</button>
</div>
<button onClick={fetchData} disabled={loading}
className="px-4 py-1.5 text-sm bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 transition-colors font-medium flex items-center gap-2 disabled:opacity-50">
<Search className="w-4 h-4" /> 조회
</button>
</div>
<div className="flex flex-wrap items-center gap-3 mt-3">
<label className="text-sm text-stone-500 font-medium">계좌</label>
<select value={selectedAccount} onChange={(e) => setSelectedAccount(e.target.value)}
className="px-3 py-1.5 text-sm border border-stone-200 rounded-lg focus:ring-2 focus:ring-emerald-500 outline-none">
<option value="">전체 계좌</option>
{accounts.map(a => (
<option key={a.bank_account_num} value={a.bank_account_num}>
{a.bank_name} {a.bank_account_num}
</option>
))}
</select>
<label className="text-sm text-stone-500 font-medium ml-2">구분</label>
<div className="flex items-center gap-1">
{[
['all', '전체', rows.length],
['bank', '은행거래', bankRows.length],
['manual', '수동전표', manualRows.length],
['unjournaled', '미분개', bankRows.filter(r => !r.hasJournal).length],
].map(([val, label, count]) => (
<button key={val} onClick={() => setViewFilter(val)}
className={`px-2.5 py-1 text-xs rounded-full font-medium transition-colors ${viewFilter === val
? 'bg-emerald-600 text-white'
: 'bg-stone-100 text-stone-500 hover:bg-stone-200'}`}>
{label} <span className="font-bold">{count}</span>
</button>
))}
</div>
</div>
</div>
{/* 통계 카드 */}
<div className="grid grid-cols-2 lg:grid-cols-5 gap-3 mb-4">
<div className="bg-white rounded-xl shadow-sm border border-stone-100 p-3">
<div className="flex items-center gap-2.5">
<div className="p-1.5 bg-stone-50 rounded-lg"><FileText className="w-4 h-4 text-stone-600" /></div>
<div>
<p className="text-[10px] text-stone-400 leading-tight">전체</p>
<p className="text-base font-bold text-stone-800">{rows.length}</p>
</div>
</div>
</div>
<div className="bg-white rounded-xl shadow-sm border border-stone-100 p-3">
<div className="flex items-center gap-2.5">
<div className="p-1.5 bg-blue-50 rounded-lg"><ArrowDownCircle className="w-4 h-4 text-blue-600" /></div>
<div>
<p className="text-[10px] text-stone-400 leading-tight">입금</p>
<p className="text-base font-bold text-blue-700">{formatCurrency(depositSum)}</p>
</div>
</div>
</div>
<div className="bg-white rounded-xl shadow-sm border border-stone-100 p-3">
<div className="flex items-center gap-2.5">
<div className="p-1.5 bg-red-50 rounded-lg"><ArrowUpCircle className="w-4 h-4 text-red-600" /></div>
<div>
<p className="text-[10px] text-stone-400 leading-tight">출금</p>
<p className="text-base font-bold text-red-700">{formatCurrency(withdrawSum)}</p>
</div>
</div>
</div>
<div className="bg-white rounded-xl shadow-sm border border-stone-100 p-3">
<div className="flex items-center gap-2.5">
<div className="p-1.5 bg-emerald-50 rounded-lg"><CheckCircle className="w-4 h-4 text-emerald-600" /></div>
<div>
<p className="text-[10px] text-stone-400 leading-tight">분개완료</p>
<p className="text-base font-bold text-emerald-700">{journalStats.journaledCount || 0}</p>
</div>
</div>
</div>
<div className="bg-white rounded-xl shadow-sm border border-stone-100 p-3">
<div className="flex items-center gap-2.5">
<div className="p-1.5 bg-amber-50 rounded-lg"><AlertTriangle className="w-4 h-4 text-amber-600" /></div>
<div>
<p className="text-[10px] text-stone-400 leading-tight">미분개</p>
<p className="text-base font-bold text-amber-700">{journalStats.unjournaledCount || 0}</p>
</div>
</div>
</div>
</div>
{/* 통합 테이블 */}
<div className="bg-white rounded-xl shadow-sm border border-stone-100 overflow-hidden">
{loading ? (
<div className="p-8 text-center text-stone-400 text-sm">조회 ...</div>
<div className="p-12 text-center text-stone-400">
<div className="animate-spin w-8 h-8 border-4 border-emerald-200 border-t-emerald-600 rounded-full mx-auto mb-3"></div>
조회 ...
</div>
) : filtered.length === 0 ? (
<div className="p-8 text-center text-stone-400 text-sm">전표가 없습니다.</div>
<div className="p-12 text-center text-stone-400">
<Landmark className="w-12 h-12 mx-auto mb-3 text-stone-300" />
<p>조회된 데이터가 없습니다.</p>
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="bg-stone-50 border-b border-stone-200">
<th className="px-4 py-2.5 text-left font-medium text-stone-600 w-[150px]">전표번호</th>
<th className="px-4 py-2.5 text-left font-medium text-stone-600 w-[100px]">전표일자</th>
<th className="px-4 py-2.5 text-left font-medium text-stone-600">적요</th>
<th className="px-4 py-2.5 text-left font-medium text-stone-600">분개 내역</th>
<th className="px-4 py-2.5 text-right font-medium text-stone-600 w-[110px]">차변</th>
<th className="px-4 py-2.5 text-right font-medium text-stone-600 w-[110px]">대변</th>
<th className="px-4 py-2.5 text-center font-medium text-stone-600 w-[50px]"></th>
<th className="px-3 py-2.5 text-left font-medium text-stone-600 w-[80px]">날짜</th>
<th className="px-3 py-2.5 text-left font-medium text-stone-600">적요</th>
<th className="px-3 py-2.5 text-right font-medium text-stone-600 w-[100px]">입금</th>
<th className="px-3 py-2.5 text-right font-medium text-stone-600 w-[100px]">출금</th>
<th className="px-3 py-2.5 text-right font-medium text-stone-600 w-[100px]">잔액</th>
<th className="px-3 py-2.5 text-left font-medium text-stone-600">분개 내역</th>
<th className="px-3 py-2.5 text-right font-medium text-stone-600 w-[90px]">차변</th>
<th className="px-3 py-2.5 text-right font-medium text-stone-600 w-[90px]">대변</th>
<th className="px-3 py-2.5 text-center font-medium text-stone-600 w-[60px]">분개</th>
</tr>
</thead>
<tbody>
{filtered.map(entry => (
<tr key={entry.id} className="border-b border-stone-100 hover:bg-stone-50/50 transition-colors group">
<td className="px-4 py-2.5">
<div className="flex items-center gap-2">
<span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${entry.source_type === 'bank_transaction' ? 'bg-blue-500' : 'bg-purple-500'}`}></span>
<span className="font-mono text-emerald-700 font-medium text-xs">{entry.entry_no}</span>
{filtered.map(row => (
<tr key={row.key} className={`border-b border-stone-100 hover:bg-stone-50/50 transition-colors group ${row.type === 'manual' ? 'bg-purple-50/30' : ''}`}>
<td className="px-3 py-2 text-stone-600 text-xs whitespace-nowrap">
<div>{formatDate(row.date)}</div>
{row.time && <div className="text-stone-400 text-[10px]">{formatTime(row.time)}</div>}
</td>
<td className="px-3 py-2 text-stone-800 text-xs max-w-[200px]">
<div className="flex items-center gap-1.5">
<span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${row.type === 'manual' ? 'bg-purple-500' : 'bg-blue-500'}`}></span>
<span className="truncate" title={row.description}>{row.description}</span>
</div>
</td>
<td className="px-4 py-2.5 text-stone-600 text-xs">{entry.entry_date}</td>
<td className="px-4 py-2.5 text-stone-600 text-xs max-w-[180px] truncate">{entry.description || '-'}</td>
<td className="px-4 py-2">
<div className="space-y-0.5">
{entry.lines.map(l => (
<div key={l.id} className="flex items-center gap-1.5 text-xs">
<span className={`px-1 py-0.5 rounded text-[10px] font-bold leading-none ${l.dc_type === 'debit' ? 'bg-blue-50 text-blue-600' : 'bg-red-50 text-red-600'}`}>
{l.dc_type === 'debit' ? '차' : '대'}
</span>
<span className="text-stone-500">{l.account_name}</span>
<span className="font-medium text-stone-700">{formatCurrency(l.debit_amount || l.credit_amount)}</span>
{l.trading_partner_name && <span className="text-stone-400">({l.trading_partner_name})</span>}
</div>
))}
</div>
<td className="px-3 py-2 text-right font-medium text-blue-600 text-xs">
{row.deposit > 0 ? formatCurrency(row.deposit) : ''}
</td>
<td className="px-4 py-2.5 text-right font-medium text-blue-700 text-xs">{formatCurrency(entry.total_debit)}</td>
<td className="px-4 py-2.5 text-right font-medium text-red-700 text-xs">{formatCurrency(entry.total_credit)}</td>
<td className="px-4 py-2.5 text-center">
<button onClick={() => onEdit(entry.id)}
className="p-1 text-stone-300 group-hover:text-stone-400 hover:!text-emerald-600 hover:bg-emerald-50 rounded transition-colors" title="수정">
<Edit3 className="w-3.5 h-3.5" />
</button>
<td className="px-3 py-2 text-right font-medium text-red-600 text-xs">
{row.withdraw > 0 ? formatCurrency(row.withdraw) : ''}
</td>
<td className="px-3 py-2 text-right text-stone-500 text-xs">
{row.balance !== null ? formatCurrency(row.balance) : ''}
</td>
<td className="px-3 py-2">
{row.hasJournal && row.lines.length > 0 ? (
<div className="space-y-0.5">
{row.lines.map((l, i) => (
<div key={l.id || i} className="flex items-center gap-1 text-[11px]">
<span className={`px-1 py-0.5 rounded text-[9px] font-bold leading-none ${l.dc_type === 'debit' ? 'bg-blue-50 text-blue-600' : 'bg-red-50 text-red-600'}`}>
{l.dc_type === 'debit' ? '차' : '대'}
</span>
<span className="text-stone-500 truncate max-w-[80px]">{l.account_name}</span>
<span className="font-medium text-stone-700">{formatCurrency(l.debit_amount || l.credit_amount)}</span>
</div>
))}
</div>
) : row.hasJournal ? (
<span className="text-emerald-600 text-xs">분개완료</span>
) : null}
</td>
<td className="px-3 py-2 text-right font-medium text-blue-700 text-xs">
{row.totalDebit > 0 ? formatCurrency(row.totalDebit) : ''}
</td>
<td className="px-3 py-2 text-right font-medium text-red-700 text-xs">
{row.totalCredit > 0 ? formatCurrency(row.totalCredit) : ''}
</td>
<td className="px-3 py-2 text-center">
{row.type === 'bank' && !row.hasJournal ? (
<button onClick={() => handleJournal(row.bankTx)}
className="px-2 py-0.5 text-[10px] font-medium bg-amber-100 text-amber-700 rounded-full hover:bg-amber-200 transition-colors">
분개
</button>
) : row.hasJournal ? (
<button onClick={() => row.type === 'bank' ? handleJournal(row.bankTx) : onEdit(row.journalId)}
className="p-1 text-stone-300 group-hover:text-emerald-500 hover:bg-emerald-50 rounded transition-colors" title="수정">
<Edit3 className="w-3.5 h-3.5" />
</button>
) : null}
</td>
</tr>
))}
</tbody>
<tfoot>
<tr className="bg-stone-50 border-t-2 border-stone-300">
<td colSpan={4} className="px-4 py-2.5 text-xs text-stone-500 font-medium">
합계 ({filtered.length})
</td>
<td className="px-4 py-2.5 text-right font-bold text-blue-700 text-sm">{formatCurrency(totalDebit)}</td>
<td className="px-4 py-2.5 text-right font-bold text-red-700 text-sm">{formatCurrency(totalCredit)}</td>
<td colSpan={2} className="px-3 py-2.5 text-xs text-stone-500 font-medium">합계 ({filtered.length})</td>
<td className="px-3 py-2.5 text-right font-bold text-blue-700 text-xs">{formatCurrency(filtered.reduce((s, r) => s + r.deposit, 0))}</td>
<td className="px-3 py-2.5 text-right font-bold text-red-700 text-xs">{formatCurrency(filtered.reduce((s, r) => s + r.withdraw, 0))}</td>
<td></td>
<td></td>
<td className="px-3 py-2.5 text-right font-bold text-blue-700 text-xs">{formatCurrency(totalDebit)}</td>
<td className="px-3 py-2.5 text-right font-bold text-red-700 text-xs">{formatCurrency(totalCredit)}</td>
<td></td>
</tr>
</tfoot>
@@ -1050,6 +1257,19 @@ className="p-1 text-stone-300 group-hover:text-stone-400 hover:!text-emerald-600
</div>
)}
</div>
{/* 분개 모달 (은행거래 → 전표 생성/수정) */}
{showJournalModal && modalTransaction && (
<JournalEntryModal
transaction={modalTransaction}
accountCodes={accountCodes}
tradingPartners={tradingPartners}
onClose={() => { setShowJournalModal(false); setModalTransaction(null); }}
onSaved={handleJournalSaved}
onDeleted={handleJournalDeleted}
onPartnerAdded={onPartnerAdded}
/>
)}
</div>
);
};
@@ -1912,13 +2132,10 @@ className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-s
</div>
</div>
<BankTransactionTab
<JournalEntryList
accountCodes={accountCodes}
tradingPartners={tradingPartners}
onPartnerAdded={handlePartnerAdded}
/>
<JournalEntryList
refreshKey={journalListRefreshKey}
onEdit={handleEditEntry}
/>