diff --git a/.env.example b/.env.example index 16db6c0..98c11c8 100644 --- a/.env.example +++ b/.env.example @@ -49,6 +49,9 @@ NODE_ENV=production # # How many days back to fetch transactions on first sync (default: 90). # SIMPLEFIN_SYNC_DAYS=90 +# +# How often the background auto-sync worker runs (default: 4 hours, minimum: 0.5). +# SIMPLEFIN_SYNC_INTERVAL_HOURS=4 # ── First-run admin account ──────────────────────────────────────────────────── # Set BOTH on first start to create the admin account automatically. diff --git a/client/components/admin/BankSyncAdminCard.jsx b/client/components/admin/BankSyncAdminCard.jsx index 56c2fa4..fafd0be 100644 --- a/client/components/admin/BankSyncAdminCard.jsx +++ b/client/components/admin/BankSyncAdminCard.jsx @@ -5,6 +5,24 @@ import { Button } from '@/components/ui/button'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; import { Toggle } from './adminShared'; +function timeAgo(iso) { + if (!iso) return null; + const secs = Math.floor((Date.now() - new Date(iso).getTime()) / 1000); + if (secs < 60) return 'just now'; + if (secs < 3600) return `${Math.floor(secs / 60)}m ago`; + if (secs < 86400) return `${Math.floor(secs / 3600)}h ago`; + return `${Math.floor(secs / 86400)}d ago`; +} + +function timeUntil(iso) { + if (!iso) return null; + const secs = Math.floor((new Date(iso).getTime() - Date.now()) / 1000); + if (secs <= 0) return 'soon'; + if (secs < 60) return `${secs}s`; + if (secs < 3600) return `${Math.floor(secs / 60)}m`; + return `${Math.floor(secs / 3600)}h`; +} + export default function BankSyncAdminCard() { const [config, setConfig] = useState(null); const [loading, setLoading] = useState(true); @@ -46,6 +64,7 @@ export default function BankSyncAdminCard() { } const changed = enabled !== !!config?.enabled; + const worker = config?.worker; return ( @@ -74,6 +93,33 @@ export default function BankSyncAdminCard() { /> + {/* Auto-sync worker status */} + {config?.enabled && worker && ( +
+

Auto-sync worker

+
+
+

Status

+

+ + {worker.running ? 'Running' : 'Idle'} +

+
+
+

Last run

+

{worker.last_run_at ? timeAgo(worker.last_run_at) : '—'}

+
+
+

Next run

+

{worker.next_run_at ? `in ${timeUntil(worker.next_run_at)}` : '—'}

+
+
+

+ Syncs every {worker.interval_hours}h. Set SIMPLEFIN_SYNC_INTERVAL_HOURS to adjust. +

+
+ )} +