From 895e008c9b248be0a02b3123823ac949dfed28e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20St=C3=B6ckel?= Date: Sat, 17 Oct 2026 18:16:27 +0200 Subject: [PATCH] :bug: fix trip editing: category mode, stopover errors and unsaved input fixes #4991 fixes #4992 fixes #4993 --- app/Services/Trip/TripEditService.php | 6 ++- lang/de.json | 2 + lang/en.json | 2 + lang/fr.json | 2 + .../components/Trip/StopoverEditor.vue | 54 +++++++++++++++++-- 5 files changed, 60 insertions(+), 6 deletions(-) diff --git a/app/Services/Trip/TripEditService.php b/app/Services/Trip/TripEditService.php index 7165b3548..0d0675ce3 100644 --- a/app/Services/Trip/TripEditService.php +++ b/app/Services/Trip/TripEditService.php @@ -36,7 +36,11 @@ public function updateTrip(Trip $trip, array $data): Trip $attributes = []; if (array_key_exists('category', $data)) { - $attributes['category'] = HafasTravelType::from($data['category']); + $category = HafasTravelType::from($data['category']); + $attributes['category'] = $category; + if ($category !== $trip->category) { + $attributes['mode'] = null; + } } if (array_key_exists('lineName', $data)) { // Manual trips carry the line name in both fields //TODO: cleanup columns diff --git a/lang/de.json b/lang/de.json index 78712667e..d0376699c 100644 --- a/lang/de.json +++ b/lang/de.json @@ -320,6 +320,7 @@ "generic.change": "Ändern", "generic.error": "Fehler", "generic.search": "Suchen", + "generic.unsaved": "Nicht gespeichert", "generic.why": "Warum?", "go-to-settings": "Zu den Einstellungen", "help": "Hilfe", @@ -1004,6 +1005,7 @@ "trip.edit.stopover-added": "Halt hinzugefügt.", "trip.edit.stopover-deleted": "Halt gelöscht.", "trip.edit.stopover-hint": "Die Reihenfolge der Halte ergibt sich aus den geplanten Zeiten. Änderungen berechnen Distanz und Punkte aller Check-ins dieser Fahrt neu.", + "trip.edit.stopover-in-use": "Dieser Halt wird von einem Check-in genutzt und kann nicht gelöscht werden. Passe zuerst den Check-in an.", "trip.edit.stopovers-shifted": "Alle Halte wurden verschoben.", "trip.edit.time-required": "Bitte gib mindestens eine geplante Ankunft oder Abfahrt an.", "trip.edit.title": "Fahrt bearbeiten", diff --git a/lang/en.json b/lang/en.json index 0fab03459..2d3b66d9a 100644 --- a/lang/en.json +++ b/lang/en.json @@ -320,6 +320,7 @@ "generic.change": "Change", "generic.error": "Error", "generic.search": "Search", + "generic.unsaved": "Unsaved", "generic.why": "Why?", "go-to-settings": "Go to settings", "help": "Help", @@ -1004,6 +1005,7 @@ "trip.edit.stopover-added": "Stopover added.", "trip.edit.stopover-deleted": "Stopover deleted.", "trip.edit.stopover-hint": "The order of the stopovers is derived from the planned times. Changes recalculate distance and points of all check-ins on this journey.", + "trip.edit.stopover-in-use": "This stop is used by a check-in and cannot be deleted. Change the check-in first.", "trip.edit.stopovers-shifted": "All stopovers have been shifted.", "trip.edit.time-required": "Please provide at least a planned arrival or departure.", "trip.edit.title": "Edit journey", diff --git a/lang/fr.json b/lang/fr.json index 8620f2c26..cf263fe85 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -322,6 +322,7 @@ "generic.change": "Changer", "generic.error": "Erreur", "generic.search": "Recherche", + "generic.unsaved": "Non enregistré", "generic.why": "Pourquoi ?", "go-to-settings": "Vers les paramètres", "help": "Aide", @@ -1009,6 +1010,7 @@ "trip.edit.stopover-added": "Arrêt ajouté.", "trip.edit.stopover-deleted": "Arrêt supprimé.", "trip.edit.stopover-hint": "L’ordre des arrêts découle des horaires prévus. Les modifications recalculent la distance et les points de tous les enregistrements de ce trajet.", + "trip.edit.stopover-in-use": "Cet arrêt est utilisé par un check-in et ne peut pas être supprimé. Modifie d’abord le check-in.", "trip.edit.stopovers-shifted": "Tous les arrêts ont été décalés.", "trip.edit.time-required": "Merci d’indiquer au moins une arrivée ou un départ prévu.", "trip.edit.title": "Modifier le trajet", diff --git a/resources/tailwind-app/components/Trip/StopoverEditor.vue b/resources/tailwind-app/components/Trip/StopoverEditor.vue index 8ffe32f06..a2e44f359 100644 --- a/resources/tailwind-app/components/Trip/StopoverEditor.vue +++ b/resources/tailwind-app/components/Trip/StopoverEditor.vue @@ -34,6 +34,8 @@ const arrivalPlatformReal = ref(''); const departurePlatformReal = ref(''); const cancelled = ref(false); +const syncedForm = ref(''); + function toLocalInput(iso: string | null | undefined): string { if (!iso) return ''; const dt = DateTime.fromISO(iso); @@ -46,6 +48,23 @@ function toIso(local: string): string | null { return dt.isValid ? dt.toISO() : null; } +function formSnapshot(): string { + return JSON.stringify([ + station.value?.uuid ?? null, + arrivalPlanned.value, + departurePlanned.value, + arrivalReal.value, + departureReal.value, + arrivalPlatformPlanned.value, + departurePlatformPlanned.value, + arrivalPlatformReal.value, + departurePlatformReal.value, + cancelled.value, + ]); +} + +const isDirty = computed((): boolean => formSnapshot() !== syncedForm.value); + function resetForm(): void { station.value = props.stopover.station ?? null; arrivalPlanned.value = toLocalInput(props.stopover.arrivalPlanned); @@ -57,9 +76,30 @@ function resetForm(): void { arrivalPlatformReal.value = props.stopover.arrivalPlatformReal ?? ''; departurePlatformReal.value = props.stopover.departurePlatformReal ?? ''; cancelled.value = props.stopover.cancelled ?? false; + syncedForm.value = formSnapshot(); } -watch(() => props.stopover, resetForm, { immediate: true }); +resetForm(); + +watch( + () => props.stopover, + () => { + if (!isDirty.value) { + resetForm(); + } + }, +); + +async function errorMessage(error: unknown, conflictMessage?: string): Promise { + if (!(error instanceof Response)) { + return trans('generic.error'); + } + if (error.status === 409 && conflictMessage) { + return conflictMessage; + } + const body = await error.json().catch(() => null); + return body?.message ?? trans('generic.error'); +} type TimeParts = { date: string; time: string }; @@ -102,9 +142,10 @@ async function save(): Promise { }); notyf?.success(trans('settings.saved')); expanded.value = false; + syncedForm.value = formSnapshot(); emit('changed'); - } catch { - notyf?.error(trans('generic.error')); + } catch (error) { + notyf?.error(await errorMessage(error)); } finally { saving.value = false; } @@ -117,8 +158,8 @@ async function remove(): Promise { await api.trips.deleteTripStopover(props.tripUuid, props.stopover.uuid); notyf?.success(trans('trip.edit.stopover-deleted')); emit('changed'); - } catch { - notyf?.error(trans('generic.error')); + } catch (error) { + notyf?.error(await errorMessage(error, trans('trip.edit.stopover-in-use'))); } finally { deleting.value = false; } @@ -151,6 +192,9 @@ async function remove(): Promise { + + {{ trans('generic.unsaved') }} + {{ trans('stationboard.stop-cancelled') }}