'use client' import { useState } from 'react' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { formatDate } from '@/lib/utils' import { getDaysUntilExpiry, isConsentExpiringSoon } from '../lib/api-client' import Link from 'next/link' import { CreditCard, AlertTriangle, RefreshCw, Settings, Trash2, Loader2, CheckCircle, ChevronDown, XCircle, Upload, } from 'lucide-react' import type { BankConnection } from '@/types' interface BankConnectionStatusProps { connection: BankConnection onSync: (connectionId: string) => void onDisconnect: (connectionId: string) => void onReconnect?: (connection: BankConnection, psuType?: 'personal' | 'business') => void onManageAccounts?: (connectionId: string) => void isSyncing?: boolean } export function BankConnectionStatus({ connection, onSync, onDisconnect, onReconnect, onManageAccounts, isSyncing = false, }: BankConnectionStatusProps) { const daysUntilExpiry = getDaysUntilExpiry(connection.consent_expires) const isExpiring = isConsentExpiringSoon(connection.consent_expires) type StatusEntry = { icon: typeof CheckCircle color: string label: string variant: 'success' | 'warning' | 'destructive' | 'secondary' } const statusConfig: Record = { active: { icon: CheckCircle, color: 'text-success', label: 'Aktiv', variant: 'success', }, pending: { icon: Loader2, color: 'text-warning', label: 'Väntar', variant: 'warning', }, expired: { icon: AlertTriangle, color: 'text-warning', label: 'Utgånget samtycke', variant: 'warning', }, error: { icon: XCircle, color: 'text-destructive', label: 'Fel', variant: 'destructive', }, revoked: { icon: XCircle, color: 'text-gray-600', label: 'Bortkopplad', variant: 'secondary', }, } const status = statusConfig[connection.status] || statusConfig.error const StatusIcon = status.icon // Parse accounts from connection const accounts = (connection.accounts_data as Array<{ uid: string iban?: string name?: string currency: string balance?: number balance_updated_at?: string enabled?: boolean }>) || [] const enabledCount = accounts.filter((a) => a.enabled !== false).length const [now] = useState(() => Date.now()) function formatBalanceAge(updatedAt: string): string { const hoursAgo = Math.floor((now - new Date(updatedAt).getTime()) / (1000 * 60 * 60)) if (hoursAgo < 1) return 'Nyss uppdaterat' if (hoursAgo < 24) return `${hoursAgo}h sedan` const daysAgo = Math.floor(hoursAgo / 24) return `${daysAgo}d sedan` } const isConnectionExpired = connection.status === 'expired' const isConnectionError = connection.status === 'error' const errorMessage = connection.error_message ?? '' return (
{/* Header */}

{connection.bank_name}

{status.label} {connection.last_synced_at && ( <> - Synkad {formatDate(connection.last_synced_at)} )}
{(isConnectionExpired || isConnectionError) && onReconnect && ( {/* Let the user pick the account type for the bank login. The server reuses the last-used type by default, but some banks (notably Handelsbanken) only sign with one of them — e.g. an AB owner who signs with a personal Mobile BankID needs "Privatkonto", not the company default "Företagskonto". */} Logga in på banken som onReconnect(connection, 'business')}> Företagskonto onReconnect(connection, 'personal')}> Privatkonto )} {isConnectionError && ( )} {connection.status === 'active' && ( )} {onManageAccounts && ( )}
{/* Error message */} {isConnectionError && errorMessage && ( <>
{errorMessage}
Du kan också importera transaktioner via bankfil
)} {/* Expired consent notice */} {isConnectionExpired && ( <>
PSD2-samtycket har löpt ut. Förnya anslutningen för att återuppta synkroniseringen.
Medan du väntar kan du importera transaktioner via bankfil
)} {/* Consent expiry warning (for active connections) */} {!isConnectionExpired && isExpiring && daysUntilExpiry !== null && (
Samtycket går ut om {daysUntilExpiry} {daysUntilExpiry === 1 ? 'dag' : 'dagar'}. Förnya genom att ansluta igen.
)} {/* Initial backfill summary — shows what the bank actually returned vs what we asked for. */} {connection.initial_sync_completed_at && connection.initial_sync_requested_from && (() => { const requested = connection.initial_sync_requested_from const min = connection.initial_sync_returned_min_date const max = connection.initial_sync_returned_max_date // Truncation = bank returned less history than requested. 7-day grace // for off-by-one + weekend posting differences. let truncated = false if (min && requested) { const requestedTime = new Date(requested).getTime() const minTime = new Date(min).getTime() truncated = (minTime - requestedTime) > 7 * 24 * 60 * 60 * 1000 } return (
Initial historik:{' '} {min ? formatDate(min) : '—'} → {max ? formatDate(max) : '—'} {' '}(begärde {formatDate(requested)}) {truncated && ( Bankens API returnerade kortare period än begärt — använd SIE-import för äldre data )}
) })()} {/* Accounts list */} {accounts.length > 0 && (

Konton

{enabledCount} av {accounts.length} synkas

{accounts.map((account) => { const isDisabled = account.enabled === false return (

{account.name || account.iban || 'Okänt konto'}

{isDisabled && ( Synkas ej )}
{account.iban && (

{account.iban.replace(/(.{4})/g, '$1 ').trim()}

)}
{account.balance !== undefined && (

{new Intl.NumberFormat('sv-SE', { style: 'currency', currency: account.currency, }).format(account.balance)}

{account.balance_updated_at && (

{formatBalanceAge(account.balance_updated_at)}

)}
)}
) })}
)}
) }