From 9d6ae8c564096a1e42464885fb418fab3e8df789 Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 9 May 2025 14:20:36 +0200 Subject: [PATCH] time check location trip entries --- .../datetime-picker.component.ts | 111 +++++++-------- .../app/_components/list/list.component.ts | 13 +- .../trip-location-form.component.html | 2 +- .../trip-location-form.component.ts | 128 ++++++++++++++++-- .../trip-location-list.component.ts | 6 +- angular/src/assets/i18n/en.json | 5 +- 6 files changed, 196 insertions(+), 69 deletions(-) diff --git a/angular/src/app/_components/datetime-picker/datetime-picker.component.ts b/angular/src/app/_components/datetime-picker/datetime-picker.component.ts index a4667a9..a4b0e1e 100644 --- a/angular/src/app/_components/datetime-picker/datetime-picker.component.ts +++ b/angular/src/app/_components/datetime-picker/datetime-picker.component.ts @@ -2,66 +2,68 @@ import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {FormBuilder, FormGroup} from "@angular/forms"; @Component({ - selector: 'app-datetime-picker', - templateUrl: './datetime-picker.component.html', - styleUrl: './datetime-picker.component.scss' + selector: 'app-datetime-picker', + templateUrl: './datetime-picker.component.html', + styleUrl: './datetime-picker.component.scss' }) export class DatetimePickerComponent implements OnInit { - @Input() label: string = 'Date and Time'; - @Input() inputId: string = 'myId'; - @Input() initialValue: string | null = null; - @Input() readonly: boolean = false; - @Input() showSeconds: boolean = false; - @Output() dateTimeChange = new EventEmitter(); + @Input() label: string = 'Date and Time'; + @Input() inputId: string = 'myId'; + @Input() initialValue: string | null = null; + @Input() readonly: boolean = false; + @Input() showSeconds: boolean = false; + @Output() dateTimeChange = new EventEmitter(); - form: FormGroup; + form: FormGroup; - constructor(private fb: FormBuilder) { - this.form = this.fb.group({ - date: [''], - time: [''] - }); - } - - ngOnInit() { - if (this.initialValue) { - const date = new Date(this.initialValue); - this.form.patchValue({ - date: this.formatDate(date), - time: this.formatTime(date) - }); + constructor(private fb: FormBuilder) { + this.form = this.fb.group({ + date: [''], + time: [''] + }); } - if (this.readonly) { - this.form.disable(); - } + ngOnInit() { + if (this.initialValue) { + // Datum als UTC interpretieren + const date = new Date(this.initialValue); + this.form.patchValue({ + date: this.formatDate(date), + time: this.formatTime(date) + }); + } - this.form.valueChanges.subscribe(() => { - if (!this.readonly) { - this.emitDateTime(); - } - }); - } + if (this.readonly) { + this.form.disable(); + } - private formatDate(date: Date): string { - return date.toLocaleDateString('en-CA'); - } + this.form.valueChanges.subscribe(() => { + if (!this.readonly) { + this.emitDateTime(); + } + }); + } + + private formatDate(date: Date): string { + // ISO String verwenden und nur den Datumsteil extrahieren (YYYY-MM-DD) + return date.toISOString().split('T')[0]; + } private formatTime(date: Date): string { + // Zeit aus ISO String extrahieren + const timeString = date.toISOString().split('T')[1]; + if (this.showSeconds) { - return date.toLocaleTimeString('en-GB', { hour12: false }); + // Rückgabe mit Sekunden (HH:MM:SS) + return timeString.substring(0, 8); } else { - // Nur Stunden und Minuten zurückgeben - return date.toLocaleTimeString('en-GB', { - hour12: false, - hour: '2-digit', - minute: '2-digit' - }); + // Nur Stunden und Minuten zurückgeben (HH:MM) + return timeString.substring(0, 5); } } private emitDateTime() { - const { date, time } = this.form.value; + const {date, time} = this.form.value; if (date && time) { const [year, month, day] = date.split('-'); @@ -74,21 +76,24 @@ export class DatetimePickerComponent implements OnInit { seconds = '00'; } + // Datum in UTC erstellen const dateTime = new Date( - Number(year), - Number(month) - 1, - Number(day), - Number(hours), - Number(minutes), - Number(seconds || 0) + Date.UTC( + Number(year), + Number(month) - 1, + Number(day), + Number(hours), + Number(minutes), + Number(seconds || 0) + ) ); - // Format the date to match the loaded format - const formattedDate = dateTime.toLocaleString('sv-SE', { timeZone: 'Europe/Berlin' }).replace(' ', 'T') + '+02:00'; + // Als ISO-String formatieren, der automatisch in UTC ist + const formattedDate = dateTime.toISOString(); this.dateTimeChange.emit(formattedDate); } else { this.dateTimeChange.emit(null); } } -} +} \ No newline at end of file diff --git a/angular/src/app/_components/list/list.component.ts b/angular/src/app/_components/list/list.component.ts index f727fae..43cdbcb 100644 --- a/angular/src/app/_components/list/list.component.ts +++ b/angular/src/app/_components/list/list.component.ts @@ -405,7 +405,6 @@ export class ListComponent implements OnInit, AfterViewInit, OnDestroy { } public onFilterChanged(filterData: {filters: any, activeCount: number}) { - console.log(filterData); const filterJson = JSON.stringify(filterData.filters); const currentFilterJson = JSON.stringify(this.filterObj); @@ -417,6 +416,12 @@ export class ListComponent implements OnInit, AfterViewInit, OnDestroy { } public onCreateData() { + // Important: Reset the data property to prevent using previous edit data + if (this.dataFormComponentData) { + this.dataFormComponentData = {...this.dataFormComponentData}; + delete this.dataFormComponentData.data; + } + this.appHelperService.openModal( this.dataFormComponent, this.dataFormComponentData, @@ -425,7 +430,11 @@ export class ListComponent implements OnInit, AfterViewInit, OnDestroy { } public onEditData(element: any) { - this.dataFormComponentData.data = element; + // Create a new object instead of modifying the existing one + this.dataFormComponentData = { + ...this.dataFormComponentData, + data: element + }; this.appHelperService.openModal( this.dataFormComponent, this.dataFormComponentData, diff --git a/angular/src/app/_views/trip/trip-location-form/trip-location-form.component.html b/angular/src/app/_views/trip/trip-location-form/trip-location-form.component.html index 02a1bbf..ba6566a 100644 --- a/angular/src/app/_views/trip/trip-location-form/trip-location-form.component.html +++ b/angular/src/app/_views/trip/trip-location-form/trip-location-form.component.html @@ -5,7 +5,7 @@ } @else {
-

{{ ('basic.edit') | translate }} {{ 'model.trip_location' | translate }}: {{ data?.location?.name }}

+

{{ ('basic.edit') | translate }} {{ 'model.trip_location' | translate }}

}
diff --git a/angular/src/app/_views/trip/trip-location-form/trip-location-form.component.ts b/angular/src/app/_views/trip/trip-location-form/trip-location-form.component.ts index 8b3ce07..d0d8c57 100644 --- a/angular/src/app/_views/trip/trip-location-form/trip-location-form.component.ts +++ b/angular/src/app/_views/trip/trip-location-form/trip-location-form.component.ts @@ -1,4 +1,4 @@ -import {Component, Input} from '@angular/core'; +import {Component} from '@angular/core'; import {AbstractDataFormComponent} from "@app/_components/_abstract/abstract-data-form-component"; import { LocationService, TripJsonld, @@ -21,11 +21,13 @@ import {SearchSelectComponent} from "@app/_components/search-select/search-selec }) export class TripLocationFormComponent extends AbstractDataFormComponent { - protected trip!: TripJsonld; - protected arrivalDateTime?: string; - protected departureDateTime?: string; + protected readonly tripLocationForm = tripLocationForm; protected readonly SearchSelectComponent = SearchSelectComponent; protected locationColDefinitions: ListColDefinition[]; + protected trip!: TripJsonld; + protected newArrivalDateTime?: string; + protected newDepartureDateTime?: string; + protected tripLocationEntries: TripLocationJsonld[]; constructor( private tripLocationService: TripLocationService, @@ -49,7 +51,7 @@ export class TripLocationFormComponent extends AbstractDataFormComponent { + alert(error); + }); + return; + } + super.onSubmit(); } - protected readonly tripLocationForm = tripLocationForm; + private hasDateOverlap(formData: TripLocationJsonld): boolean { + // Wenn kein Datum gesetzt ist, kann keine Überlappung stattfinden + if (!formData.arrivalDateTime && !formData.departureDateTime) { + return false; + } + + // Wenn wir im Edit-Modus sind, entferne den aktuellen Eintrag aus der Überprüfung + const entriesToCheck = this.isEditMode() + ? this.tripLocationEntries.filter(entry => entry.id !== this.data?.id) + : this.tripLocationEntries; + + for (const entry of entriesToCheck) { + // Fall 1: Nur ein Datum ist gesetzt in formData + if (formData.arrivalDateTime && !formData.departureDateTime) { + // Arrival date darf nicht mit anderen arrival dates übereinstimmen + if (entry.arrivalDateTime === formData.arrivalDateTime) { + return true; + } + + // Überprüfe, ob arrival innerhalb eines existierenden Zeitraums liegt + // ABER: arrival darf gleich mit einem departure sein + if (entry.arrivalDateTime && entry.departureDateTime) { + const arrivalDate = new Date(formData.arrivalDateTime); + const entryArrival = new Date(entry.arrivalDateTime); + const entryDeparture = new Date(entry.departureDateTime); + + // Liegt innerhalb des Zeitraums, aber ist NICHT gleich dem departure + if (arrivalDate > entryArrival && arrivalDate < entryDeparture) { + return true; + } + } + } + else if (!formData.arrivalDateTime && formData.departureDateTime) { + // Departure date darf nicht mit anderen departure dates übereinstimmen + if (entry.departureDateTime === formData.departureDateTime) { + return true; + } + + // Überprüfe, ob departure innerhalb eines existierenden Zeitraums liegt + // ABER: departure darf gleich mit einem arrival sein + if (entry.arrivalDateTime && entry.departureDateTime) { + const departureDate = new Date(formData.departureDateTime); + const entryArrival = new Date(entry.arrivalDateTime); + const entryDeparture = new Date(entry.departureDateTime); + + // Liegt innerhalb des Zeitraums, aber ist NICHT gleich dem arrival + if (departureDate > entryArrival && departureDate < entryDeparture) { + return true; + } + } + } + // Fall 2: Beide Daten sind gesetzt in formData, überprüfe auf Überlappung + else if (formData.arrivalDateTime && formData.departureDateTime) { + const formArrival = new Date(formData.arrivalDateTime); + const formDeparture = new Date(formData.departureDateTime); + + // Wenn der andere Eintrag beide Daten hat + if (entry.arrivalDateTime && entry.departureDateTime) { + const entryArrival = new Date(entry.arrivalDateTime); + const entryDeparture = new Date(entry.departureDateTime); + + // Überprüfung auf Überlappung der Zeiträume, erlaubt grenzen zu teilen + // Formular-Zeitraum darf vor oder nach dem Entry-Zeitraum liegen, + // oder die Grenzen dürfen gleich sein (departure gleich arrival oder umgekehrt) + if ( + // Überlappung, aber nicht nur am Rand + (formArrival < entryDeparture && formDeparture > entryArrival) && + // Erlaubt: formDeparture === entryArrival ODER formArrival === entryDeparture + !(formDeparture.getTime() === entryArrival.getTime() || + formArrival.getTime() === entryDeparture.getTime()) + ) { + return true; + } + } + // Wenn der andere Eintrag nur arrival hat + else if (entry.arrivalDateTime && !entry.departureDateTime) { + const entryArrival = new Date(entry.arrivalDateTime); + + // Das arrival des anderen Eintrags darf nicht innerhalb des neuen Zeitraums liegen + // ABER: Es darf am Rand liegen (gleich mit formDeparture) + if (entryArrival > formArrival && entryArrival < formDeparture) { + return true; + } + } + // Wenn der andere Eintrag nur departure hat + else if (!entry.arrivalDateTime && entry.departureDateTime) { + const entryDeparture = new Date(entry.departureDateTime); + + // Das departure des anderen Eintrags darf nicht innerhalb des neuen Zeitraums liegen + // ABER: Es darf am Rand liegen (gleich mit formArrival) + if (entryDeparture > formArrival && entryDeparture < formDeparture) { + return true; + } + } + } + } + + return false; + } } diff --git a/angular/src/app/_views/trip/trip-location-list/trip-location-list.component.ts b/angular/src/app/_views/trip/trip-location-list/trip-location-list.component.ts index a32fe86..3531e82 100644 --- a/angular/src/app/_views/trip/trip-location-list/trip-location-list.component.ts +++ b/angular/src/app/_views/trip/trip-location-list/trip-location-list.component.ts @@ -100,7 +100,6 @@ export class TripLocationListComponent { map(response => { // Set default arrival and departure time before return let arrivalDateTime = this.appHelperService.getDateTimeWithoutTimezone(); - console.log(arrivalDateTime); let departureDateTime = this.appHelperService.getDateTimeWithoutTimezone(new Date(new Date().setHours(new Date().getHours() + 1))); if (response.member && response.member.length > 0) { const lastEntry = response.member[response.member.length - 1]; @@ -116,8 +115,9 @@ export class TripLocationListComponent { } this.dataFormComponentData = { trip: this.trip, - arrivalDateTime: arrivalDateTime, - departureDateTime: departureDateTime + newArrivalDateTime: arrivalDateTime, + newDepartureDateTime: departureDateTime, + tripLocationEntries: response.member }; return response; diff --git a/angular/src/assets/i18n/en.json b/angular/src/assets/i18n/en.json index 78d932e..b8516da 100644 --- a/angular/src/assets/i18n/en.json +++ b/angular/src/assets/i18n/en.json @@ -44,7 +44,7 @@ }, "common": { "code": "Code", - "created_at": "Created at", + "created_at": "Created at (UTC)", "date": "Date", "name": "Name", "note": "Note", @@ -107,7 +107,8 @@ }, "trip_location": { "arrival_date_time": "ETA", - "departure_date_time": "ETD" + "departure_date_time": "ETD", + "overlap_error": "These dates and times overlap with other entries, please check!!" }, "user_trip": { "approved": "Approved",