import { useMemo } from 'react'; import { useStore } from '../store'; import { buildLedger, getAvailableMonths, filterByMonth, formatAmount, calcAccountBalance } from '../utils/bookkeeping'; import { ACCOUNT_TYPE_LABELS } from '../utils/accounts'; import type { AccountType } from '../types'; const TYPE_ORDER: AccountType[] = ['asset', 'liability', 'equity', 'income', 'expense']; export default function LedgerPage() { const { accounts, transactions, openingBalances, selectedMonth, setSelectedMonth, selectedLedgerAccountId: selectedAccountId, setSelectedLedgerAccountId: setSelectedAccountId, previousPage, goBack, hideInactiveLedgerAccounts, setHideInactiveLedgerAccounts, } = useStore(); const availableMonths = useMemo(() => getAvailableMonths(transactions), [transactions]); const effectiveMonth = selectedMonth; const [year, month] = effectiveMonth ? [parseInt(effectiveMonth.split('-')[0]), parseInt(effectiveMonth.split('-')[1])] : [0, 0]; // 対象取引 const targetTxs = useMemo( () => effectiveMonth ? filterByMonth(transactions, year, month) : transactions, [transactions, effectiveMonth, year, month] ); // 勘定科目グループ const accountGroups = useMemo(() => { return TYPE_ORDER.map(type => ({ type, label: ACCOUNT_TYPE_LABELS[type], accounts: accounts .filter(a => a.type === type) .sort((a, b) => a.order - b.order), })); }, [accounts]); const selectedAccount = accounts.find(a => a.id === selectedAccountId); const ledgerLines = useMemo(() => { if (!selectedAccount) return []; const isBsAccount = selectedAccount.type === 'asset' || selectedAccount.type === 'liability' || selectedAccount.type === 'equity'; // B/S科目かつ月フィルター時は「対象月より前の全仕訳による残高」を開始値とする // P/L科目(収益・費用)は月次でリセットされるので期首残高0から始める const startBalance = (isBsAccount && effectiveMonth) ? calcAccountBalance( selectedAccount, transactions.filter(tx => tx.date < `${effectiveMonth}-01`), openingBalances, ) : (isBsAccount ? (openingBalances.find(b => b.accountId === selectedAccountId)?.amount ?? 0) : 0); return buildLedger(selectedAccount, accounts, targetTxs, startBalance); }, [selectedAccount, accounts, targetTxs, openingBalances, selectedAccountId, effectiveMonth, transactions]); // 前月繰越残高(B/S科目かつ月次表示時のみ) const carryOverBalance = useMemo(() => { if (!selectedAccount || !effectiveMonth) return 0; const isBsAccount = selectedAccount.type === 'asset' || selectedAccount.type === 'liability' || selectedAccount.type === 'equity'; if (!isBsAccount) return 0; return calcAccountBalance( selectedAccount, transactions.filter(tx => tx.date < `${effectiveMonth}-01`), openingBalances, ); }, [selectedAccount, effectiveMonth, transactions, openingBalances]); // サマリ(借方合計・貸方合計・残高) const summary = useMemo(() => { const debitTotal = ledgerLines.reduce((s, l) => s + l.debit, 0); const creditTotal = ledgerLines.reduce((s, l) => s + l.credit, 0); const balance = ledgerLines.length > 0 ? ledgerLines[ledgerLines.length - 1].balance : carryOverBalance; return { debitTotal, creditTotal, balance }; }, [ledgerLines, carryOverBalance]); if (transactions.length === 0) { return (

📗

データがありません。CSVをインポートしてください。

); } return (
{/* 左: 勘定科目一覧 */}
{previousPage && ( )}

勘定科目

{accountGroups.map(group => { const visibleAccounts = group.accounts.filter(acc => { const hasActivity = targetTxs.some(tx => tx.entries.some(e => e.accountId === acc.id) ); return !hideInactiveLedgerAccounts || hasActivity; }); if (visibleAccounts.length === 0) return null; return (

{group.label}

{visibleAccounts.map(acc => { const hasActivity = targetTxs.some(tx => tx.entries.some(e => e.accountId === acc.id) ); return ( ); })}
); })}
{/* 右: 元帳明細 */}
{selectedAccount ? ( <>

{selectedAccount.name}

{ACCOUNT_TYPE_LABELS[selectedAccount.type]} / 正規残高: {selectedAccount.normalBalance === 'debit' ? '借方' : '貸方'}

{/* サマリ */}

借方合計

{summary.debitTotal.toLocaleString()}

貸方合計

{summary.creditTotal.toLocaleString()}

残高

= 0 ? 'text-slate-100' : 'text-rose-400'}`}> {formatAmount(summary.balance)}

{/* 前月繰越行(月次表示かつ繰越残高あり) */} {effectiveMonth && carryOverBalance !== 0 && ( )} {ledgerLines.length === 0 ? ( ) : ( ledgerLines.map((line, i) => ( )) )}
日付 摘要 相手勘定 借方 貸方 残高
前月繰越 = 0 ? 'text-slate-400' : 'text-rose-400'}`}> {carryOverBalance.toLocaleString()}
この期間の取引はありません
{line.date} {line.description} {line.counterAccountName} {line.debit > 0 ? line.debit.toLocaleString() : ''} {line.credit > 0 ? line.credit.toLocaleString() : ''} = 0 ? 'text-slate-200' : 'text-rose-400' }`}> {line.balance.toLocaleString()}
) : (

左から勘定科目を選択してください

)}
); }