diff --git a/frontend/src/pages/AdminBlsImportPage.jsx b/frontend/src/pages/AdminBlsImportPage.jsx index 4018841..d7addee 100644 --- a/frontend/src/pages/AdminBlsImportPage.jsx +++ b/frontend/src/pages/AdminBlsImportPage.jsx @@ -1,8 +1,96 @@ import { useEffect, useState } from 'react' import { api } from '../utils/api' -function FileImportBlock({ label, kind, busy, onRun }) { +function summarizeCheck(kind, res) { + if (!res) return '' + if (kind === 'components') return `${res.attributes ?? 0} Stoffe erkannt` + return `${res.foods ?? 0} Lebensmittel, ${res.attribute_columns ?? 0} Stoffspalten` +} + +function ImportSwitch({ on, disabled, busy, onEnable }) { + return ( + + ) +} + +function FileImportBlock({ label, kind, onImported }) { const [file, setFile] = useState(null) + const [check, setCheck] = useState(null) + const [error, setError] = useState(null) + const [busy, setBusy] = useState(null) + const [imported, setImported] = useState(false) + const [applyResult, setApplyResult] = useState(null) + + const apiFn = kind === 'components' ? api.adminBlsImportComponents : api.adminBlsImportFoods + + const checkFile = async (nextFile) => { + setError(null) + setCheck(null) + setApplyResult(null) + setImported(false) + if (!nextFile) return + setBusy('check') + try { + setCheck(await apiFn(nextFile, true)) + } catch (e) { + setError(e.message) + } finally { + setBusy(null) + } + } + + const applyImport = async () => { + if (!file || !check) return + setBusy('apply') + setError(null) + try { + const res = await apiFn(file, false) + setApplyResult(res) + setImported(true) + onImported?.() + } catch (e) { + setError(e.message) + setImported(false) + } finally { + setBusy(null) + } + } + return (
@@ -10,55 +98,50 @@ function FileImportBlock({ label, kind, busy, onRun }) { type="file" accept=".xlsx" className="form-input" - onChange={(e) => setFile(e.target.files?.[0] || null)} + disabled={busy} + onChange={(e) => { + const next = e.target.files?.[0] || null + setFile(next) + checkFile(next) + }} + /> + {busy &&

{busy === 'apply' ? 'Importiere…' : 'Prüfe…'}

} + {error &&

{error}

} + {check && !error && ( +

+ Prüfung: {summarizeCheck(kind, check)} +

+ )} + {applyResult && ( +

+ {applyResult.inserted ?? 0} neu · {applyResult.updated ?? 0} aktualisiert +

+ )} + - {file &&

{file.name}

} -
- - -
) } export default function AdminBlsImportPage() { const [status, setStatus] = useState(null) - const [msg, setMsg] = useState(null) const [error, setError] = useState(null) - const [busy, setBusy] = useState(false) const load = () => { api.adminBlsStatus().then(setStatus).catch((e) => setError(e.message)) } useEffect(() => { load() }, []) - const run = async (kind, file, dry) => { - setError(null) - setBusy(true) - setMsg(dry ? 'Prüfe…' : 'Importiere…') - try { - const fn = kind === 'components' ? api.adminBlsImportComponents : api.adminBlsImportFoods - const res = await fn(file, dry) - setMsg(JSON.stringify(res, null, 2)) - if (!dry) load() - } catch (e) { - setError(e.message) - setMsg(null) - } finally { - setBusy(false) - } - } - return (

BLS 4.0 importieren

Offizielle Dateien von blsdb.de (frei verfügbar, MRI). Codes bleiben erhalten. - Zuerst Components, dann die Datendatei. Immer zuerst „Prüfen“. + Datei wählen prüft automatisch. Der Schalter startet den Import. Zuerst Components, dann Daten.

{status && (

@@ -67,9 +150,8 @@ export default function AdminBlsImportPage() {

)} {error &&

{error}

} - - - {msg &&
{msg}
} + +
) }