'use client' import Link from 'next/link' import { useState } from 'react' import { useTranslations } from 'next-intl' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { AlertTriangle, Download, Loader2, CheckCircle2, ChevronDown, Info } from 'lucide-react' import { useToast } from '@/components/ui/use-toast' import { getErrorMessage } from '@/lib/errors/get-error-message' type PaymentFormat = 'bg_lb' | 'pain001' interface PaymentFilePanelProps { salaryRunId: string periodLabel: string paymentFileFormat: string | null paymentFileGeneratedAt: string | null defaultFormat: PaymentFormat /** company_settings.salary_default_bank: sorts and auto-expands the matching bank's instructions. */ defaultBank?: string | null readOnly?: boolean onDownloaded?: () => void } type BankKey = 'swedbank' | 'seb' | 'handelsbanken' | 'nordea' const BANK_NAME: Record = { swedbank: 'Swedbank', seb: 'SEB', handelsbanken: 'Handelsbanken', nordea: 'Nordea', } // Instruction copy lives in messages/{sv,en}.json under // salary_payments.steps__; this is the ordered key list. const BANKS_BY_FORMAT: Record = { bg_lb: ['swedbank', 'seb', 'handelsbanken', 'nordea'], pain001: ['swedbank', 'seb', 'handelsbanken', 'nordea'], } export function PaymentFilePanel({ salaryRunId, periodLabel, paymentFileFormat, paymentFileGeneratedAt, defaultFormat, defaultBank, readOnly, onDownloaded, }: PaymentFilePanelProps) { const t = useTranslations('salary_payments') const { toast } = useToast() const [format, setFormat] = useState(defaultFormat) const [downloading, setDownloading] = useState(false) const banks = BANKS_BY_FORMAT[format] const matchedBank = banks.find((b) => b === defaultBank) ?? null const sortedBanks = matchedBank ? [matchedBank, ...banks.filter((b) => b !== matchedBank)] : banks const [showInstructions, setShowInstructions] = useState(Boolean(matchedBank)) const FORMAT_LABEL: Record = { bg_lb: t('format_bg_lb'), pain001: t('format_pain001'), } const endpoint = format === 'bg_lb' ? `/api/salary/runs/${salaryRunId}/payment/bg-lb` : `/api/salary/runs/${salaryRunId}/payment/pain001` async function handleDownload() { setDownloading(true) try { const res = await fetch(endpoint) if (!res.ok) { const result = await res.json().catch(() => ({ error: t('download_failed_fallback') })) toast({ title: t('download_failed_title'), description: getErrorMessage(result, { context: 'salary', statusCode: res.status }), variant: 'destructive', }) return } const blob = await res.blob() const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url const ext = format === 'bg_lb' ? 'txt' : 'xml' a.download = `lon_${periodLabel}.${ext}` document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) toast({ title: t('downloaded') }) onDownloaded?.() } finally { setDownloading(false) } } return ( {t('title')} {paymentFileFormat && paymentFileGeneratedAt && (
{t('last_generated')}{' '} {FORMAT_LABEL[paymentFileFormat as PaymentFormat] ?? paymentFileFormat} {' '} ({new Date(paymentFileGeneratedAt).toLocaleString('sv-SE')})
)} {!readOnly && ( <>

{format === 'bg_lb' ? t('format_description_bg_lb') : t('format_description_pain001')}

{format === 'bg_lb' && (
{t('sunset_warning')}{' '} {t('sunset_link')}
)}
{showInstructions && (
{sortedBanks.map((bank) => (
{BANK_NAME[bank]} {bank === matchedBank ? ` (${t('your_bank')})` : ''}. {' '} {t(`steps_${format}_${bank}`)}
))}

{t('instructions_footer')}

)}
{t('open_payments_note')}
)}
) }