'use client' import Link from 'next/link' import { useTranslations } from 'next-intl' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { CheckCircle, XCircle, AlertCircle, FileText, ExternalLink, RotateCcw, Info, Undo2, } from 'lucide-react' import { DestructiveConfirmDialog, useDestructiveConfirm, } from '@/components/ui/destructive-confirm-dialog' import { formatCurrency } from '@/lib/utils' import { useCompany } from '@/contexts/CompanyContext' import { ENABLED_EXTENSION_IDS } from '@/lib/extensions/_generated/enabled-extensions' import TheaterCanvas from '@/components/import/TheaterCanvas' import type { ImportPreview, ImportResult } from '@/lib/import/types' import type { TheaterModel } from '@/lib/import/theater-model' interface ImportResultStepProps { result: ImportResult onNewImport: () => void onUndo?: (importId: string) => Promise | void /** When the theater ran, the reveal replaces the plain success header: * the settled constellation beside the personalized story + the bank * bridge. Absent (failure, oversized file, parse miss) = plain header. */ preview?: ImportPreview | null theaterModel?: TheaterModel | null } export default function ImportResultStep({ result, onNewImport, onUndo, preview = null, theaterModel = null, }: ImportResultStepProps) { const t = useTranslations('import') const { dialogProps, confirm } = useDestructiveConfirm() const { isSandbox } = useCompany() const hasBanking = ENABLED_EXTENSION_IDS.has('enable-banking') // The reveal only tells a story that is true: it needs the theater model, // the preview, and actual imported entries (an opening-balances-only run // must not claim "history in place" over an untouched constellation). const showReveal = result.success && !!theaterModel && !!preview && result.journalEntriesCreated > 0 const handleUndoClick = async () => { if (!result.importId || !onUndo) return const ok = await confirm({ title: 'Ångra hela importen?', description: `Detta raderar ${result.journalEntriesCreated} verifikation${result.journalEntriesCreated === 1 ? '' : 'er'} och rensar ingående balanser från den här importen. Bifogade dokument blir okopplade men finns kvar.`, confirmLabel: 'Ångra import', }) if (!ok) return await onUndo(result.importId) } const hasErrors = result.errors.length > 0 const skipped = result.details?.skippedVouchers const untransferred = result.details?.untransferredResults // Filter out raw warnings when we have structured data for them let otherWarnings = skipped && skipped.total > 0 ? result.warnings.filter((w) => !w.includes('hoppades över')) : result.warnings if (untransferred && untransferred.length > 0) { otherWarnings = otherWarnings.filter((w) => !w.includes('förts om till eget kapital')) } return (
{/* "Din historia" reveal (theater ran) or the plain success/failure header */} {showReveal && theaterModel && preview ? (

{t('reveal_eyebrow')}

{t('reveal_title', { years: Math.max(theaterModel.years.length, 1) })}

{t('reveal_stats', { vouchers: result.journalEntriesCreated, accounts: preview.accountCount, counterparties: theaterModel.totalCounterparties, })}

{/* The balance claim comes from the pre-import file check; it stays honest only when no unbalanced voucher was skipped. */} {preview.trialBalance.isBalanced && !(skipped && skipped.unbalanced > 0) && (

{t('reveal_tie_ok')}

)} {skipped && skipped.total > 0 && (

{t('reveal_skipped', { count: skipped.total })}

)}
{/* Sandbox strips live bank connections from /import, so the bridge quiets down to the plain way onward there. */} {!isSandbox &&

{t('reveal_bridge')}

}
{!isSandbox && ( )} {t('reveal_cta_open')}
) : ( {result.success ? ( <> Import genomförd ) : ( <> Import misslyckades )} {result.success ? skipped && skipped.total > 0 ? `Din bokföring har importerats. ${result.journalEntriesCreated} verifikationer skapades, ${skipped.total} hoppades över: se detaljer nedan.` : 'Din bokföring har importerats framgångsrikt.' : 'Det uppstod fel under importen. Läs felmeddelanden nedan för att förstå vad som gick snett och hur du kan åtgärda det.'} )} {/* IB resync notice (prior-year backfill) */} {result.success && result.nextPeriodIBResync && ( Ingående balanser synkades om Eftersom du importerade ett tidigare räkenskapsår uppdaterades ingående balanser för{' '} {result.nextPeriodIBResync.nextPeriodName}{' '} automatiskt (gammal IB makulerad, ny IB skapad från utgående balans). )} {result.success && result.nextPeriodIBResyncSkipped && ( Ingående balanser för {result.nextPeriodIBResyncSkipped.nextPeriodName} kunde inte synkas Nästa räkenskapsår är låst eller stängt. Lås upp perioden och kör importen igen om du vill att ingående balanser ska uppdateras automatiskt. )} {/* Dimensions detected (lossless SIE round-trip, dimensions plan PR5) */} {result.success && result.dimensionsImported && ( Dimensioner följde med importen Filen innehöll kostnadsställen/projekt: {result.dimensionsImported.taggedLines}{' '} taggade rader importerades {result.dimensionsImported.values > 0 && ( <> och {result.dimensionsImported.values} nya värden lades till i registret )} .{' '} {result.dimensionsImported.toggleEnabled && ( <>Dimensioner aktiverades automatiskt för företaget: du hittar registret under{' '} Kostnadsställen & projekt . )} )} {/* Statistics */} {result.success && !showReveal && (
Verifikationer skapade

{result.journalEntriesCreated}

Räkenskapsår
{result.fiscalPeriodId ? ( Skapat ) : ( Befintligt )}
Ingående balanser
{result.openingBalanceEntryId ? ( Importerade ) : ( Inga )}
)} {/* Errors */} {hasErrors && ( Fel ({result.errors.length})
{result.errors.map((error, i) => (
{error}
))}
{!result.success && (

Vad kan du göra?

  • Kontrollera att SIE-filen exporterades korrekt från källsystemet
  • Prova att exportera filen igen och ladda upp på nytt
  • Om felet kvarstår, kontakta support med felmeddelandet ovan
)}
)} {/* Skipped vouchers: structured breakdown */} {skipped && skipped.total > 0 && ( Hoppade över {skipped.total} verifikationer
{skipped.empty > 0 && (

{skipped.empty} tomma verifikationer

Platshållare utan bokföringsrader: vanligt i Fortnox och Visma. Påverkar inte din bokföring.

)} {skipped.unbalanced > 0 && (

{skipped.unbalanced} obalanserade verifikationer

Debet och kredit stämmer inte överens i källsystemet. Saldon har justerats automatiskt.

)} {skipped.singleLine > 0 && (

{skipped.singleLine} enradsverifikationer

Verifikationer med bara en rad (t.ex. periodiseringar). Kräver minst två rader för dubbelbokning.

)} {skipped.unmapped > 0 && (

{skipped.unmapped} verifikationer med ej kopplade konton

Innehåller konton som inte kunde kopplas till din kontoplan.

)}
)} {/* Untransferred prior-year results — the year-end omföring is missing */} {untransferred && untransferred.length > 0 && ( Årets resultat är inte omfört Följande räkenskapsår saknar omföring av årets resultat till eget kapital. Senare års balansräkning visar en differens på beloppet tills omföringen bokförs (konto 8999 mot eget kapital, t.ex. 2099) i respektive år.
{untransferred.map((u) => (
{u.period_name} {formatCurrency(u.pl_net, 'SEK', { minimumFractionDigits: 2 })}
))}
)} {/* Other warnings (filtered) */} {otherWarnings.length > 0 && ( Varningar ({otherWarnings.length})
{otherWarnings.map((warning, i) => (
{warning}
))}
)} {/* Next steps */} {result.success && ( Nästa steg
1

Granska importerade verifikationer

Kontrollera att allt ser korrekt ut i bokföringslistan

2

Verifiera balanserna

Jämför huvudboken med din tidigare bokföring

3

Fortsätt med ny bokföring

Nu kan du börja lägga till nya transaktioner

)} {/* Actions */}
{result.success && result.importId && onUndo && ( )}
{result.success && ( <> )}
) }