Files
accounted/components/import/BankFileUploadStep.tsx
T
Jakob Wennberg bbb82866ee feat: add 4 new Swedish bank CSV parsers and improve transaction categorization
Add auto-detecting CSV parsers for Länsförsäkringar, ICA Banken, Skandia,
and Lunar. Refine SEB detection to avoid false matches. Update bank file
upload UI with new bank options and export instructions. Include booking
templates, improved AI categorization, and transaction review enhancements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:38:44 +01:00

269 lines
9.5 KiB
TypeScript

'use client'
import { useState, useCallback } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Progress } from '@/components/ui/progress'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import {
Upload,
FileText,
AlertCircle,
CheckCircle,
Building2,
HelpCircle,
} from 'lucide-react'
import type { BankFileFormatId } from '@/lib/import/bank-file/types'
const FORMAT_NAMES: Record<string, string> = {
nordea: 'Nordea',
seb: 'SEB',
swedbank: 'Swedbank',
handelsbanken: 'Handelsbanken',
lansforsakringar: 'Länsförsäkringar',
ica_banken: 'ICA Banken',
skandia: 'Skandia',
lunar: 'Lunar',
generic_csv: 'CSV (manuell mappning)',
camt053: 'ISO 20022 camt.053',
}
interface BankFileUploadStepProps {
onFileSelect: (file: File, formatOverride?: BankFileFormatId) => void
isLoading: boolean
error: string | null
detectedFormat?: string | null
detectedFormatName?: string | null
}
export default function BankFileUploadStep({
onFileSelect,
isLoading,
error,
detectedFormat,
detectedFormatName,
}: BankFileUploadStepProps) {
const [isDragging, setIsDragging] = useState(false)
const [selectedFile, setSelectedFile] = useState<File | null>(null)
const [formatOverride, setFormatOverride] = useState<BankFileFormatId | undefined>(undefined)
const acceptedExtensions = '.csv,.txt,.xml'
const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault()
setIsDragging(true)
}, [])
const handleDragLeave = useCallback((e: React.DragEvent) => {
e.preventDefault()
setIsDragging(false)
}, [])
const handleDrop = useCallback((e: React.DragEvent) => {
e.preventDefault()
setIsDragging(false)
const files = e.dataTransfer.files
if (files.length > 0) {
const file = files[0]
const ext = file.name.toLowerCase()
if (ext.endsWith('.csv') || ext.endsWith('.txt') || ext.endsWith('.xml')) {
setSelectedFile(file)
onFileSelect(file, formatOverride)
}
}
}, [onFileSelect, formatOverride])
const handleFileInput = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files
if (files && files.length > 0) {
setSelectedFile(files[0])
onFileSelect(files[0], formatOverride)
}
}, [onFileSelect, formatOverride])
const handleFormatChange = (value: string) => {
const format = value === 'auto' ? undefined : value as BankFileFormatId
setFormatOverride(format)
if (selectedFile) {
onFileSelect(selectedFile, format)
}
}
return (
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Upload className="h-5 w-5" />
Ladda upp kontoutdrag
</CardTitle>
<CardDescription>
Exportera transaktioner som CSV eller XML från din internetbank och ladda upp filen.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* Format override */}
<div className="flex items-center gap-3">
<label className="text-sm font-medium whitespace-nowrap">Bank/format:</label>
<Select
value={formatOverride || 'auto'}
onValueChange={handleFormatChange}
>
<SelectTrigger className="w-64">
<SelectValue placeholder="Automatisk identifiering" />
</SelectTrigger>
<SelectContent>
<SelectItem value="auto">Automatisk identifiering</SelectItem>
<SelectItem value="nordea">Nordea</SelectItem>
<SelectItem value="seb">SEB</SelectItem>
<SelectItem value="swedbank">Swedbank</SelectItem>
<SelectItem value="handelsbanken">Handelsbanken</SelectItem>
<SelectItem value="lansforsakringar">Länsförsäkringar</SelectItem>
<SelectItem value="ica_banken">ICA Banken</SelectItem>
<SelectItem value="skandia">Skandia</SelectItem>
<SelectItem value="lunar">Lunar</SelectItem>
<SelectItem value="camt053">ISO 20022 camt.053 (XML)</SelectItem>
<SelectItem value="generic_csv">Annan CSV (manuell mappning)</SelectItem>
</SelectContent>
</Select>
</div>
{/* Drop zone */}
<div
className={`
relative border-2 border-dashed rounded-lg p-8 text-center transition-colors
${isDragging ? 'border-primary bg-primary/5' : 'border-muted-foreground/25'}
${error ? 'border-destructive bg-destructive/5' : ''}
${isLoading ? 'pointer-events-none opacity-50' : 'cursor-pointer hover:border-primary/50'}
`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={() => document.getElementById('bank-file-input')?.click()}
>
<input
id="bank-file-input"
type="file"
accept={acceptedExtensions}
className="hidden"
onChange={handleFileInput}
disabled={isLoading}
/>
{isLoading ? (
<div className="space-y-4">
<FileText className="mx-auto h-12 w-12 text-muted-foreground animate-pulse" />
<p className="text-muted-foreground">Analyserar fil...</p>
<Progress value={33} className="w-48 mx-auto" />
</div>
) : selectedFile && detectedFormat ? (
<div className="space-y-4">
<CheckCircle className="mx-auto h-12 w-12 text-green-600" />
<div>
<p className="font-medium">{selectedFile.name}</p>
<p className="text-sm text-muted-foreground">
{(selectedFile.size / 1024).toFixed(1)} KB
</p>
<Badge variant="secondary" className="mt-2">
<Building2 className="mr-1 h-3 w-3" />
{detectedFormatName || FORMAT_NAMES[detectedFormat] || detectedFormat}
</Badge>
</div>
</div>
) : (
<div className="space-y-4">
<Upload className="mx-auto h-12 w-12 text-muted-foreground" />
<div>
<p className="font-medium">Dra och släpp bankfil här</p>
<p className="text-sm text-muted-foreground">
CSV, TXT eller XML (max 10 MB)
</p>
</div>
</div>
)}
</div>
{/* Error display */}
{error && (
<div className="p-4 bg-destructive/10 border border-destructive/20 rounded-lg flex gap-3">
<AlertCircle className="h-5 w-5 text-destructive flex-shrink-0 mt-0.5" />
<div>
<p className="font-medium text-destructive">Kunde inte läsa filen</p>
<p className="text-sm text-muted-foreground">{error}</p>
</div>
</div>
)}
</CardContent>
</Card>
{/* Bank export instructions */}
<Card className="bg-muted/50">
<CardHeader>
<CardTitle className="text-base flex items-center gap-2">
<HelpCircle className="h-4 w-4" />
exporterar du från din bank
</CardTitle>
</CardHeader>
<CardContent className="text-sm space-y-3">
<div>
<p className="font-medium">Nordea</p>
<p className="text-muted-foreground">
Logga in Konton Välj konto Transaktioner Exportera (CSV)
</p>
</div>
<div>
<p className="font-medium">SEB</p>
<p className="text-muted-foreground">
Logga in Konton Kontoutdrag Hämta som fil (CSV)
</p>
</div>
<div>
<p className="font-medium">Swedbank</p>
<p className="text-muted-foreground">
Logga in Konton Transaktioner Exportera kontoutdrag (CSV)
</p>
</div>
<div>
<p className="font-medium">Handelsbanken</p>
<p className="text-muted-foreground">
Logga in Konton Transaktioner Ladda ner (CSV)
</p>
</div>
<div>
<p className="font-medium">Länsförsäkringar</p>
<p className="text-muted-foreground">
Logga in Konton Kontoutdrag Exportera (CSV)
</p>
</div>
<div>
<p className="font-medium">ICA Banken</p>
<p className="text-muted-foreground">
Logga in Konton Transaktioner Exportera till fil (CSV)
</p>
</div>
<div>
<p className="font-medium">Skandia</p>
<p className="text-muted-foreground">
Logga in Konton Transaktioner Exportera (CSV)
</p>
</div>
<div>
<p className="font-medium">Lunar</p>
<p className="text-muted-foreground">
Logga in Konto Transaktioner Exportera (CSV)
</p>
</div>
</CardContent>
</Card>
</div>
)
}