import { useEffect, useState, type ReactElement } from 'react'; import type { AccountStep, MonthStep, TaskState } from '../lib/ws.js'; type Props = { sourceId: string; nickname: string; state: TaskState; onNavigateAccounts?: () => void; }; const BADGE_STYLES: Record = { pending: { background: '#e5e7eb', color: '#374151' }, running: { background: '#dbeafe', color: '#1d4ed8' }, done: { background: '#dcfce7', color: '#15803d' }, error: { background: '#fee2e2', color: '#b91c1c' }, blocked: { background: '#fef9c3', color: '#854d0e' }, 'otp-required': { background: '#ede9fe', color: '#6d28d9' }, }; const STATUS_LABELS: Record = { pending: 'Pending', running: 'Running…', done: 'Done', error: 'Error', blocked: 'Blocked', 'otp-required': 'OTP Required', }; const MONTH_PHASE_BADGE: Record = { fetching: { background: '#dbeafe', color: '#1d4ed8', label: '⟳ Fetching' }, fetched: { background: '#e5e7eb', color: '#374151', label: 'Fetched' }, error: { background: '#fee2e2', color: '#b91c1c', label: 'Error' }, uploading: { background: '#fef9c3', color: '#854d0e', label: '⟳ Uploading' }, uploaded: { background: '#dcfce7', color: '#15803d', label: 'Uploaded' }, }; const TXN_PHASE_BADGE: Record = { fetching: { background: '#dbeafe', color: '#1d4ed8', label: '⟳' }, uploading: { background: '#fef9c3', color: '#854d0e', label: '⟳' }, done: { background: '#dcfce7', color: '#15803d', label: '✓' }, }; const VAULT_STATUS_BADGE: Record = { accepted: { background: '#dcfce7', color: '#15803d' }, ignored: { background: '#e5e7eb', color: '#6b7280' }, blocked: { background: '#fee2e2', color: '#b91c1c' }, unknown: { background: '#fef9c3', color: '#854d0e' }, }; function Badge({ style, children, }: { style: { background: string; color: string }; children: React.ReactNode; }) { return ( {children} ); } function MonthStepsTable({ steps }: { steps: MonthStep[] }) { return ( {steps.map(step => { const badge = MONTH_PHASE_BADGE[step.phase] ?? MONTH_PHASE_BADGE['fetching']!; return ( ); })}
Month Status Transactions Inserted Skipped Error
{step.month} {badge.label} {step.transactionCount ?? '—'} {step.inserted ?? '—'} {step.skipped ?? '—'} {step.error ?? ''}
); } function TxnTypeCell({ state, }: { state: { phase: string; count?: number; inserted?: number; skipped?: number } | undefined; }) { if (!state) return —; const badge = TXN_PHASE_BADGE[state.phase] ?? TXN_PHASE_BADGE['fetching']!; return ( {badge.label} {state.phase === 'done' && ( {state.inserted ?? 0}↑ {state.skipped ?? 0}– )} {state.phase === 'uploading' && state.count != null && ( {state.count} )} ); } function AccountStepsTable({ steps }: { steps: AccountStep[] }) { return ( {steps.map(step => { const vaultBadge = step.vaultStatus ? VAULT_STATUS_BADGE[step.vaultStatus] : undefined; return ( ); })}
Account Vault ILS Foreign Swift
{step.accountId} {vaultBadge ? {step.vaultStatus} : '—'}
); } function CollapsibleSection({ label, count, status, children, }: { label: string; count: number; status: string; children: React.ReactNode; }) { const [collapsed, setCollapsed] = useState(false); useEffect(() => { if (status === 'done' || status === 'error' || status === 'blocked') { setCollapsed(true); } }, [status]); if (count === 0) return null; return (
{!collapsed &&
{children}
}
); } export function TaskRow({ sourceId: _sourceId, nickname, state, onNavigateAccounts, }: Props): ReactElement { const [expanded, setExpanded] = useState(false); const badgeStyle = BADGE_STYLES[state.status] ?? BADGE_STYLES['pending']!; return (
{state.status === 'running' ? '⟳ ' : ''} {STATUS_LABELS[state.status] ?? state.status} {nickname} {state.status === 'done' && ( <> ↑ {state.inserted ?? 0} new / {state.skipped ?? 0} skipped {(state.changedTransactions?.length ?? 0) > 0 && ( <> / {state.changedTransactions!.length} changed )} {((state.insertedTransactions?.length ?? 0) > 0 || (state.changedTransactions?.length ?? 0) > 0) && ( )} )} {state.status === 'error' && ( )}
{(state.accountSteps?.length ?? 0) > 0 && ( )} {(state.monthSteps?.length ?? 0) > 0 && ( )} {state.status === 'error' && expanded && (
{state.error}
{state.stack && (
{state.stack}
)}
)} {state.status === 'done' && expanded && (
{(state.insertedTransactions?.length ?? 0) > 0 && (
New transactions ({state.insertedTransactions!.length})
{state.insertedTransactions!.map(t => ( ))}
Date Description Amount Account
{t.date ?? '—'} {t.description ?? '—'} {t.amount ?? '—'} {t.account ?? '—'}
)} {(state.changedTransactions?.length ?? 0) > 0 && (
Changed transactions ({state.changedTransactions!.length})
{state.changedTransactions!.map(ct => (
{ct.id}
{ct.changedFields.map(f => ( ))}
Field Old New
{f.field} {f.oldValue ?? '—'} {f.newValue ?? '—'}
))}
)}
)} {state.status === 'blocked' && (
Unknown accounts: {(state.blockedAccounts ?? []).join(', ')}
{onNavigateAccounts ? ( ) : ( Go to Accounts tab )}
)}
); }