/**
* 首页待办条(spec §7.1):周期记账到期 / 信用卡还款提醒 / 未确认自动账单。
* 三类都无待办时不渲染。
*/
import React, { useMemo } from 'react';
import { Alert, Pressable, StyleSheet, Text, View } from 'react-native';
import { useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import { useTheme } from '../theme';
import { useT } from '../i18n';
import { Card } from './Card';
import { useLedgerStore } from '../store/ledgerStore';
import { useMetadataStore } from '../store/metadataStore';
import { useAutomationStore } from '../store/automationStore';
import { getDueRecurring, instantiateRecurring, calculateNextDueDate } from '../domain/recurring';
import { calculateBillingPeriod } from '../domain/creditCards';
import type { CreditCard } from '../domain/creditCards';
import { toDateString } from '../domain/decimal';
import { shiftAnchor } from '../domain/periodNav';
export function TodoStrip() {
const { theme } = useTheme();
const t = useT();
const router = useRouter();
const addTransaction = useLedgerStore(s => s.addTransaction);
const recurringTransactions = useMetadataStore(s => s.recurringTransactions);
const updateRecurringTransaction = useMetadataStore(s => s.updateRecurringTransaction);
const creditCards = useMetadataStore(s => s.creditCards);
const draftsCount = useAutomationStore(s => s.drafts.length);
const todayStr = useMemo(() => toDateString(new Date()), []);
const weekLater = useMemo(() => shiftAnchor(todayStr, 'weekly', 1), [todayStr]);
// 下一还款日:当月还款日已过则取下月周期(月末跨月场景,filter 与展示共用)
const nextDueDate = (card: CreditCard): string => {
const now = new Date();
const period = calculateBillingPeriod(card, now);
if (period.dueDate >= todayStr) return period.dueDate;
return calculateBillingPeriod(card, new Date(now.getFullYear(), now.getMonth() + 1, 1)).dueDate;
};
// autoConfirm 的由首页自动处理,待办条只列需手动确认的
const dueRecurring = useMemo(
() => getDueRecurring(recurringTransactions, todayStr).filter(r => !r.autoConfirm),
[recurringTransactions, todayStr],
);
const dueCards = useMemo(
() => creditCards.filter(card => {
const due = nextDueDate(card);
return due >= todayStr && due <= weekLater;
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[creditCards, todayStr, weekLater],
);
if (dueRecurring.length === 0 && dueCards.length === 0 && draftsCount === 0) return null;
const handleConfirmRecurring = async (rec: (typeof dueRecurring)[number]) => {
try {
const draft = instantiateRecurring(rec);
await addTransaction(draft);
const nextDueDate = calculateNextDueDate(rec.frequency, rec.interval, rec.nextDueDate);
updateRecurringTransaction(rec.id, { nextDueDate });
Alert.alert(t('home.recurringSuccess'), t('home.recurringSuccessDesc', { name: rec.name }));
} catch (e) {
Alert.alert(t('home.recurringFail'), String(e));
}
};
const renderRow = (icon: keyof typeof Ionicons.glyphMap, text: string, key: string, action?: { label: string; onPress: () => void }) => (
{text}
{action ? (
{action.label}
) : (
)}
);
return (
{dueRecurring.map(rec =>
renderRow('repeat-outline', t('todo.dueRecurring', { name: rec.name }), `rec-${rec.id}`, { label: t('todo.confirm'), onPress: () => handleConfirmRecurring(rec) }),
)}
{dueCards.map(card =>
renderRow('card-outline', t('todo.creditCardDue', { name: card.name, date: nextDueDate(card).slice(5) }), `card-${card.id}`, { label: t('todo.view'), onPress: () => router.push('/credit-card') }),
)}
{draftsCount > 0 &&
renderRow('flash-outline', t('todo.pendingDrafts', { count: draftsCount }), 'drafts', { label: t('todo.view'), onPress: () => router.push('/automation') })}
);
}
const styles = StyleSheet.create({
row: { flexDirection: 'row', alignItems: 'center', gap: 10, paddingVertical: 8, borderBottomWidth: StyleSheet.hairlineWidth },
rowText: { flex: 1 },
actionBtn: { paddingHorizontal: 12, paddingVertical: 5 },
actionText: { fontSize: 12, fontWeight: '700' },
});