Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0aafbf6
feat(import): add generic import infrastructure and registry
Uli-Z Nov 16, 2025
74eec04
feat(format): add Spliit-JSON adapter with detection and parsing
Uli-Z Nov 16, 2025
742137f
feat(format): add Debug adapter and fixtures for error-path testing
Uli-Z Nov 16, 2025
08af1c1
feat(trpc): add import endpoints (preview, start, run-chunk, cancel, …
Uli-Z Nov 16, 2025
8f1d5e3
feat(ui): add import components (dropzone, analysis, progress, result)
Uli-Z Nov 16, 2025
b6b7f20
feat(ui): add file import modal wiring all steps
Uli-Z Nov 16, 2025
c7110a5
feat(ui): integrate file import into groups overview
Uli-Z Nov 16, 2025
907bd3a
feat(i18n): add translations for file import flow
Uli-Z Nov 16, 2025
5ae9434
feat(db): add ImportJob model for serverless-compatible import state
Uli-Z Dec 9, 2025
16326d6
refactor(import): replace in-memory job state with DB persistence
Uli-Z Dec 9, 2025
2218cf0
test(import): add unit tests for Spliit-JSON format adapter
Uli-Z Dec 9, 2025
d1dc759
refactor(import): improve SpliitJsonFormat readability and maintainab…
Uli-Z Dec 9, 2025
dbe3098
feat(import): add cleanup for old import jobs
Uli-Z Dec 9, 2025
33848cd
feat(import): add Zod validation for import job expenses
Uli-Z Dec 9, 2025
32b086b
feat(import): Finalize import feature with security, tests, and UI re…
Uli-Z Dec 10, 2025
20cd6fe
style: Apply final Prettier formatting
Uli-Z Dec 10, 2025
7f22cca
fix: remove import overlay spinner
Uli-Z Dec 10, 2025
4996517
fix(file-import): Prevent infinite loop on modal close
Uli-Z Dec 10, 2025
bab64c0
refactor(import): async interface and client-side preview
Uli-Z Dec 18, 2025
4757b70
feat(api): add transactional batch expense creation procedure
Uli-Z Dec 23, 2025
4843464
feat(import): implement client-side batch uploading
Uli-Z Dec 23, 2025
d8762b2
refactor(cleanup): remove server-side import job state and legacy pro…
Uli-Z Dec 23, 2025
18cc3ac
style: apply code formatting
Uli-Z Dec 23, 2025
4396f09
style: apply prettier formatting
Uli-Z Dec 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const eslintConfig = defineConfig([
'out/**',
'build/**',
'next-env.d.ts',
'postgres-data/**',
'node_modules/**',
]),
{
rules: {
Expand Down
52 changes: 52 additions & 0 deletions messages/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@
"myGroups": "Meine Gruppen",
"create": "Erstellen",
"loadingRecent": "Lade letzte Gruppen…",
"CreateOptions": {
"openMenu": "Erstelloptionen öffnen",
"newGroup": "Neue Gruppe",
"importFromFile": "Aus Datei importieren"
},
"ImportDialog": {
"successTitle": "Gruppe erstellt",
"successDescription": "Gruppe {name} wurde aus dem Import erstellt."
},
"NoRecent": {
"description": "Du hast in der letzten Zeit keine Gruppe besucht.",
"create": "Erstelle eine",
Expand Down Expand Up @@ -456,4 +465,47 @@
"heading": "Geläufigste"
}
}
,
"FileImport": {
"title": "Gruppe aus Datei importieren",
"buttonLabel": "Import-Dialog öffnen",
"errorTitle": "Fehler",
"processing": "Verarbeite…",
"fileLabel": "Datei auswählen",
"uploadDragTitle": "Datei hier ablegen oder klicken, um auszuwählen",
"uploadDragDescription": "Unterstützt: JSON (Spliit-JSON) und Debug-Dateien",
"fileReadError": "Die ausgewählte Datei konnte nicht gelesen werden.",
"newGroupNameLabel": "Name der neuen Gruppe",
"newGroupNamePlaceholder": "Gruppennamen eingeben (optional)",
"analysisAwaiting": "Warte auf Datei-Analyse…",
"analysisExplanation": "Wähle eine Datei, um sie vor dem Import zu analysieren.",
"previewErrorTitle": "Die Datei konnte nicht analysiert werden",
"generalInfoTitle": "Allgemeine Informationen",
"generalInfoFormat": "Format",
"generalInfoRows": "Zeilen",
"generalInfoInvalidRows": "Ungültige Zeilen",
"generalInfoTotal": "Gesamtbetrag",
"generalInfoParticipants": "Teilnehmer und Salden",
"generalInfoParticipantsEmpty": "Keine Teilnehmerinformationen erkannt.",
"generalInfoUnknown": "Unbekannt",
"analysisHeaderTitle": "Probleme im Header",
"analysisGeneralWarningsTitle": "Allgemeine Warnungen",
"analysisCategoryTitle": "Kategorie-Zuordnungswarnungen",
"analysisErrorsTitle": "Zeilenfehler",
"analysisFatalHint": "Behebe diese Fehler, um den Import zu aktivieren.",
"import": "Importieren",
"importing": "Importiere…",
"importProgressLabel": "Importiere Ausgaben",
"importCancel": "Import abbrechen",
"importCanceling": "Breche ab…",
"importResultCompleted": "{created} von {total} Zeilen importiert",
"importResultCancelledSimple": "Import abgebrochen",
"importResultConfirm": "Weiter"
},
"FileImportErrors": {
"noParticipants": "Keine Teilnehmer in der Datei definiert.",
"fileEmpty": "Die hochgeladene Datei war leer.",
"invalidAmount": "Ungültiger Betrag in einer oder mehreren Zeilen.",
"invalidDate": "Ungültiges Ausgabedatum in einer oder mehreren Zeilen."
}
}
52 changes: 52 additions & 0 deletions messages/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@
"myGroups": "My groups",
"create": "Create",
"loadingRecent": "Loading recent groups…",
"CreateOptions": {
"openMenu": "Open create options",
"newGroup": "New group",
"importFromFile": "Import from file"
},
"ImportDialog": {
"successTitle": "Group created",
"successDescription": "Group {name} was created from import."
},
"NoRecent": {
"description": "You have not visited any group recently.",
"create": "Create one",
Expand Down Expand Up @@ -449,4 +458,47 @@
"heading": "Other currencies"
}
}
,
"FileImport": {
"title": "Import group from file",
"buttonLabel": "Open import dialog",
"errorTitle": "Error",
"processing": "Processing…",
"fileLabel": "Select a file",
"uploadDragTitle": "Drop a file here or click to select",
"uploadDragDescription": "Supported: JSON (Spliit-JSON) and debug files",
"fileReadError": "Could not read the selected file.",
"newGroupNameLabel": "New group name",
"newGroupNamePlaceholder": "Enter a group name (optional)",
"analysisAwaiting": "Awaiting file analysis…",
"analysisExplanation": "Select a file to analyze its content before importing.",
"previewErrorTitle": "We couldn't analyze your file",
"generalInfoTitle": "General information",
"generalInfoFormat": "Format",
"generalInfoRows": "Rows",
"generalInfoInvalidRows": "Invalid rows",
"generalInfoTotal": "Total amount",
"generalInfoParticipants": "Participants and balances",
"generalInfoParticipantsEmpty": "No participant information detected.",
"generalInfoUnknown": "Unknown",
"analysisHeaderTitle": "Header issues",
"analysisGeneralWarningsTitle": "General warnings",
"analysisCategoryTitle": "Category mapping warnings",
"analysisErrorsTitle": "Row errors",
"analysisFatalHint": "Fix these errors to enable import.",
"import": "Import",
"importing": "Importing…",
"importProgressLabel": "Importing expenses",
"importCancel": "Cancel import",
"importCanceling": "Cancelling…",
"importResultCompleted": "Imported {created} of {total} rows",
"importResultCancelledSimple": "Import cancelled",
"importResultConfirm": "Continue"
},
"FileImportErrors": {
"noParticipants": "No participants defined in file.",
"fileEmpty": "The uploaded file was empty.",
"invalidAmount": "Invalid amount in one or more rows.",
"invalidDate": "Invalid expense date in one or more rows."
}
}
52 changes: 52 additions & 0 deletions messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@
"myGroups": "Mis grupos",
"create": "Crear",
"loadingRecent": "Cargando grupos recientes…",
"CreateOptions": {
"openMenu": "Abrir opciones de creación",
"newGroup": "Nuevo grupo",
"importFromFile": "Importar desde archivo"
},
"ImportDialog": {
"successTitle": "Grupo creado",
"successDescription": "El grupo {name} se creó a partir de la importación."
},
"NoRecent": {
"description": "No has visitado ningun grupo recientemente.",
"create": "Crea uno",
Expand Down Expand Up @@ -448,4 +457,47 @@
"heading": "Otras monedas"
}
}
,
"FileImport": {
"title": "Importar grupo desde archivo",
"buttonLabel": "Abrir diálogo de importación",
"errorTitle": "Error",
"processing": "Procesando…",
"fileLabel": "Seleccionar un archivo",
"uploadDragTitle": "Suelta un archivo aquí o haz clic para seleccionar",
"uploadDragDescription": "Compatible: JSON (Spliit‑JSON) y archivos de depuración",
"fileReadError": "No se pudo leer el archivo seleccionado.",
"newGroupNameLabel": "Nombre del nuevo grupo",
"newGroupNamePlaceholder": "Introduce un nombre de grupo (opcional)",
"analysisAwaiting": "Esperando el análisis del archivo…",
"analysisExplanation": "Selecciona un archivo para analizar su contenido antes de importar.",
"previewErrorTitle": "No pudimos analizar tu archivo",
"generalInfoTitle": "Información general",
"generalInfoFormat": "Formato",
"generalInfoRows": "Filas",
"generalInfoInvalidRows": "Filas inválidas",
"generalInfoTotal": "Monto total",
"generalInfoParticipants": "Participantes y saldos",
"generalInfoParticipantsEmpty": "No se detectó información de participantes.",
"generalInfoUnknown": "Desconocido",
"analysisHeaderTitle": "Problemas en el encabezado",
"analysisGeneralWarningsTitle": "Advertencias generales",
"analysisCategoryTitle": "Advertencias de categorías",
"analysisErrorsTitle": "Errores por fila",
"analysisFatalHint": "Corrige estos errores para habilitar la importación.",
"import": "Importar",
"importing": "Importando…",
"importProgressLabel": "Importando gastos",
"importCancel": "Cancelar importación",
"importCanceling": "Cancelando…",
"importResultCompleted": "{created} de {total} filas importadas",
"importResultCancelledSimple": "Importación cancelada",
"importResultConfirm": "Continuar"
},
"FileImportErrors": {
"noParticipants": "No hay participantes definidos en el archivo.",
"fileEmpty": "El archivo subido estaba vacío.",
"invalidAmount": "Monto inválido en una o más filas.",
"invalidDate": "Fecha de gasto inválida en una o más filas."
}
}
52 changes: 52 additions & 0 deletions messages/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@
"myGroups": "Mes groupes",
"create": "Créer",
"loadingRecent": "Chargement des groupes récents…",
"CreateOptions": {
"openMenu": "Ouvrir les options de création",
"newGroup": "Nouveau groupe",
"importFromFile": "Importer depuis un fichier"
},
"ImportDialog": {
"successTitle": "Groupe créé",
"successDescription": "Le groupe {name} a été créé à partir de l'import."
},
"NoRecent": {
"description": "Vous n'avez visité aucun groupe récemment.",
"create": "Créer un groupe",
Expand Down Expand Up @@ -456,4 +465,47 @@
"heading": "Autres devises"
}
}
,
"FileImport": {
"title": "Importer un groupe depuis un fichier",
"buttonLabel": "Ouvrir la boîte d'import",
"errorTitle": "Erreur",
"processing": "Traitement…",
"fileLabel": "Sélectionner un fichier",
"uploadDragTitle": "Déposez un fichier ici ou cliquez pour sélectionner",
"uploadDragDescription": "Pris en charge : JSON (Spliit‑JSON) et fichiers de débogage",
"fileReadError": "Impossible de lire le fichier sélectionné.",
"newGroupNameLabel": "Nom du nouveau groupe",
"newGroupNamePlaceholder": "Entrez un nom de groupe (optionnel)",
"analysisAwaiting": "En attente de l'analyse du fichier…",
"analysisExplanation": "Sélectionnez un fichier pour analyser son contenu avant l'import.",
"previewErrorTitle": "Nous n'avons pas pu analyser votre fichier",
"generalInfoTitle": "Informations générales",
"generalInfoFormat": "Format",
"generalInfoRows": "Lignes",
"generalInfoInvalidRows": "Lignes invalides",
"generalInfoTotal": "Montant total",
"generalInfoParticipants": "Participants et soldes",
"generalInfoParticipantsEmpty": "Aucune information de participant détectée.",
"generalInfoUnknown": "Inconnu",
"analysisHeaderTitle": "Problèmes d'en‑tête",
"analysisGeneralWarningsTitle": "Avertissements généraux",
"analysisCategoryTitle": "Avertissements de catégorisation",
"analysisErrorsTitle": "Erreurs de lignes",
"analysisFatalHint": "Corrigez ces erreurs pour activer l'import.",
"import": "Importer",
"importing": "Importation…",
"importProgressLabel": "Import des dépenses",
"importCancel": "Annuler l'import",
"importCanceling": "Annulation…",
"importResultCompleted": "{created} sur {total} lignes importées",
"importResultCancelledSimple": "Import annulé",
"importResultConfirm": "Continuer"
},
"FileImportErrors": {
"noParticipants": "Aucun participant défini dans le fichier.",
"fileEmpty": "Le fichier téléchargé était vide.",
"invalidAmount": "Montant invalide dans une ou plusieurs lignes.",
"invalidDate": "Date de dépense invalide dans une ou plusieurs lignes."
}
}
Loading
Loading