mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
feat(import): show character counts and a Preview busy state
The JSON and CSV paste boxes now cap input at the 16 MB body ceiling the routes enforce and show a live character count beneath each, so an over-long paste is bounded with feedback instead of silently rejected at submit. The CSV Preview button also renders the same spinner the Import button does while a dry-run is in flight, with motion-reduce honoured, so the tap no longer looks inert on a slow preview.
This commit is contained in:
1 parent
c6beba2229
commit
9f960a59d0
7 files changed
+30
No files matched your search
@@ -4006,6 +4006,7 @@
|
||||
"import": {
|
||||
"heading": "Import",
|
||||
"description": "Importiere Daten aus einem Apple-Health-Export oder einer JSON-Datei. Bereits vorhandene Einträge werden übersprungen, ein erneuter Import ist also unbedenklich.",
|
||||
"charCount": "{used} / {max} Zeichen",
|
||||
"appleHealth": {
|
||||
"title": "Apple Health",
|
||||
"description": "Lade die export.zip aus der Health-App auf dem iPhone hoch (Profil → Alle Gesundheitsdaten exportieren).",
|
||||
|
||||
@@ -4006,6 +4006,7 @@
|
||||
"import": {
|
||||
"heading": "Import",
|
||||
"description": "Bring data into HealthLog from an Apple Health export, a JSON file, or a CSV. Imports skip rows that already exist, so re-running one is safe.",
|
||||
"charCount": "{used} / {max} characters",
|
||||
"appleHealth": {
|
||||
"title": "Apple Health",
|
||||
"description": "Upload the export.zip from the Health app on iPhone (Profile → Export All Health Data).",
|
||||
|
||||
@@ -4006,6 +4006,7 @@
|
||||
"import": {
|
||||
"heading": "Importar",
|
||||
"description": "Importa datos a HealthLog desde una exportación de Apple Health o un archivo JSON. La importación omite las filas que ya existen, así que repetirla es seguro.",
|
||||
"charCount": "{used} / {max} caracteres",
|
||||
"appleHealth": {
|
||||
"title": "Apple Health",
|
||||
"description": "Sube el archivo export.zip de la app Salud del iPhone (Perfil → Exportar todos los datos de salud).",
|
||||
|
||||
@@ -4006,6 +4006,7 @@
|
||||
"import": {
|
||||
"heading": "Import",
|
||||
"description": "Importez des données dans HealthLog depuis un export Apple Santé ou un fichier JSON. L'import ignore les lignes déjà présentes : le relancer est sans risque.",
|
||||
"charCount": "{used} / {max} caractères",
|
||||
"appleHealth": {
|
||||
"title": "Apple Santé",
|
||||
"description": "Importez le fichier export.zip de l'app Santé sur iPhone (Profil → Exporter toutes les données de santé).",
|
||||
|
||||
@@ -4006,6 +4006,7 @@
|
||||
"import": {
|
||||
"heading": "Importa",
|
||||
"description": "Importa dati in HealthLog da un'esportazione di Apple Salute o da un file JSON. L'importazione salta le righe già presenti, quindi ripeterla è sicuro.",
|
||||
"charCount": "{used} / {max} caratteri",
|
||||
"appleHealth": {
|
||||
"title": "Apple Salute",
|
||||
"description": "Carica il file export.zip dall'app Salute su iPhone (Profilo → Esporta tutti i dati sanitari).",
|
||||
|
||||
@@ -4006,6 +4006,7 @@
|
||||
"import": {
|
||||
"heading": "Import",
|
||||
"description": "Zaimportuj dane do HealthLog z eksportu Apple Health lub pliku JSON. Import pomija wiersze, które już istnieją, więc ponowne uruchomienie jest bezpieczne.",
|
||||
"charCount": "{used} / {max} znaków",
|
||||
"appleHealth": {
|
||||
"title": "Apple Health",
|
||||
"description": "Prześlij plik export.zip z aplikacji Zdrowie na iPhonie (Profil → Eksportuj wszystkie dane zdrowotne).",
|
||||
|
||||
@@ -48,6 +48,13 @@ import { cn } from "@/lib/utils";
|
||||
import { apiFetchRaw, apiGet } from "@/lib/api/api-fetch";
|
||||
import { CSV_EXAMPLE_COLUMNS } from "@/lib/import/csv-measurements";
|
||||
|
||||
/**
|
||||
* Upper bound for the paste textareas, mirroring the 16 MB server-side body
|
||||
* ceiling on `/api/import` and `/api/import/csv`. Caps an accidental over-paste
|
||||
* before it ever reaches the route and feeds the live character counter.
|
||||
*/
|
||||
const MAX_PASTE_CHARS = 16 * 1024 * 1024;
|
||||
|
||||
// ─────────────────────────── Section wrapper ───────────────────────────
|
||||
|
||||
export function ImportPanel() {
|
||||
@@ -537,10 +544,17 @@ function JsonImportCard() {
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
rows={5}
|
||||
maxLength={MAX_PASTE_CHARS}
|
||||
spellCheck={false}
|
||||
placeholder='{"measurements":[…],"moodEntries":[…]}'
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
<p className="text-muted-foreground text-right text-xs tabular-nums">
|
||||
{t("settings.sections.export.import.charCount", {
|
||||
used: text.length,
|
||||
max: MAX_PASTE_CHARS,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<input
|
||||
@@ -762,10 +776,17 @@ function CsvImportCard() {
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
rows={5}
|
||||
maxLength={MAX_PASTE_CHARS}
|
||||
spellCheck={false}
|
||||
placeholder="type,value,unit,measuredAt,…"
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
<p className="text-muted-foreground text-right text-xs tabular-nums">
|
||||
{t("settings.sections.export.import.charCount", {
|
||||
used: text.length,
|
||||
max: MAX_PASTE_CHARS,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<input
|
||||
@@ -865,6 +886,9 @@ function CsvImportCard() {
|
||||
onClick={() => void send(true)}
|
||||
data-testid="import-csv-preview"
|
||||
>
|
||||
{busy && (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin motion-reduce:animate-none" />
|
||||
)}
|
||||
{t("settings.sections.export.import.csv.preview")}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
Reference in new issue
Block a user