'use client' import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import { useTranslations } from 'next-intl' import { AlertTriangle, Archive, Loader2 } from 'lucide-react' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { useToast } from '@/components/ui/use-toast' import { getBranding } from '@/lib/branding/service' import { getErrorMessage as getUserErrorMessage } from '@/lib/errors/get-error-message' import { useFormat } from '@/lib/hooks/use-format' import { COMPANY_MIGRATION_RESET_COUNT_KEYS, type CompanyMigrationResetBlocker, type CompanyMigrationResetEligibility, } from '@/types' interface CompanyMigrationResetDialogProps { companyId: string companyName: string open: boolean onOpenChange: (open: boolean) => void } const branding = getBranding() function readApiError(body: unknown, fallback: string): string { if (!body || typeof body !== 'object') return fallback const error = (body as { error?: unknown }).error if (typeof error === 'string') return error if (error && typeof error === 'object') { const message = (error as { message?: unknown }).message if (typeof message === 'string') return message } return fallback } export function CompanyMigrationResetDialog({ companyId, companyName, open, onOpenChange, }: CompanyMigrationResetDialogProps) { const t = useTranslations('settings_company') const router = useRouter() const { toast } = useToast() const { formatDateLong } = useFormat() const loadFailedMessage = t('reset_load_failed') const [eligibility, setEligibility] = useState(null) const [loadError, setLoadError] = useState(null) const [isLoading, setIsLoading] = useState(false) const [isResetting, setIsResetting] = useState(false) const [reason, setReason] = useState('') const [confirmName, setConfirmName] = useState('') const [confirmedNoFilings, setConfirmedNoFilings] = useState(false) const [confirmedArchive, setConfirmedArchive] = useState(false) useEffect(() => { if (!open) return let cancelled = false async function loadEligibility() { setIsLoading(true) setLoadError(null) setEligibility(null) try { const response = await fetch(`/api/company/${companyId}/migration-reset`, { cache: 'no-store', }) const body = await response.json().catch(() => ({})) if (!response.ok) { throw new Error(readApiError(body, loadFailedMessage)) } if (!cancelled) setEligibility(body.data as CompanyMigrationResetEligibility) } catch (error) { if (!cancelled) { setLoadError( error instanceof Error ? getUserErrorMessage(error) : loadFailedMessage, ) } } finally { if (!cancelled) setIsLoading(false) } } void loadEligibility() return () => { cancelled = true } }, [companyId, loadFailedMessage, open]) function resetForm() { setEligibility(null) setLoadError(null) setReason('') setConfirmName('') setConfirmedNoFilings(false) setConfirmedArchive(false) } function handleOpenChange(nextOpen: boolean) { if (isResetting) return onOpenChange(nextOpen) if (!nextOpen) resetForm() } function blockerMessage(blocker: CompanyMigrationResetBlocker): string { switch (blocker.code) { case 'migration_window_expired': return t('reset_blocker_window', { date: eligibility ? formatDateLong(eligibility.window_ends_at) : '', }) case 'sandbox_company': return t('reset_blocker_sandbox') case 'locked_or_closed_periods': return t('reset_blocker_periods', { count: blocker.count }) case 'authority_submission_detected': return t('reset_blocker_filings', { count: blocker.count }) case 'live_bank_connections': return t('reset_blocker_bank_connections', { count: blocker.count }) case 'imports_in_progress': return t('reset_blocker_imports', { count: blocker.count }) case 'active_integrations_or_schedules': return t('reset_blocker_automations', { count: blocker.count }) case 'background_work_in_progress': return t('reset_blocker_background_work', { count: blocker.count }) default: return t('reset_blocker_other') } } function countLabel(key: (typeof COMPANY_MIGRATION_RESET_COUNT_KEYS)[number]): string { switch (key) { case 'journal_entries': return t('reset_count_journal_entries') case 'journal_entry_lines': return t('reset_count_journal_entry_lines') case 'committed_import_entries': return t('reset_count_committed_import_entries') case 'transactions': return t('reset_count_transactions') case 'fiscal_periods': return t('reset_count_fiscal_periods') case 'documents': return t('reset_count_documents') case 'voucher_sequences': return t('reset_count_voucher_sequences') case 'sie_imports': return t('reset_count_sie_imports') case 'bank_file_imports': return t('reset_count_bank_file_imports') case 'skattekonto_file_imports': return t('reset_count_skattekonto_file_imports') case 'bank_connections': return t('reset_count_bank_connections') case 'customers': return t('reset_count_customers') case 'suppliers': return t('reset_count_suppliers') case 'invoices': return t('reset_count_invoices') case 'supplier_invoices': return t('reset_count_supplier_invoices') } } const confirmationName = eligibility?.display_name ?? companyName const canReset = eligibility?.eligible === true && reason.trim().length >= 20 && confirmName.trim() === confirmationName.trim() && confirmedNoFilings && confirmedArchive && !isResetting async function handleReset() { if (!canReset) return setIsResetting(true) try { const response = await fetch(`/api/company/${companyId}/migration-reset`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ confirm_name: confirmName, reason, confirm_no_filed_declarations: confirmedNoFilings, confirm_retained_archive: confirmedArchive, }), }) const body = await response.json().catch(() => ({})) if (!response.ok) { throw new Error(readApiError(body, t('reset_failed_default'))) } toast({ title: t('reset_success_title'), description: t('reset_success_description'), }) setIsResetting(false) onOpenChange(false) resetForm() router.push('/import') router.refresh() } catch (error) { toast({ title: t('reset_failed_title'), description: error instanceof Error ? getUserErrorMessage(error) : t('reset_failed_default'), variant: 'destructive', }) setIsResetting(false) } } return ( {t('reset_dialog_title', { companyName })} {t('reset_dialog_description')} {isLoading ? (
{t('reset_checking')}
) : loadError ? (
{loadError}
) : eligibility ? (

{t('reset_archive_title')}

{t('reset_archive_description')}

{eligibility.blockers.length > 0 ? (
{t('reset_blocked_title')}
    {eligibility.blockers.map((blocker) => (
  • {blockerMessage(blocker)}
  • ))}
) : null}

{t('reset_retained_heading')}

{COMPANY_MIGRATION_RESET_COUNT_KEYS.map((key) => (
{countLabel(key)}
{eligibility.counts[key] ?? 0}
))}
{eligibility.eligible ? ( <>