2026-09-12 14:35:57 +02:00
|
|
|
import { useEffect, useState } from 'react'
|
|
|
|
|
import { api } from '../utils/api'
|
|
|
|
|
|
2026-09-12 14:41:42 +02:00
|
|
|
function FileImportBlock({ label, kind, busy, onRun }) {
|
|
|
|
|
const [file, setFile] = useState(null)
|
|
|
|
|
return (
|
|
|
|
|
<div className="form-row" style={{ flexDirection: 'column', alignItems: 'stretch', gap: 8, marginTop: 16 }}>
|
|
|
|
|
<label className="form-label">{label}</label>
|
|
|
|
|
<input
|
|
|
|
|
type="file"
|
|
|
|
|
accept=".xlsx"
|
|
|
|
|
className="form-input"
|
|
|
|
|
onChange={(e) => setFile(e.target.files?.[0] || null)}
|
|
|
|
|
/>
|
|
|
|
|
{file && <p style={{ fontSize: 12, color: 'var(--text2)', margin: 0 }}>{file.name}</p>}
|
|
|
|
|
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
|
|
|
<button type="button" className="btn btn-secondary" disabled={!file || busy} onClick={() => onRun(kind, file, true)}>
|
|
|
|
|
Prüfen
|
|
|
|
|
</button>
|
|
|
|
|
<button type="button" className="btn btn-primary" disabled={!file || busy} onClick={() => onRun(kind, file, false)}>
|
|
|
|
|
Anwenden
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-12 14:35:57 +02:00
|
|
|
export default function AdminBlsImportPage() {
|
|
|
|
|
const [status, setStatus] = useState(null)
|
|
|
|
|
const [msg, setMsg] = useState(null)
|
|
|
|
|
const [error, setError] = useState(null)
|
2026-09-12 14:41:42 +02:00
|
|
|
const [busy, setBusy] = useState(false)
|
2026-09-12 14:35:57 +02:00
|
|
|
|
|
|
|
|
const load = () => {
|
|
|
|
|
api.adminBlsStatus().then(setStatus).catch((e) => setError(e.message))
|
|
|
|
|
}
|
|
|
|
|
useEffect(() => { load() }, [])
|
|
|
|
|
|
|
|
|
|
const run = async (kind, file, dry) => {
|
|
|
|
|
setError(null)
|
2026-09-12 14:41:42 +02:00
|
|
|
setBusy(true)
|
2026-09-12 14:35:57 +02:00
|
|
|
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)
|
2026-09-12 14:41:42 +02:00
|
|
|
} finally {
|
|
|
|
|
setBusy(false)
|
2026-09-12 14:35:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="card">
|
|
|
|
|
<h1 className="page-title">BLS 4.0 importieren</h1>
|
|
|
|
|
<p style={{ fontSize: 13, color: 'var(--text2)', lineHeight: 1.6 }}>
|
|
|
|
|
Offizielle Dateien von blsdb.de (frei verfügbar, MRI). Codes bleiben erhalten.
|
|
|
|
|
Zuerst Components, dann die Datendatei. Immer zuerst „Prüfen“.
|
|
|
|
|
</p>
|
|
|
|
|
{status && (
|
|
|
|
|
<p style={{ fontSize: 13 }}>
|
|
|
|
|
{status.official_foods} offizielle Lebensmittel · {status.official_attributes} Stoffe ·
|
|
|
|
|
{status.manual_foods} manuell · {status.source}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
{error && <p style={{ color: 'var(--danger)' }}>{error}</p>}
|
2026-09-12 14:41:42 +02:00
|
|
|
<FileImportBlock label="Components XLSX" kind="components" busy={busy} onRun={run} />
|
|
|
|
|
<FileImportBlock label="Daten XLSX" kind="foods" busy={busy} onRun={run} />
|
2026-09-12 14:35:57 +02:00
|
|
|
{msg && <pre style={{ whiteSpace: 'pre-wrap', fontSize: 12, background: 'var(--surface2)', padding: 12, borderRadius: 8 }}>{msg}</pre>}
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|