diff --git a/hifi-src/src/pages/admin/settings/ExportImportSettings.jsx b/hifi-src/src/pages/admin/settings/ExportImportSettings.jsx index eb559b9..c77f55d 100644 --- a/hifi-src/src/pages/admin/settings/ExportImportSettings.jsx +++ b/hifi-src/src/pages/admin/settings/ExportImportSettings.jsx @@ -1,25 +1,105 @@ import { useState } from 'react'; import { api, API_BASE } from '../../../api/client.js'; +// Gruppierung muss zur Backend-Konstante SettingsController::IMPORT_SECTIONS passen. +// Tabellen innerhalb einer Gruppe haengen per Fremdschluessel voneinander ab, darum +// werden sie beim Import immer zusammen ersetzt (nie einzeln). +const SECTIONS = [ + { + key: 'catalog', + label: 'Fahrzeug-Katalog', + hint: 'Marken, Modelle, Pakete, Produkte, Upgrades', + tables: ['brands', 'car_models', 'packages', 'package_products', 'package_upgrades'], + }, + { key: 'services', label: 'Leistungen', hint: 'Die Leistungen-Übersicht', tables: ['services'] }, + { key: 'faqs', label: 'FAQs', hint: 'Häufig gestellte Fragen', tables: ['faqs'] }, + { + key: 'gallery', + label: 'Bildergalerie', + hint: 'Galerie-Marken, Projekte und Fotos (inkl. Bilder)', + tables: ['gallery_brands', 'gallery_projects', 'gallery_photos'], + }, +]; + +const TABLE_LABELS = { + brands: 'Marken', + car_models: 'Modelle', + packages: 'Pakete', + package_products: 'Produkte', + package_upgrades: 'Upgrades', + services: 'Leistungen', + faqs: 'FAQs', + gallery_brands: 'Galerie-Marken', + gallery_projects: 'Galerie-Projekte', + gallery_photos: 'Galerie-Fotos', +}; + +// Kurze, lesbare Zusammenfassung ("3 Marken, 12 Modelle, 45 Pakete") aus den im Export +// enthaltenen Zeilen einer Sektion - reine Vorschau, keine Validierung. +const summarize = (parsedData, tables) => { + if (!parsedData) return null; + const parts = tables + .map((t) => [t, Array.isArray(parsedData[t]) ? parsedData[t].length : 0]) + .filter(([, count]) => count > 0) + .map(([t, count]) => `${count} ${TABLE_LABELS[t] || t}`); + return parts.length ? parts.join(', ') : 'leer in dieser Datei'; +}; + export default function ExportImportSettings() { const [importFile, setImportFile] = useState(null); + const [parsedData, setParsedData] = useState(null); + const [parseError, setParseError] = useState(''); + const [selectedSections, setSelectedSections] = useState(() => new Set(SECTIONS.map((s) => s.key))); const [importConfirmed, setImportConfirmed] = useState(false); const [importBusy, setImportBusy] = useState(false); const [importResult, setImportResult] = useState(null); const [importError, setImportError] = useState(''); + const handleFileChange = async (e) => { + const file = e.target.files?.[0] || null; + setImportFile(file); + setImportResult(null); + setImportError(''); + setParsedData(null); + setParseError(''); + setSelectedSections(new Set(SECTIONS.map((s) => s.key))); + if (!file) return; + try { + const text = await file.text(); + const parsed = JSON.parse(text); + if (!parsed || typeof parsed.data !== 'object') throw new Error('kein gültiges Export-Format'); + setParsedData(parsed.data); + } catch { + // Vorschau ist nur ein Komfort-Feature - schlaegt sie fehl, importiert der + // Server trotzdem ganz normal alle Bereiche; die eigentliche Validierung + // passiert ohnehin serverseitig. + setParseError('Datei konnte nicht gelesen werden – Vorschau der Inhalte nicht möglich. Import würde trotzdem versucht (alle Bereiche).'); + } + }; + + const toggleSection = (key) => { + setSelectedSections((prev) => { + const next = new Set(prev); + if (next.has(key)) next.delete(key); + else next.add(key); + return next; + }); + }; + const handleImport = async (e) => { e.preventDefault(); - if (!importFile || !importConfirmed) return; + if (!importFile || !importConfirmed || selectedSections.size === 0) return; setImportBusy(true); setImportError(''); setImportResult(null); try { const formData = new FormData(); formData.append('file', importFile); + formData.append('sections', JSON.stringify([...selectedSections])); const result = await api.post('/settings/import', formData); setImportResult(result); setImportFile(null); + setParsedData(null); setImportConfirmed(false); e.target.reset(); } catch (err) { @@ -29,6 +109,8 @@ export default function ExportImportSettings() { } }; + const selectedLabels = SECTIONS.filter((s) => selectedSections.has(s.key)).map((s) => s.label); + return (
@@ -49,18 +131,50 @@ export default function ExportImportSettings() {

Daten importieren

- Lädt eine zuvor exportierte Datei auf diesen Server. Ersetzt dabei alle aktuellen - Marken, Modelle, Pakete, Produkte, Upgrades, Leistungen, FAQs und die komplette Bildergalerie auf - diesem Server – das kann nicht rückgängig gemacht werden. Kontaktanfragen und Benutzerkonten bleiben - unangetastet. + Lädt eine zuvor exportierte Datei auf diesen Server. Ersetzt dabei die ausgewählten Bereiche komplett + durch den Inhalt der Datei – das kann nicht rückgängig gemacht werden. Kontaktanfragen und + Benutzerkonten bleiben immer unangetastet.

-
+ setImportFile(e.target.files?.[0] || null)} + onChange={handleFileChange} className="text-sm" /> + + {importFile && ( +
+
+ Was soll importiert werden? +
+ {parseError &&

{parseError}

} +
+ {SECTIONS.map((section) => ( + + ))} +
+
+ )} +