import { useEffect, useState, useRef } from 'react'; import { api, money, nowMinute, type Position } from './api'; import { AccountPicker } from './AccountPicker'; import { useToast } from './Toast'; import { t as tr } from './i18n'; type Plan = { id: string; name: string; operation: string; amount: string; nextAt: string; intervalDays: number; enabled: boolean; completed: boolean; sourceId: string; targetId: string | null; received: string; notes: string; }; export function SchedulePanel({ accounts, refresh, }: { accounts: Position[]; refresh: () => Promise; }) { const [rows, setRows] = useState([]), [editor, setEditor] = useState(undefined), [busy, setBusy] = useState(false), [error, setError] = useState(''), [status, setStatus] = useState(''); const dialog = useRef(null); useToast(error, 'error'); useToast(status, 'success'); useEffect(() => { if (editor !== undefined) dialog.current?.showModal(); }, [editor]); function openEditor(plan: Plan | null) { setError(''); setOperation(plan?.operation ?? 'expense'); setSource(plan?.sourceId ?? accounts[0]?.id ?? ''); setTarget(plan?.targetId ?? ''); setAmount(plan?.amount ?? ''); setEditor(plan); } const [operation, setOperation] = useState('expense'), [source, setSource] = useState(''), [target, setTarget] = useState(''), [amount, setAmount] = useState(''); const sourceAccounts = operation === 'expense' ? accounts.filter((p) => p.side === 'asset') : accounts; const sourceId = sourceAccounts.some((p) => p.id === source) ? source : sourceAccounts[0]?.id || ''; const sourceAccount = accounts.find((p) => p.id === sourceId), targetAccount = accounts.find((p) => p.id === target); const same = sourceAccount?.currency === targetAccount?.currency; useEffect(() => { let active = true; setRows([]); void api('/schedules') .then((v) => { if (active) setRows(v); }) .catch((e) => { if (active) setError(e.message); }); return () => { active = false; }; }, [accounts]); async function act(work: () => Promise) { setBusy(true); setError(''); setStatus(''); try { await work(); await refresh(); setRows(await api('/schedules')); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } } return (

{tr('定时计划')} ({rows.length})

{tr('打开账户页或点击执行时处理到期计划。每次最多执行20项,离线期间的计划会逐次补记。')}

{editor !== undefined && ( { if (busy) e.preventDefault(); else setEditor(undefined); }} onClose={() => setEditor(undefined)} >

{tr(editor ? '修改计划' : '添加计划')}

{ e.preventDefault(); const data = Object.fromEntries(new FormData(e.currentTarget)); void act(async () => { await api( editor ? '/schedules/' + editor.id : '/schedules', editor ? 'PUT' : 'POST', { ...data, operation, sourceId, targetId: operation === 'transfer' ? target : null, received: operation === 'transfer' ? (same ? amount : data.received) : '0', intervalDays: Number(data.intervalDays), }, ); setEditor(undefined); setStatus(tr('计划已保存')); }); }} > {operation === 'transfer' && ( )} {operation === 'transfer' && !same && ( )}