feat: import system improvements, INK2 fix, and Swedish text corrections (#10)
- SIE parser: Windows-1252 and CP437 encoding detection and decoding - Bank file parser: add Nordea Business (Företag) CSV format - Bank file parser: improve format detection for SEB, Länsförsäkringar, generic CSV - INK2 engine: calculate årets resultat (7222) from income statement for open fiscal years - Dashboard: parallel Supabase queries, simplified dashboard page - Fix Swedish characters (å, ä, ö) in BAS data descriptions, validation messages, AI consent disclosures - Import wizard UI improvements across all steps - Migration: add 'bas_range' match type to sie_account_mappings constraint - Extensive new tests for SIE parser encoding and bank file parser Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
a1ca96453c
commit
e8fb84b4fd
@@ -204,7 +204,7 @@ export default function AccountMappingStep({
|
||||
</div>
|
||||
|
||||
{/* Mapping table */}
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
<div className="border rounded-lg overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
@@ -317,11 +317,11 @@ export default function AccountMappingStep({
|
||||
</Card>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={onBack}>
|
||||
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:justify-between">
|
||||
<Button variant="outline" className="min-h-11" onClick={onBack}>
|
||||
Tillbaka
|
||||
</Button>
|
||||
<Button onClick={onContinue} disabled={!canContinue}>
|
||||
<Button className="min-h-11" onClick={onContinue} disabled={!canContinue}>
|
||||
{canContinue ? 'Fortsätt till granskning' : `${stats.unmapped} konton saknar mappning`}
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
@@ -248,7 +248,7 @@ export default function BankFileColumnMappingStep({
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="rounded-md border max-h-64 overflow-y-auto">
|
||||
<div className="rounded-md border max-h-64 overflow-x-auto overflow-y-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
@@ -286,12 +286,12 @@ export default function BankFileColumnMappingStep({
|
||||
)}
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={onBack}>
|
||||
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:justify-between">
|
||||
<Button variant="outline" className="min-h-11" onClick={onBack}>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Tillbaka
|
||||
</Button>
|
||||
<Button onClick={handleConfirm} disabled={!isValid}>
|
||||
<Button className="min-h-11" onClick={handleConfirm} disabled={!isValid}>
|
||||
Fortsätt
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
@@ -112,12 +112,13 @@ export default function BankFileConfirmStep({
|
||||
</Card>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={onBack} disabled={isLoading}>
|
||||
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:justify-between">
|
||||
<Button variant="outline" className="min-h-11" onClick={onBack} disabled={isLoading}>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Tillbaka
|
||||
</Button>
|
||||
<Button
|
||||
className="min-h-11"
|
||||
onClick={() => onExecute({
|
||||
skip_duplicates: true,
|
||||
auto_categorize: false,
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function BankFilePreviewStep({
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Summary cards */}
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center gap-2 text-muted-foreground mb-1">
|
||||
@@ -129,7 +129,7 @@ export default function BankFilePreviewStep({
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="rounded-md border max-h-96 overflow-y-auto">
|
||||
<div className="rounded-md border max-h-96 overflow-x-auto overflow-y-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
@@ -201,12 +201,12 @@ export default function BankFilePreviewStep({
|
||||
)}
|
||||
|
||||
{/* Navigation */}
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={onBack}>
|
||||
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:justify-between">
|
||||
<Button variant="outline" className="min-h-11" onClick={onBack}>
|
||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||
Tillbaka
|
||||
</Button>
|
||||
<Button onClick={onContinue} disabled={hasIssues || transactions.length === 0}>
|
||||
<Button className="min-h-11" onClick={onContinue} disabled={hasIssues || transactions.length === 0}>
|
||||
Fortsätt
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function BankFileResultStep({
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
{isSuccess ? (
|
||||
<>
|
||||
<CheckCircle className="h-6 w-6 text-muted-foreground" />
|
||||
<CheckCircle className="h-6 w-6 text-success" />
|
||||
Import genomförd
|
||||
</>
|
||||
) : (
|
||||
@@ -97,21 +97,19 @@ export default function BankFileResultStep({
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={onNewImport}>
|
||||
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:justify-between">
|
||||
<Button variant="outline" className="min-h-11" onClick={onNewImport}>
|
||||
<RotateCcw className="mr-2 h-4 w-4" />
|
||||
Ny import
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
{isSuccess && (
|
||||
<Button asChild>
|
||||
<Link href="/transactions">
|
||||
Visa transaktioner
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{isSuccess && (
|
||||
<Button className="min-h-11" asChild>
|
||||
<Link href="/transactions">
|
||||
Visa transaktioner
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -23,6 +23,7 @@ import type { BankFileFormatId } from '@/lib/import/bank-file/types'
|
||||
|
||||
const FORMAT_NAMES: Record<string, string> = {
|
||||
nordea: 'Nordea',
|
||||
nordea_business: 'Nordea Företag',
|
||||
seb: 'SEB',
|
||||
swedbank: 'Swedbank',
|
||||
handelsbanken: 'Handelsbanken',
|
||||
@@ -110,18 +111,19 @@ export default function BankFileUploadStep({
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Format override */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3">
|
||||
<label className="text-sm font-medium whitespace-nowrap">Bank/format:</label>
|
||||
<Select
|
||||
value={formatOverride || 'auto'}
|
||||
onValueChange={handleFormatChange}
|
||||
>
|
||||
<SelectTrigger className="w-64">
|
||||
<SelectTrigger className="w-full sm:w-64">
|
||||
<SelectValue placeholder="Automatisk identifiering" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="auto">Automatisk identifiering</SelectItem>
|
||||
<SelectItem value="nordea">Nordea</SelectItem>
|
||||
<SelectItem value="nordea_business">Nordea Företag</SelectItem>
|
||||
<SelectItem value="seb">SEB</SelectItem>
|
||||
<SelectItem value="swedbank">Swedbank</SelectItem>
|
||||
<SelectItem value="handelsbanken">Handelsbanken</SelectItem>
|
||||
@@ -181,7 +183,8 @@ export default function BankFileUploadStep({
|
||||
<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="font-medium hidden sm:block">Dra och släpp bankfil här</p>
|
||||
<p className="font-medium sm:hidden">Tryck för att välja bankfil</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
CSV, TXT eller XML (max 10 MB)
|
||||
</p>
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function ImportResultStep({ result, onNewImport }: ImportResultSt
|
||||
|
||||
{/* Statistics */}
|
||||
{result.success && (
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-3">
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center gap-2 text-muted-foreground mb-1">
|
||||
@@ -183,21 +183,21 @@ export default function ImportResultStep({ result, onNewImport }: ImportResultSt
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={onNewImport}>
|
||||
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:justify-between">
|
||||
<Button variant="outline" className="min-h-11" onClick={onNewImport}>
|
||||
<RotateCcw className="mr-2 h-4 w-4" />
|
||||
Ny import
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-col gap-2 sm:flex-row">
|
||||
{result.success && (
|
||||
<>
|
||||
<Button variant="outline" asChild>
|
||||
<Button variant="outline" className="min-h-11" asChild>
|
||||
<Link href="/bookkeeping">
|
||||
Visa bokföring
|
||||
<ExternalLink className="ml-2 h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild>
|
||||
<Button className="min-h-11" asChild>
|
||||
<Link href="/reports">
|
||||
Visa rapporter
|
||||
<ExternalLink className="ml-2 h-4 w-4" />
|
||||
|
||||
@@ -246,11 +246,11 @@ export default function ImportReviewStep({
|
||||
</Card>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={onBack} disabled={isLoading}>
|
||||
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:justify-between">
|
||||
<Button variant="outline" className="min-h-11" onClick={onBack} disabled={isLoading}>
|
||||
Tillbaka
|
||||
</Button>
|
||||
<Button onClick={handleExecute} disabled={isLoading}>
|
||||
<Button className="min-h-11" onClick={handleExecute} disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
|
||||
@@ -99,7 +99,7 @@ export default function SIEPreviewStep({
|
||||
</Card>
|
||||
|
||||
{/* Statistics */}
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center gap-2 text-muted-foreground mb-1">
|
||||
@@ -201,7 +201,7 @@ export default function SIEPreviewStep({
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Totalt</p>
|
||||
<p className="font-medium">{preview.mappingStatus.total}</p>
|
||||
@@ -303,11 +303,11 @@ export default function SIEPreviewStep({
|
||||
)}
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-between">
|
||||
<Button variant="outline" onClick={onBack}>
|
||||
<div className="flex flex-col-reverse gap-3 sm:flex-row sm:justify-between">
|
||||
<Button variant="outline" className="min-h-11" onClick={onBack}>
|
||||
Tillbaka
|
||||
</Button>
|
||||
<Button onClick={onContinue} disabled={hasBlockingErrors}>
|
||||
<Button className="min-h-11" onClick={onContinue} disabled={hasBlockingErrors}>
|
||||
{preview.mappingStatus.lowConfidence > 0 || preview.mappingStatus.unmapped > 0
|
||||
? 'Granska mappningar'
|
||||
: 'Fortsätt'}
|
||||
|
||||
@@ -103,8 +103,10 @@ export default function SIEUploadStep({ onFileSelect, isLoading, error }: SIEUpl
|
||||
<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 SIE-fil här</p>
|
||||
<p className="text-sm text-muted-foreground">eller klicka för att välja fil</p>
|
||||
<p className="font-medium hidden sm:block">Dra och släpp SIE-fil här</p>
|
||||
<p className="font-medium sm:hidden">Tryck för att välja SIE-fil</p>
|
||||
<p className="text-sm text-muted-foreground hidden sm:block">eller klicka för att välja fil</p>
|
||||
<p className="text-sm text-muted-foreground sm:hidden">.sie eller .se-filer</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user