import { useEffect, useMemo, useState } from 'react' import { Link, useNavigate, useParams } from 'react-router-dom' import { ArrowLeft, FileSpreadsheet, Loader2, Save, Trash2 } from 'lucide-react' import { api } from '../utils/api' import { csvPreviewTdStyle } from '../utils/csvPreviewCells' const MODULE_LABEL = { nutrition: 'Ernährung', weight: 'Gewicht', blood_pressure: 'Blutdruck', activity: 'Aktivität', sleep: 'Schlaf', vitals_baseline: 'Vitalwerte (Baseline)', } /** Erlaubt Eingaben wie 1,03 oder 1.03 während des Tippens; Finale normalisiert bei Blur/Speichern. */ function normalizeDecimalInputString(raw) { let s = String(raw).trim().replace(/\s/g, '') if (s === '') return '' const lastComma = s.lastIndexOf(',') const lastDot = s.lastIndexOf('.') if (lastComma >= 0 && lastDot >= 0) { if (lastComma > lastDot) { s = s.replace(/\./g, '').replace(',', '.') } else { s = s.replace(/,/g, '') } } else if (lastComma >= 0) { s = s.replace(',', '.') } return s } function applyCustomFactorToTcObject(tc, fieldKey, raw) { const base = tc[fieldKey] && typeof tc[fieldKey] === 'object' ? { ...tc[fieldKey] } : { type: 'float', decimal_separator: 'auto', flexible: true } const normalized = normalizeDecimalInputString(raw) if (normalized === '') { delete base.conversion_factor } else { const num = Number(normalized) if (Number.isNaN(num)) { return { ok: false, message: `Konvertierungsfaktor (${fieldKey}): keine gültige Zahl.` } } base.conversion_factor = num base.source_unit = 'custom' } delete base.target_unit tc[fieldKey] = base return { ok: true } } function SampleTable({ sampleRows, columns }) { if (!sampleRows?.length || !columns?.length) return null const showCols = columns.slice(0, 8) return (
| {c} | ))}
|---|
| {row[c] ?? '—'} | ))}
Modul bestehender Vorlagen kann nicht geändert werden. System-Vorlagen können hier bearbeitet und gespeichert werden (Signatur, Trennzeichen, Zuordnungen).
)} {aggregateSleepImport && (Schlaf (Apple-Aggregat): Die Zeilen werden nicht über Spalten-Ziele importiert, sondern vom Apple-Schlaf-Parser ausgewertet (Schlafanalyse oder Segment-Export). Alle CSV-Spalten bleiben auf „ignorieren“. Wichtig sind die{' '} gespeicherte Spalten-Signatur und das passende Datei-Format — damit die Datei in der Nutzer-Auswahl erkannt wird.
)}Seed: {seedHint.mapping_name} · Vorlage abgedeckt{' '} {Math.round((seedHint.confidence || 0) * 100)} % {seedHint.columns_matched != null && seedHint.columns_in_template != null ? ` (${seedHint.columns_matched}/${seedHint.columns_in_template} Spalten)` : ''} {seedHint.jaccard != null && ( <> {' '} · Jaccard {Math.round(seedHint.jaccard * 100)} % > )}
)} {sampleRows.length > 0 &&Nach CSV-Analyse erscheinen die Zeilen hier. Bei Schlaf-Vorlagen ohne Analyse: Signatur oben pflegen, speichern, oder Beispiel-CSV analysieren.
) : (
{col}
Noch zuzuweisen: {missingRequired.join(', ')}
)}
Ziel-Einheit kommt aus dem Datenmodell (z. B. kcal, g, kg, km). Standard-Umrechnungen wählen; für
abweichende Skalen (z. B. Volumen→Masse mit variabler Dichte) "Benutzerdefiniert" und den
Faktor eintragen (CSV-Wert × Faktor → Speicher-Einheit). Bei Registry-Optionen werden{' '}
target_unit und ein alter conversion_factor entfernt; bei
Benutzerdefiniert bleibt der Faktor erhalten, bis du ihn leerst.
Dezimalkomma oder -punkt; mit Tab oder Klick außerhalb übernehmen (oder direkt Speichern).
{getCustomFactorFieldDisplay(fkey) === '' && (Ohne Faktor gilt keine zusätzliche Skalierung (wie 1:1 nach dem Parsen).
)}
Vom Vorschlag übernommen; bei Dropdowns 3b wird source_unit ergänzt. Zusätzlich manuell
z. B. Datumsformat. Freie Umrechnung (nicht in der Liste 3b):{' '}
conversion_factor als Multiplikator nach dem Parsen (und nach Registry-
source_unit). Optional im JSON source_unit: 'custom', wenn nur{' '}
conversion_factor gelten soll.