| @@ -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<string | null>(); | |||
| @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<string | null>(); | |||
| 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); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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, | |||
| @@ -5,7 +5,7 @@ | |||
| </div> | |||
| } @else { | |||
| <div class="spt-headline d-flex justify-content-between align-items-start"> | |||
| <h2>{{ ('basic.edit') | translate }} {{ 'model.trip_location' | translate }}: {{ data?.location?.name }}</h2> | |||
| <h2>{{ ('basic.edit') | translate }} {{ 'model.trip_location' | translate }}</h2> | |||
| </div> | |||
| } | |||
| <div class="spt-form"> | |||
| @@ -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<TripLocationJsonld> { | |||
| 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<TripLoc | |||
| translateService, | |||
| router | |||
| ); | |||
| this.tripLocationEntries = []; | |||
| this.redirectAfterDelete = '/' + ROUTE_BASE_DATA; | |||
| this.locationColDefinitions = SearchSelectComponent.getDefaultColDefLocations(); | |||
| } | |||
| @@ -58,8 +60,11 @@ export class TripLocationFormComponent extends AbstractDataFormComponent<TripLoc | |||
| super.ngOnInit(); | |||
| this.form.get('tripIri')?.setValue(this.trip.id); | |||
| if (!this.isEditMode()) { | |||
| this.form.get('arrivalDateTime')?.setValue(this.arrivalDateTime); | |||
| this.form.get('departureDateTime')?.setValue(this.departureDateTime); | |||
| this.form.get('newArrivalDateTime')?.setValue(this.newArrivalDateTime); | |||
| this.form.get('newDepartureDateTime')?.setValue(this.newDepartureDateTime); | |||
| } else { | |||
| this.form.get('newArrivalDateTime')?.setValue(this.data?.arrivalDateTime); | |||
| this.form.get('newDepartureDateTime')?.setValue(this.data?.departureDateTime); | |||
| } | |||
| } | |||
| @@ -72,8 +77,115 @@ export class TripLocationFormComponent extends AbstractDataFormComponent<TripLoc | |||
| } | |||
| override onSubmit() { | |||
| const formData = this.form.value as TripLocationJsonld; | |||
| if (this.hasDateOverlap(formData)) { | |||
| this.translateService?.get('trip_location.overlap_error').subscribe((error: string) => { | |||
| 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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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", | |||