import React, { useState } from 'react'; import { Alert, Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { useTheme } from '../../theme'; import { useLedgerStore } from '../../store/ledgerStore'; import { useT } from '../../i18n'; import { Card } from '../../components/Card'; import { FormModal, type FormField } from '../../components/FormModal'; import { addDecimals } from '../../domain/decimal'; import { computeAccountBalances } from '../../domain/ledger'; type TabType = 'assets' | 'liabilities' | 'expenses' | 'income'; export default function AccountScreen() { const { theme } = useTheme(); const t = useT(); const router = useRouter(); const ledger = useLedgerStore(s => s.ledger); const autoOpenAccounts = useLedgerStore(s => s.autoOpenAccounts); const autoCloseAccount = useLedgerStore(s => s.autoCloseAccount); const adjustAccountBalance = useLedgerStore(s => s.adjustAccountBalance); const [tab, setTab] = useState('assets'); const [isAdding, setIsAdding] = useState(false); const [adjustingAccount, setAdjustingAccount] = useState(null); const accounts = Array.from(ledger?.accounts.keys() || []).sort(); // 计算每个账户的余额(包含 balance 断言) const balances = ledger ? computeAccountBalances(ledger) : new Map(); // 根据当前选择的 Tab 过滤账户 const prefix = tab === 'assets' ? 'Assets:' : tab === 'liabilities' ? 'Liabilities:' : tab === 'expenses' ? 'Expenses:' : 'Income:'; const filteredAccounts = accounts.filter(a => a.startsWith(prefix)); const handleCloseAccount = (accountName: string) => { Alert.alert( t('account.confirmClose'), t('account.confirmCloseDesc', { name: accountName }), [ { text: t('common.cancel'), style: 'cancel' }, { text: t('account.btnConfirmClose'), style: 'destructive', onPress: async () => { try { await autoCloseAccount(accountName); Alert.alert(t('account.closeSuccess'), t('account.closeSuccessDesc')); } catch (e) { Alert.alert(t('account.closeFail'), e instanceof Error ? e.message : String(e)); } }, }, ] ); }; const handleAddAccount = async (values: Record) => { const typeVal = values.type?.trim(); const nameVal = values.name?.trim(); const currencyVal = values.currency?.trim() || 'CNY'; if (!typeVal || !nameVal) { Alert.alert(t('account.addFail'), t('account.addFailEmpty')); return; } // 拼接成 Beancount 账户名并清洗特殊字符 const rawAccount = `${typeVal}:${nameVal}`; let sanitized = rawAccount.replace(/:/g, ':'); sanitized = sanitized .split(':') .map((seg, idx) => { if (idx === 0) return seg; // 保留顶级前缀如 Assets // 将中英文括号和中括号转换为 - 连字符 let cleaned = seg.replace(/[\(\)()\[\]]/g, '-'); cleaned = cleaned.replace(/-+/g, '-'); cleaned = cleaned.replace(/^-|-$/g, ''); cleaned = cleaned.replace(/[^\w\u4e00-\u9fa5-]/g, ''); return cleaned; }) .join(':'); try { await autoOpenAccounts([sanitized]); setIsAdding(false); Alert.alert(t('account.openSuccess'), t('account.openSuccessDesc', { name: sanitized })); } catch (e) { Alert.alert(t('account.openFail'), e instanceof Error ? e.message : String(e)); } }; const handleConfirmAdjust = async (values: Record) => { if (!adjustingAccount) return; const balanceVal = values.balance?.trim(); const dateVal = values.date?.trim() || new Date().toISOString().slice(0, 10); if (!balanceVal) { Alert.alert(t('account.adjustFail'), t('account.adjustFailEmpty')); return; } try { await adjustAccountBalance(adjustingAccount, balanceVal, dateVal); setAdjustingAccount(null); Alert.alert(t('account.adjustSuccess'), t('account.adjustSuccessDesc', { name: adjustingAccount, balance: balanceVal, currency: 'CNY' })); } catch (e) { Alert.alert(t('account.adjustFail'), e instanceof Error ? e.message : String(e)); } }; const tabs: { key: TabType; label: string }[] = [ { key: 'assets', label: t('account.tabAssets') }, { key: 'liabilities', label: t('account.tabLiabilities') }, { key: 'expenses', label: t('account.tabExpenses') }, { key: 'income', label: t('account.tabIncome') }, ]; const fields: FormField[] = [ { key: 'type', label: t('account.fieldType'), placeholder: 'Assets / Liabilities / Expenses / Income', defaultValue: tab === 'assets' ? 'Assets' : tab === 'liabilities' ? 'Liabilities' : tab === 'expenses' ? 'Expenses' : 'Income', }, { key: 'name', label: t('account.fieldName'), placeholder: t('account.fieldNamePlaceholder'), defaultValue: '', }, { key: 'currency', label: t('account.fieldCurrency'), placeholder: 'CNY', defaultValue: 'CNY', }, ]; return ( {/* 头部导航 */} router.back()}> {t('account.title')} {/* Tab 选项卡 */} {tabs.map(item => { const isActive = tab === item.key; return ( setTab(item.key)} > {item.label} ); })} {/* 账户列表 */} setIsAdding(true)} style={styles.addBtn}> {t('account.addNew')} {filteredAccounts.map(account => { const shortName = account.slice(prefix.length); const balance = balances.get(account) ?? '0.00'; const currency = ledger?.accounts.get(account)?.currencies[0] || 'CNY'; return ( {t('account.fullName')} {account} {t('account.currentBalance')} {balance} {currency} setAdjustingAccount(account)} style={[styles.actionBtn, { borderColor: theme.colors.border, marginRight: 8 }]} > {t('account.adjustBalance')} handleCloseAccount(account)} style={[styles.closeBtn, { borderColor: theme.colors.border }]} > {t('account.closeAccount')} ); })} {filteredAccounts.length === 0 && ( {t('account.empty')} )} {/* 新开户对话框 */} setIsAdding(false)} /> {/* 调整余额对话框 */} setAdjustingAccount(null)} /> ); } const styles = StyleSheet.create({ page: { flex: 1 }, header: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingTop: 8, paddingBottom: 12 }, tabBar: { flexDirection: 'row', borderBottomWidth: 1, paddingHorizontal: 8 }, tabItem: { flex: 1, alignItems: 'center', paddingVertical: 12 }, content: { padding: 16, gap: 12 }, addBtn: { flexDirection: 'row', alignItems: 'center', marginBottom: 8 }, infoRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 4, alignItems: 'center' }, cardActions: { flexDirection: 'row', justifyContent: 'flex-end', marginTop: 8 }, actionBtn: { flexDirection: 'row', alignItems: 'center', borderWidth: 1, borderRadius: 4, paddingHorizontal: 8, paddingVertical: 4 }, closeBtn: { flexDirection: 'row', alignItems: 'center', borderWidth: 1, borderRadius: 4, paddingHorizontal: 8, paddingVertical: 4 }, });