import { useRef, useState } from 'react'; import { Link } from 'react-router-dom'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import Card from '../../components/ui/Card'; import Button from '../../components/ui/Button'; import { ArrowLeft, Download, Upload, Package, Info, Loader2, Check, AlertCircle, Building2, Tag, Clock, Calendar, FileType, FileText, FileCode, } from 'lucide-react'; import api from '../../services/api'; interface PreviewCounts { providers: number; tariffs: number; cancellationPeriods: number; contractDurations: number; contractCategories: number; pdfTemplates: number; appSettings: number; } interface ImportResult { providers: number; tariffs: number; cancellationPeriods: number; contractDurations: number; contractCategories: number; pdfTemplates: number; pdfTemplatesSkipped: number; appSettings: number; warnings: string[]; } export default function FactoryDefaults() { const [downloading, setDownloading] = useState(false); const [downloadError, setDownloadError] = useState(null); const [downloadDone, setDownloadDone] = useState(false); const fileInputRef = useRef(null); const [importing, setImporting] = useState(false); const [importError, setImportError] = useState(null); const [importResult, setImportResult] = useState(null); const queryClient = useQueryClient(); const { data: previewData, isLoading } = useQuery({ queryKey: ['factory-defaults-preview'], queryFn: async () => { const res = await api.get<{ success: boolean; data: { counts: PreviewCounts } }>( '/factory-defaults/preview', ); return res.data; }, }); const counts = previewData?.data?.counts; const handleExport = async () => { setDownloading(true); setDownloadError(null); setDownloadDone(false); try { const res = await api.get('/factory-defaults/export', { responseType: 'blob', }); const blob = new Blob([res.data as BlobPart], { type: 'application/zip' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); const dateStr = new Date().toISOString().split('T')[0]; a.href = url; a.download = `factory-defaults-${dateStr}.zip`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); setDownloadDone(true); setTimeout(() => setDownloadDone(false), 3000); } catch (err) { setDownloadError( err instanceof Error ? err.message : 'Fehler beim Export', ); } finally { setDownloading(false); } }; const sections = counts ? [ { icon: Building2, label: 'Anbieter', count: counts.providers, color: 'text-blue-600' }, { icon: Tag, label: 'Tarife', count: counts.tariffs, color: 'text-indigo-600' }, { icon: Clock, label: 'Kündigungsfristen', count: counts.cancellationPeriods, color: 'text-purple-600' }, { icon: Calendar, label: 'Laufzeiten', count: counts.contractDurations, color: 'text-pink-600' }, { icon: FileType, label: 'Vertragskategorien', count: counts.contractCategories, color: 'text-orange-600' }, { icon: FileText, label: 'PDF-Auftragsvorlagen', count: counts.pdfTemplates, color: 'text-green-600' }, { icon: FileCode, label: 'HTML-Templates', count: counts.appSettings ?? 0, color: 'text-teal-600' }, ] : []; const handleImport = async (file: File) => { setImporting(true); setImportError(null); setImportResult(null); try { const formData = new FormData(); formData.append('zip', file); const res = await api.post<{ success: boolean; data: ImportResult; error?: string }>( '/factory-defaults/import', formData, { headers: { 'Content-Type': 'multipart/form-data' } }, ); if (!res.data.success) { throw new Error(res.data.error || 'Fehler beim Import'); } setImportResult(res.data.data); // Caches invalidieren – neue Anbieter, Tarife, Vorlagen tauchen sofort // an anderer Stelle (Provider-Liste, Vertrag-Anlage, …) auf. queryClient.invalidateQueries(); } catch (err: any) { setImportError( err?.response?.data?.error || err?.message || 'Fehler beim Import', ); } finally { setImporting(false); if (fileInputRef.current) fileInputRef.current.value = ''; } }; return (

Factory-Defaults

{/* Info-Box */}

Was sind Factory-Defaults?

Das sind Stammdaten-Kataloge wie Anbieter, Tarife, Kündigungsfristen, Vertragskategorien, PDF-Auftragsvorlagen und die HTML-Standardtexte (Datenschutzerklärung, Impressum, Vollmacht-Vorlage, Website-Datenschutz). Du kannst sie exportieren, um sie in anderen OpenCRM-Installationen als Startpunkt zu verwenden.

Nicht enthalten sind Kundendaten, Verträge, Dokumente, E-Mails oder Konfigurationen (SMTP, Secrets) – dafür gibt es den separaten{' '} Datenbank-Backup .

Erstellt ein ZIP mit allen Kataloge-Daten, PDF-Auftragsvorlagen und den HTML-Standardtexten (Datenschutz / Impressum / Vollmacht). In einer anderen OpenCRM-Installation kannst du es dann unten unter Import wieder einspielen.

{isLoading ? (
Lade Übersicht…
) : counts ? (
{sections.map((s) => (
{s.label}
{s.count}
))}
) : null}
{downloadError && (
{downloadError}
)}

Lade hier eine zuvor exportierte Factory-Defaults-ZIP hoch. Bestehende Einträge werden anhand des Unique-Keys (Name / Code) aktualisiert, neue werden angelegt. Es wird nichts gelöscht – der Vorgang ist idempotent.

{ const f = e.target.files?.[0]; if (f) handleImport(f); }} />
Alternativ:{' '} npm run seed:defaults {' '} im Backend
{importResult && (
Import erfolgreich
  • Anbieter: {importResult.providers}
  • Tarife: {importResult.tariffs}
  • Kündigungsfristen: {importResult.cancellationPeriods}
  • Laufzeiten: {importResult.contractDurations}
  • Vertragskategorien: {importResult.contractCategories}
  • PDF-Vorlagen: {importResult.pdfTemplates} {importResult.pdfTemplatesSkipped > 0 && ` (${importResult.pdfTemplatesSkipped} übersprungen)`}
  • HTML-Templates: {importResult.appSettings}
{importResult.warnings.length > 0 && (
Hinweise:
    {importResult.warnings.map((w, i) => (
  • {w}
  • ))}
)}
)} {importError && (
{importError}
)}
); }