| @@ -1659,6 +1659,56 @@ paths: | |||||
| style: form | style: form | ||||
| explode: false | explode: false | ||||
| allowReserved: false | allowReserved: false | ||||
| - | |||||
| name: userTrip | |||||
| in: query | |||||
| description: '' | |||||
| required: false | |||||
| deprecated: false | |||||
| allowEmptyValue: false | |||||
| schema: | |||||
| type: string | |||||
| style: form | |||||
| explode: false | |||||
| allowReserved: false | |||||
| - | |||||
| name: 'userTrip[]' | |||||
| in: query | |||||
| description: '' | |||||
| required: false | |||||
| deprecated: false | |||||
| allowEmptyValue: false | |||||
| schema: | |||||
| type: array | |||||
| items: | |||||
| type: string | |||||
| style: form | |||||
| explode: true | |||||
| allowReserved: false | |||||
| - | |||||
| name: custom_json_filter | |||||
| in: query | |||||
| description: '' | |||||
| required: false | |||||
| deprecated: false | |||||
| allowEmptyValue: false | |||||
| schema: | |||||
| type: string | |||||
| style: form | |||||
| explode: false | |||||
| allowReserved: false | |||||
| - | |||||
| name: custom_json_order | |||||
| in: query | |||||
| description: '' | |||||
| required: false | |||||
| deprecated: false | |||||
| allowEmptyValue: false | |||||
| schema: | |||||
| type: string | |||||
| style: form | |||||
| explode: false | |||||
| allowReserved: false | |||||
| deprecated: false | deprecated: false | ||||
| post: | post: | ||||
| operationId: api_user_trip_events_post | operationId: api_user_trip_events_post | ||||
| @@ -1717,6 +1767,31 @@ paths: | |||||
| explode: false | explode: false | ||||
| allowReserved: false | allowReserved: false | ||||
| deprecated: false | deprecated: false | ||||
| delete: | |||||
| operationId: api_user_trip_events_id_delete | |||||
| tags: | |||||
| - UserTripEvent | |||||
| responses: | |||||
| '204': | |||||
| description: 'UserTripEvent resource deleted' | |||||
| '404': | |||||
| description: 'Resource not found' | |||||
| summary: 'Removes the UserTripEvent resource.' | |||||
| description: 'Removes the UserTripEvent resource.' | |||||
| parameters: | |||||
| - | |||||
| name: id | |||||
| in: path | |||||
| description: 'UserTripEvent identifier' | |||||
| required: true | |||||
| deprecated: false | |||||
| allowEmptyValue: false | |||||
| schema: | |||||
| type: string | |||||
| style: simple | |||||
| explode: false | |||||
| allowReserved: false | |||||
| deprecated: false | |||||
| patch: | patch: | ||||
| operationId: api_user_trip_events_id_patch | operationId: api_user_trip_events_id_patch | ||||
| tags: | tags: | ||||
| @@ -241,4 +241,17 @@ export class SearchSelectComponent implements OnInit, AfterViewInit { | |||||
| } as ListColDefinition, | } as ListColDefinition, | ||||
| ]; | ]; | ||||
| } | } | ||||
| public static getDefaultColDefEvents(subResource?: string): ListColDefinition[] { | |||||
| return [ | |||||
| { | |||||
| name: 'name', | |||||
| text: 'common.name', | |||||
| type: ListComponent.COLUMN_TYPE_TEXT, | |||||
| field: 'name', | |||||
| sortable: true, | |||||
| filterType: FilterBarComponent.FILTER_TYPE_TEXT, | |||||
| } as ListColDefinition, | |||||
| ]; | |||||
| } | |||||
| } | } | ||||
| @@ -104,7 +104,7 @@ | |||||
| [getDataFunction]="getUsers" | [getDataFunction]="getUsers" | ||||
| [displayedDataField]="'fullName'" | [displayedDataField]="'fullName'" | ||||
| [listColDefinitions]="userColDefinitions" | [listColDefinitions]="userColDefinitions" | ||||
| [dataSet]="userTrip.userIri" | |||||
| [dataSet]="userTrip.user" | |||||
| (change)="onUserSelectChange(i)" | (change)="onUserSelectChange(i)" | ||||
| > | > | ||||
| </app-search-select> | </app-search-select> | ||||
| @@ -121,8 +121,8 @@ | |||||
| </div> | </div> | ||||
| <div class="mt-4"> | <div class="mt-4"> | ||||
| <button type="button" class="btn btn-success" (click)="saveAllUserTrips()"> | |||||
| {{ 'trip.save_user_assignments' | translate }} | |||||
| <button type="button" class="btn btn-primary" (click)="saveAllUserTrips()"> | |||||
| {{ 'basic.save' | translate }} | |||||
| </button> | </button> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -26,6 +26,7 @@ import {map} from "rxjs/operators"; | |||||
| export class TripDetailComponent implements OnInit, AfterViewInit { | export class TripDetailComponent implements OnInit, AfterViewInit { | ||||
| protected trip!: TripJsonld; | protected trip!: TripJsonld; | ||||
| protected readonly FormMode = FormMode; | protected readonly FormMode = FormMode; | ||||
| protected originalUserTrips: UserTripJsonld[] = []; | |||||
| protected tripLocations: TripLocationJsonld[] = []; | protected tripLocations: TripLocationJsonld[] = []; | ||||
| protected userTrips: UserTripJsonld[] = []; | protected userTrips: UserTripJsonld[] = []; | ||||
| protected users: UserJsonld[] = []; | protected users: UserJsonld[] = []; | ||||
| @@ -212,11 +213,10 @@ export class TripDetailComponent implements OnInit, AfterViewInit { | |||||
| addNewUserTrip() { | addNewUserTrip() { | ||||
| // Erstelle ein unvollständiges Objekt (ohne user-Property) | // Erstelle ein unvollständiges Objekt (ohne user-Property) | ||||
| const newUserTrip: any = { | |||||
| trip: { | |||||
| 'id': this.trip.id | |||||
| } as TripJsonld, | |||||
| user: undefined | |||||
| const newUserTrip: UserTripJsonld = { | |||||
| tripIri: this.trip.id!, | |||||
| userIri: null, | |||||
| completed: false | |||||
| }; | }; | ||||
| // Füge es als UserTripJsonld hinzu | // Füge es als UserTripJsonld hinzu | ||||
| @@ -239,16 +239,28 @@ export class TripDetailComponent implements OnInit, AfterViewInit { | |||||
| 1, | 1, | ||||
| 200, | 200, | ||||
| this.trip !== undefined ? this.trip.id : undefined, | this.trip !== undefined ? this.trip.id : undefined, | ||||
| ).subscribe( | |||||
| data => { | |||||
| ).subscribe({ | |||||
| next: (data) => { | |||||
| this.userTrips = data.member; | this.userTrips = data.member; | ||||
| // Create a form for each user trip | |||||
| this.originalUserTrips = [...data.member]; // Kopie der ursprünglichen UserTrips speichern | |||||
| // Formulare für jeden UserTrip erstellen | |||||
| this.userForms = []; | this.userForms = []; | ||||
| this.userTrips.forEach(() => { | |||||
| this.userForms.push(this.createUserForm()); | |||||
| this.userTrips.forEach((userTrip, index) => { | |||||
| const form = this.createUserForm(); | |||||
| // Formularwerte initialisieren | |||||
| if (userTrip.userIri || (userTrip.user && userTrip.user.id)) { | |||||
| form.get('user')?.setValue(userTrip.userIri || userTrip.user?.id); | |||||
| } | |||||
| this.userForms.push(form); | |||||
| }); | }); | ||||
| }, | |||||
| error: (error) => { | |||||
| console.error('Fehler beim Laden der Benutzerzuweisungen:', error); | |||||
| } | } | ||||
| ); | |||||
| }); | |||||
| } | } | ||||
| removeTripLocation(index: number) { | removeTripLocation(index: number) { | ||||
| @@ -270,21 +282,9 @@ export class TripDetailComponent implements OnInit, AfterViewInit { | |||||
| } | } | ||||
| removeUserTrip(index: number) { | removeUserTrip(index: number) { | ||||
| const userTripId = this.userTrips[index].id; | |||||
| if (userTripId) { | |||||
| // If it exists on the server, delete it | |||||
| this.userTripService.userTripsIdDelete(this.appHelperService.extractId(userTripId)).subscribe( | |||||
| () => { | |||||
| this.userTrips.splice(index, 1); | |||||
| this.userForms.splice(index, 1); | |||||
| } | |||||
| ); | |||||
| } else { | |||||
| // If it's only local, just remove it from the array | |||||
| this.userTrips.splice(index, 1); | |||||
| this.userForms.splice(index, 1); | |||||
| } | |||||
| // Nur aus dem Array entfernen, tatsächliches Löschen erfolgt beim Speichern | |||||
| this.userTrips.splice(index, 1); | |||||
| this.userForms.splice(index, 1); | |||||
| } | } | ||||
| saveAllTripLocations() { | saveAllTripLocations() { | ||||
| @@ -296,9 +296,7 @@ export class TripDetailComponent implements OnInit, AfterViewInit { | |||||
| if (locationFormValue) { | if (locationFormValue) { | ||||
| // If just an ID was set, create a proper location object | // If just an ID was set, create a proper location object | ||||
| if (typeof locationFormValue === 'string') { | if (typeof locationFormValue === 'string') { | ||||
| tripLocation.location = { | |||||
| 'id': locationFormValue | |||||
| } as LocationJsonld; | |||||
| tripLocation.locationIri = locationFormValue; | |||||
| } | } | ||||
| } else { | } else { | ||||
| // If no location is set, show an error | // If no location is set, show an error | ||||
| @@ -338,50 +336,91 @@ export class TripDetailComponent implements OnInit, AfterViewInit { | |||||
| } | } | ||||
| saveAllUserTrips() { | saveAllUserTrips() { | ||||
| // First update the user objects in our userTrips array | |||||
| this.userTrips.forEach((userTrip, index) => { | |||||
| const userFormValue = this.userForms[index].get('user')?.value; | |||||
| // Ensure we have a user | |||||
| if (userFormValue) { | |||||
| // If just an ID was set, create a proper user object | |||||
| if (typeof userFormValue === 'string') { | |||||
| userTrip.userIri = userFormValue; | |||||
| // Aktuelle IDs speichern, um später gelöschte Einträge zu identifizieren | |||||
| let originalUserTripIds: string[] = []; | |||||
| // Zuerst alle existierenden UserTrips vom Server holen | |||||
| this.userTripService.userTripsGetCollection( | |||||
| 1, 200, this.trip.dbId?.toString() | |||||
| ).subscribe({ | |||||
| next: (existingUserTrips) => { | |||||
| // IDs der existierenden UserTrips speichern | |||||
| originalUserTripIds = existingUserTrips.member | |||||
| .filter(userTrip => userTrip.id) | |||||
| .map(userTrip => this.appHelperService.extractId(userTrip.id!)); | |||||
| // Aktualisieren der user objects in unserem userTrips-Array | |||||
| this.userTrips.forEach((userTrip, index) => { | |||||
| const userFormValue = this.userForms[index].get('user')?.value; | |||||
| // User setzen, wenn verfügbar | |||||
| if (userFormValue) { | |||||
| if (typeof userFormValue === 'string') { | |||||
| userTrip.userIri = userFormValue; | |||||
| } | |||||
| } | |||||
| }); | |||||
| // Filtern: Nur UserTrips mit gültigen User-Zuweisungen behalten | |||||
| const validUserTrips = this.userTrips.filter(ut => ut.userIri || (ut.user && ut.user.id)); | |||||
| if (validUserTrips.length === 0 && this.userTrips.length > 0) { | |||||
| window.alert('Bitte wähle für jeden Eintrag einen Benutzer aus oder entferne ungenutzte Einträge.'); | |||||
| return; | |||||
| } | } | ||||
| } | |||||
| }); | |||||
| // Check if there are any invalid user trips (without a proper user selection) | |||||
| const invalidEntries = this.userTrips.filter(ut => !ut.user || !ut.user.id); | |||||
| // Array für alle Operationen erstellen | |||||
| const allPromises: Promise<any>[] = []; | |||||
| // 1. Neue UserTrips erstellen und existierende aktualisieren | |||||
| validUserTrips.forEach(userTrip => { | |||||
| if (userTrip.id) { | |||||
| // Existierenden UserTrip aktualisieren | |||||
| const id = this.appHelperService.extractId(userTrip.id); | |||||
| // ID aus der Liste der zu löschenden IDs entfernen | |||||
| const idIndex = originalUserTripIds.indexOf(id); | |||||
| if (idIndex > -1) { | |||||
| originalUserTripIds.splice(idIndex, 1); | |||||
| } | |||||
| // Update-Promise hinzufügen | |||||
| allPromises.push( | |||||
| firstValueFrom(this.userTripService.userTripsIdPatch( | |||||
| id, | |||||
| this.appHelperService.convertJsonldToJson(userTrip) | |||||
| )) | |||||
| ); | |||||
| } else { | |||||
| // Neuen UserTrip erstellen | |||||
| allPromises.push( | |||||
| firstValueFrom(this.userTripService.userTripsPost(userTrip)) | |||||
| ); | |||||
| } | |||||
| }); | |||||
| if (invalidEntries.length > 0) { | |||||
| // Show alert to the user | |||||
| window.alert('Please select a user for all entries or remove unused entries before saving.'); | |||||
| return; | |||||
| } | |||||
| // 2. Gelöschte UserTrips von der Datenbank entfernen | |||||
| originalUserTripIds.forEach(id => { | |||||
| allPromises.push( | |||||
| firstValueFrom(this.userTripService.userTripsIdDelete(id)) | |||||
| ); | |||||
| }); | |||||
| // At this point, all entries are valid | |||||
| const savePromises = this.userTrips.map(userTrip => { | |||||
| if (userTrip.id) { | |||||
| // Update existing | |||||
| return firstValueFrom(this.userTripService.userTripsIdPatch( | |||||
| this.appHelperService.extractId(userTrip.id), | |||||
| this.appHelperService.convertJsonldToJson(userTrip) | |||||
| )); | |||||
| } else { | |||||
| // Create new | |||||
| return firstValueFrom(this.userTripService.userTripsPost(userTrip)); | |||||
| // Alle Operationen ausführen | |||||
| Promise.all(allPromises) | |||||
| .then(() => { | |||||
| // Nach dem Speichern alle UserTrips neu laden | |||||
| this.loadUserTrips(); | |||||
| }) | |||||
| .catch(error => { | |||||
| console.error('Fehler beim Speichern der Benutzerzuweisungen:', error); | |||||
| window.alert('Beim Speichern der Benutzerzuweisungen ist ein Fehler aufgetreten. Bitte versuche es erneut.'); | |||||
| }); | |||||
| }, | |||||
| error: (error) => { | |||||
| console.error('Fehler beim Laden der existierenden Benutzerzuweisungen:', error); | |||||
| window.alert('Die aktuellen Benutzerzuweisungen konnten nicht geladen werden. Bitte versuche es erneut.'); | |||||
| } | } | ||||
| }); | }); | ||||
| Promise.all(savePromises) | |||||
| .then(() => { | |||||
| // Reload user trips to get updated data | |||||
| this.loadUserTrips(); | |||||
| }) | |||||
| .catch(error => { | |||||
| console.error('Error saving user trips:', error); | |||||
| window.alert('An error occurred while saving user assignments. Please try again.'); | |||||
| }); | |||||
| } | } | ||||
| } | } | ||||
| @@ -16,7 +16,91 @@ | |||||
| </app-user-trip-form> | </app-user-trip-form> | ||||
| </mat-tab> | </mat-tab> | ||||
| <mat-tab label="{{ 'user_trip.events' | translate }}"> | <mat-tab label="{{ 'user_trip.events' | translate }}"> | ||||
| <div> | |||||
| <h4 class="mb-4">{{ 'user_trip.events' | translate }}</h4> | |||||
| <div *ngFor="let userTripEvent of userTripEvents; let i = index" class="mb-4"> | |||||
| <div class="row"> | |||||
| <div class="col-12 col-md-3 mb-3"> | |||||
| <label [for]="'event_' + i" class="form-label">{{ 'model.event' | translate }}*:</label> | |||||
| <app-search-select | |||||
| [formId]="'eventIri'" | |||||
| [formLabelLangKey]="'model.event'" | |||||
| [documentForm]="eventForms[i]" | |||||
| [getDataFunction]="getEvents" | |||||
| [displayedDataField]="'name'" | |||||
| [listColDefinitions]="eventColDefinitions" | |||||
| [dataSet]="userTripEvent.event" | |||||
| > | |||||
| </app-search-select> | |||||
| </div> | |||||
| <div class="col-12 col-md-3 mb-3"> | |||||
| <label [for]="'location_' + i" class="form-label">{{ 'model.location' | translate }}*:</label> | |||||
| <app-search-select | |||||
| [formId]="'locationIri'" | |||||
| [formLabelLangKey]="'model.location'" | |||||
| [documentForm]="eventForms[i]" | |||||
| [getDataFunction]="getLocations" | |||||
| [displayedDataField]="'name'" | |||||
| [listColDefinitions]="locationColDefinitions" | |||||
| [dataSet]="userTripEvent.location" | |||||
| > | |||||
| </app-search-select> | |||||
| </div> | |||||
| <div class="col-12 col-md-2 mb-3"> | |||||
| <label class="form-label">{{ 'user_trip.event_date' | translate }} ({{ 'common.date' | translate }}):</label> | |||||
| <div> | |||||
| <input | |||||
| type="date" | |||||
| class="form-control" | |||||
| [value]="formatDateForInput(userTripEvent.date)" | |||||
| (change)="onDateInputChange($event, i)" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-12 col-md-2 mb-3"> | |||||
| <label class="form-label">{{ 'user_trip.event_date' | translate }} ({{ 'common.time' | translate }}):</label> | |||||
| <div> | |||||
| <input | |||||
| type="time" | |||||
| class="form-control" | |||||
| [value]="formatTimeForInput(userTripEvent.date)" | |||||
| (change)="onTimeInputChange($event, i)" | |||||
| /> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-12 col-md-1 mb-3 d-flex align-items-end"> | |||||
| <button type="button" class="btn btn-danger" (click)="removeUserTripEvent(i)">X</button> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <div class="col-12 mb-3"> | |||||
| <label [for]="'note_' + i" class="form-label">{{ 'common.note' | translate }}:</label> | |||||
| <textarea | |||||
| class="form-control" | |||||
| [id]="'note_' + i" | |||||
| rows="2" | |||||
| [(ngModel)]="userTripEvent.note" | |||||
| ></textarea> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="my-3"> | |||||
| <button type="button" class="btn btn-primary" (click)="addNewUserTripEvent()">+</button> | |||||
| </div> | |||||
| <div class="mt-4"> | |||||
| <button type="button" class="btn btn-success" (click)="saveAllUserTripEvents()"> | |||||
| {{ 'basic.save' | translate }} | |||||
| </button> | |||||
| </div> | |||||
| </div> | |||||
| </mat-tab> | </mat-tab> | ||||
| </mat-tab-group> | </mat-tab-group> | ||||
| } | |||||
| } | |||||
| @@ -1,52 +1,294 @@ | |||||
| import { Component } from '@angular/core'; | |||||
| import { Component, OnInit, AfterViewInit, QueryList, ViewChildren } from '@angular/core'; | |||||
| import { FormBuilder, FormGroup } from '@angular/forms'; | |||||
| import { | import { | ||||
| LocationService, | LocationService, | ||||
| TripJsonld, | TripJsonld, | ||||
| TripLocationService, | |||||
| TripService, UserService, | |||||
| LocationJsonld, | |||||
| UserService, | |||||
| UserTripJsonld, | UserTripJsonld, | ||||
| UserTripService | |||||
| UserTripService, | |||||
| UserTripEventJsonld, | |||||
| UserTripEventService, | |||||
| EventService, | |||||
| EventJsonld | |||||
| } from "@app/core/api/v1"; | } from "@app/core/api/v1"; | ||||
| import {AppHelperService} from "@app/_helpers/app-helper.service"; | |||||
| import {ActivatedRoute} from "@angular/router"; | |||||
| import {FormBuilder} from "@angular/forms"; | |||||
| import {FormMode, FormSubmitEvent} from "@app/_components/_abstract/abstract-data-form-component"; | |||||
| import {ModalStatus} from "@app/_helpers/modal.states"; | |||||
| import { AppHelperService } from "@app/_helpers/app-helper.service"; | |||||
| import { ActivatedRoute } from "@angular/router"; | |||||
| import { FormMode, FormSubmitEvent } from "@app/_components/_abstract/abstract-data-form-component"; | |||||
| import { ModalStatus } from "@app/_helpers/modal.states"; | |||||
| import { firstValueFrom, Observable } from 'rxjs'; | |||||
| import { ListColDefinition } from '@app/_components/list/list-col-definition'; | |||||
| import { SearchSelectComponent } from '@app/_components/search-select/search-select.component'; | |||||
| import { ListComponent } from '@app/_components/list/list.component'; | |||||
| import { FilterBarComponent } from "@app/_components/filter-bar/filter-bar.component"; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-user-trip-detail', | |||||
| templateUrl: './user-trip-detail.component.html', | |||||
| styleUrl: './user-trip-detail.component.scss' | |||||
| selector: 'app-user-trip-detail', | |||||
| templateUrl: './user-trip-detail.component.html', | |||||
| styleUrl: './user-trip-detail.component.scss' | |||||
| }) | }) | ||||
| export class UserTripDetailComponent { | |||||
| export class UserTripDetailComponent implements OnInit, AfterViewInit { | |||||
| protected userTrip!: UserTripJsonld; | protected userTrip!: UserTripJsonld; | ||||
| protected readonly FormMode = FormMode; | protected readonly FormMode = FormMode; | ||||
| // Events related properties | |||||
| protected userTripEvents: UserTripEventJsonld[] = []; | |||||
| protected originalUserTripEvents: UserTripEventJsonld[] = []; // Original-Events zum Vergleich | |||||
| protected eventForms: FormGroup[] = []; | |||||
| protected locationColDefinitions: ListColDefinition[] = SearchSelectComponent.getDefaultColDefLocations(); | |||||
| protected eventColDefinitions: ListColDefinition[] = []; | |||||
| @ViewChildren(SearchSelectComponent) searchSelects!: QueryList<SearchSelectComponent>; | |||||
| constructor( | constructor( | ||||
| private tripService: TripService, | |||||
| private tripLocationService: TripLocationService, | |||||
| private locationService: LocationService, | private locationService: LocationService, | ||||
| private userTripService: UserTripService, | private userTripService: UserTripService, | ||||
| private userService: UserService, | |||||
| private userTripEventService: UserTripEventService, | |||||
| private eventService: EventService, | |||||
| protected appHelperService: AppHelperService, | protected appHelperService: AppHelperService, | ||||
| private route: ActivatedRoute, | private route: ActivatedRoute, | ||||
| private fb: FormBuilder | private fb: FormBuilder | ||||
| ) {} | |||||
| ) { | |||||
| // Initialize event column definitions | |||||
| this.eventColDefinitions = SearchSelectComponent.getDefaultColDefEvents(); | |||||
| } | |||||
| ngOnInit() { | ngOnInit() { | ||||
| this.route.params.subscribe(params => { | this.route.params.subscribe(params => { | ||||
| this.userTripService.userTripsIdGet(params['id']).subscribe( | this.userTripService.userTripsIdGet(params['id']).subscribe( | ||||
| data => { | data => { | ||||
| this.userTrip = data; | this.userTrip = data; | ||||
| this.loadUserTripEvents(); | |||||
| } | } | ||||
| ); | ); | ||||
| }); | }); | ||||
| } | } | ||||
| ngAfterViewInit() { | |||||
| // Reinitialize search selects when they change | |||||
| this.searchSelects.changes.subscribe(components => { | |||||
| components.forEach((component: SearchSelectComponent) => { | |||||
| // Force search selects to initialize | |||||
| if (component.dataSet) { | |||||
| component.ngAfterViewInit(); | |||||
| } | |||||
| }); | |||||
| }); | |||||
| } | |||||
| onFormUpdate(event: FormSubmitEvent<UserTripJsonld>) { | onFormUpdate(event: FormSubmitEvent<UserTripJsonld>) { | ||||
| if (event.status === ModalStatus.Submitted && event.data) { | if (event.status === ModalStatus.Submitted && event.data) { | ||||
| this.userTrip = event.data; | this.userTrip = event.data; | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| loadUserTripEvents() { | |||||
| if (!this.userTrip || !this.userTrip.id) return; | |||||
| this.userTripEventService.userTripEventsGetCollection( | |||||
| 1, | |||||
| 200, | |||||
| this.userTrip.id | |||||
| ).subscribe({ | |||||
| next: (data) => { | |||||
| this.userTripEvents = data.member; | |||||
| this.originalUserTripEvents = [...data.member]; // Kopie der ursprünglichen Events speichern | |||||
| // Formular für jedes Event erstellen | |||||
| this.eventForms = []; | |||||
| this.userTripEvents.forEach((event, index) => { | |||||
| const form = this.createEventForm(); | |||||
| // Formularwerte initialisieren | |||||
| if (event.eventIri) { | |||||
| form.get('eventIri')?.setValue(event.eventIri); | |||||
| } | |||||
| if (event.locationIri) { | |||||
| form.get('locationIri')?.setValue(event.locationIri); | |||||
| } | |||||
| this.eventForms.push(form); | |||||
| }); | |||||
| }, | |||||
| error: (error) => { | |||||
| console.error('Fehler beim Laden der Events:', error); | |||||
| } | |||||
| }); | |||||
| } | |||||
| createEventForm(): FormGroup { | |||||
| return this.fb.group({ | |||||
| eventIri: [null], | |||||
| locationIri: [null] | |||||
| }); | |||||
| } | |||||
| getEvents = (page: number, pageSize: number, term?: string): Observable<any> => { | |||||
| return this.eventService.eventsGetCollection(page, pageSize, undefined, term); | |||||
| } | |||||
| getLocations = (page: number, pageSize: number, term?: string): Observable<any> => { | |||||
| return this.locationService.locationsGetCollection(page, pageSize, undefined, term); | |||||
| } | |||||
| formatDateForInput(dateString: string): string { | |||||
| if (!dateString) return ''; | |||||
| const date = new Date(dateString); | |||||
| return date.toISOString().split('T')[0]; | |||||
| } | |||||
| formatTimeForInput(dateString: string): string { | |||||
| if (!dateString) return ''; | |||||
| const date = new Date(dateString); | |||||
| return date.toTimeString().slice(0, 5); | |||||
| } | |||||
| onDateInputChange(event: Event, index: number) { | |||||
| const input = event.target as HTMLInputElement; | |||||
| const currentDate = new Date(this.userTripEvents[index].date || new Date()); | |||||
| const [year, month, day] = input.value.split('-').map(Number); | |||||
| currentDate.setFullYear(year, month - 1, day); | |||||
| this.userTripEvents[index].date = currentDate.toISOString(); | |||||
| } | |||||
| onTimeInputChange(event: Event, index: number) { | |||||
| const input = event.target as HTMLInputElement; | |||||
| const currentDate = new Date(this.userTripEvents[index].date || new Date()); | |||||
| const [hours, minutes] = input.value.split(':').map(Number); | |||||
| currentDate.setHours(hours, minutes); | |||||
| this.userTripEvents[index].date = currentDate.toISOString(); | |||||
| } | |||||
| addNewUserTripEvent() { | |||||
| // Neues leeres User-Trip-Event erstellen | |||||
| const newUserTripEvent: UserTripEventJsonld = { | |||||
| userTripIri: this.userTrip.id!, | |||||
| date: new Date().toISOString(), | |||||
| eventIri: null, | |||||
| locationIri: null, | |||||
| note: null | |||||
| }; | |||||
| this.userTripEvents.push(newUserTripEvent); | |||||
| this.eventForms.push(this.createEventForm()); | |||||
| // Im nächsten Event-Loop erzwingen, dass die search-select-Komponenten initialisiert werden | |||||
| setTimeout(() => { | |||||
| if (this.searchSelects) { | |||||
| const lastSelects = this.searchSelects.toArray().slice(-2); | |||||
| lastSelects.forEach(select => { | |||||
| if (select) { | |||||
| select.ngAfterViewInit(); | |||||
| } | |||||
| }); | |||||
| } | |||||
| }); | |||||
| } | |||||
| removeUserTripEvent(index: number) { | |||||
| // Nur aus dem Array entfernen, tatsächliches Löschen erfolgt beim Speichern | |||||
| this.userTripEvents.splice(index, 1); | |||||
| this.eventForms.splice(index, 1); | |||||
| } | |||||
| saveAllUserTripEvents() { | |||||
| // Aktuelle IDs speichern, um später gelöschte Einträge zu identifizieren | |||||
| let originalEventIds: string[] = []; | |||||
| // Zuerst alle existierenden Events vom Server holen | |||||
| this.userTripEventService.userTripEventsGetCollection( | |||||
| 1, 200, this.userTrip.id | |||||
| ).subscribe({ | |||||
| next: (existingEvents) => { | |||||
| // IDs der existierenden Events speichern | |||||
| originalEventIds = existingEvents.member | |||||
| .filter(event => event.id) | |||||
| .map(event => this.appHelperService.extractId(event.id!)); | |||||
| // Aktualisieren der Event- und Location-Objekte in unserem userTripEvents-Array | |||||
| this.userTripEvents.forEach((userTripEvent, index) => { | |||||
| const eventFormValue = this.eventForms[index].get('eventIri')?.value; | |||||
| const locationFormValue = this.eventForms[index].get('locationIri')?.value; | |||||
| // Event setzen, wenn verfügbar | |||||
| if (eventFormValue) { | |||||
| if (typeof eventFormValue === 'string') { | |||||
| userTripEvent.eventIri = eventFormValue; | |||||
| } | |||||
| } | |||||
| // Location setzen, wenn verfügbar | |||||
| if (locationFormValue) { | |||||
| if (typeof locationFormValue === 'string') { | |||||
| userTripEvent.locationIri = locationFormValue; | |||||
| } | |||||
| } | |||||
| }); | |||||
| // Events filtern, die sowohl Event als auch Location haben | |||||
| const validUserTripEvents = this.userTripEvents.filter( | |||||
| ute => ute.eventIri && ute.locationIri | |||||
| ); | |||||
| if (validUserTripEvents.length === 0 && this.userTripEvents.length > 0) { | |||||
| window.alert('Bitte stelle sicher, dass alle Events sowohl einen Event-Typ als auch einen Ort haben.'); | |||||
| return; | |||||
| } | |||||
| // Array für alle Operationen erstellen | |||||
| const allPromises: Promise<any>[] = []; | |||||
| // 1. Neue Events erstellen und existierende aktualisieren | |||||
| validUserTripEvents.forEach(userTripEvent => { | |||||
| if (userTripEvent.id) { | |||||
| // Existierendes Event aktualisieren | |||||
| const id = this.appHelperService.extractId(userTripEvent.id); | |||||
| // ID aus der Liste der zu löschenden IDs entfernen | |||||
| const idIndex = originalEventIds.indexOf(id); | |||||
| if (idIndex > -1) { | |||||
| originalEventIds.splice(idIndex, 1); | |||||
| } | |||||
| // Update-Promise hinzufügen | |||||
| allPromises.push( | |||||
| firstValueFrom(this.userTripEventService.userTripEventsIdPatch( | |||||
| id, | |||||
| this.appHelperService.convertJsonldToJson(userTripEvent) | |||||
| )) | |||||
| ); | |||||
| } else { | |||||
| // Neues Event erstellen | |||||
| allPromises.push( | |||||
| firstValueFrom(this.userTripEventService.userTripEventsPost(userTripEvent)) | |||||
| ); | |||||
| } | |||||
| }); | |||||
| // 2. Gelöschte Events von der Datenbank entfernen | |||||
| originalEventIds.forEach(id => { | |||||
| allPromises.push( | |||||
| firstValueFrom(this.userTripEventService.userTripEventsIdDelete(id)) | |||||
| ); | |||||
| }); | |||||
| // Alle Operationen ausführen | |||||
| Promise.all(allPromises) | |||||
| .then(() => { | |||||
| // Nach dem Speichern alle Events neu laden | |||||
| this.loadUserTripEvents(); | |||||
| }) | |||||
| .catch(error => { | |||||
| console.error('Fehler beim Speichern der Events:', error); | |||||
| window.alert('Beim Speichern der Events ist ein Fehler aufgetreten. Bitte versuche es erneut.'); | |||||
| }); | |||||
| }, | |||||
| error: (error) => { | |||||
| console.error('Fehler beim Laden der existierenden Events:', error); | |||||
| window.alert('Die aktuellen Events konnten nicht geladen werden. Bitte versuche es erneut.'); | |||||
| } | |||||
| }); | |||||
| } | |||||
| } | |||||
| @@ -100,13 +100,17 @@ export class UserTripEventService { | |||||
| * Retrieves the collection of UserTripEvent resources. | * Retrieves the collection of UserTripEvent resources. | ||||
| * @param page The collection page number | * @param page The collection page number | ||||
| * @param itemsPerPage The number of items per page | * @param itemsPerPage The number of items per page | ||||
| * @param userTrip | |||||
| * @param userTrip2 | |||||
| * @param customJsonFilter | |||||
| * @param customJsonOrder | |||||
| * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. | * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. | ||||
| * @param reportProgress flag to report request and response progress. | * @param reportProgress flag to report request and response progress. | ||||
| */ | */ | ||||
| public userTripEventsGetCollection(page?: number, itemsPerPage?: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<ApiUserTripEventsGetCollection200Response>; | |||||
| public userTripEventsGetCollection(page?: number, itemsPerPage?: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<ApiUserTripEventsGetCollection200Response>>; | |||||
| public userTripEventsGetCollection(page?: number, itemsPerPage?: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<ApiUserTripEventsGetCollection200Response>>; | |||||
| public userTripEventsGetCollection(page?: number, itemsPerPage?: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<any> { | |||||
| public userTripEventsGetCollection(page?: number, itemsPerPage?: number, userTrip?: string, userTrip2?: Array<string>, customJsonFilter?: string, customJsonOrder?: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<ApiUserTripEventsGetCollection200Response>; | |||||
| public userTripEventsGetCollection(page?: number, itemsPerPage?: number, userTrip?: string, userTrip2?: Array<string>, customJsonFilter?: string, customJsonOrder?: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<ApiUserTripEventsGetCollection200Response>>; | |||||
| public userTripEventsGetCollection(page?: number, itemsPerPage?: number, userTrip?: string, userTrip2?: Array<string>, customJsonFilter?: string, customJsonOrder?: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<ApiUserTripEventsGetCollection200Response>>; | |||||
| public userTripEventsGetCollection(page?: number, itemsPerPage?: number, userTrip?: string, userTrip2?: Array<string>, customJsonFilter?: string, customJsonOrder?: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/ld+json', context?: HttpContext, transferCache?: boolean}): Observable<any> { | |||||
| let localVarQueryParameters = new HttpParams({encoder: this.encoder}); | let localVarQueryParameters = new HttpParams({encoder: this.encoder}); | ||||
| if (page !== undefined && page !== null) { | if (page !== undefined && page !== null) { | ||||
| @@ -117,6 +121,24 @@ export class UserTripEventService { | |||||
| localVarQueryParameters = this.addToHttpParams(localVarQueryParameters, | localVarQueryParameters = this.addToHttpParams(localVarQueryParameters, | ||||
| <any>itemsPerPage, 'itemsPerPage'); | <any>itemsPerPage, 'itemsPerPage'); | ||||
| } | } | ||||
| if (userTrip !== undefined && userTrip !== null) { | |||||
| localVarQueryParameters = this.addToHttpParams(localVarQueryParameters, | |||||
| <any>userTrip, 'userTrip'); | |||||
| } | |||||
| if (userTrip2) { | |||||
| userTrip2.forEach((element) => { | |||||
| localVarQueryParameters = this.addToHttpParams(localVarQueryParameters, | |||||
| <any>element, 'userTrip[]'); | |||||
| }) | |||||
| } | |||||
| if (customJsonFilter !== undefined && customJsonFilter !== null) { | |||||
| localVarQueryParameters = this.addToHttpParams(localVarQueryParameters, | |||||
| <any>customJsonFilter, 'custom_json_filter'); | |||||
| } | |||||
| if (customJsonOrder !== undefined && customJsonOrder !== null) { | |||||
| localVarQueryParameters = this.addToHttpParams(localVarQueryParameters, | |||||
| <any>customJsonOrder, 'custom_json_order'); | |||||
| } | |||||
| let localVarHeaders = this.defaultHeaders; | let localVarHeaders = this.defaultHeaders; | ||||
| @@ -176,6 +198,77 @@ export class UserTripEventService { | |||||
| ); | ); | ||||
| } | } | ||||
| /** | |||||
| * Removes the UserTripEvent resource. | |||||
| * Removes the UserTripEvent resource. | |||||
| * @param id UserTripEvent identifier | |||||
| * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. | |||||
| * @param reportProgress flag to report request and response progress. | |||||
| */ | |||||
| public userTripEventsIdDelete(id: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<any>; | |||||
| public userTripEventsIdDelete(id: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<any>>; | |||||
| public userTripEventsIdDelete(id: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<any>>; | |||||
| public userTripEventsIdDelete(id: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable<any> { | |||||
| if (id === null || id === undefined) { | |||||
| throw new Error('Required parameter id was null or undefined when calling userTripEventsIdDelete.'); | |||||
| } | |||||
| let localVarHeaders = this.defaultHeaders; | |||||
| let localVarCredential: string | undefined; | |||||
| // authentication (JWT) required | |||||
| localVarCredential = this.configuration.lookupCredential('JWT'); | |||||
| if (localVarCredential) { | |||||
| localVarHeaders = localVarHeaders.set('Authorization', 'Bearer ' + localVarCredential); | |||||
| } | |||||
| let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; | |||||
| if (localVarHttpHeaderAcceptSelected === undefined) { | |||||
| // to determine the Accept header | |||||
| const httpHeaderAccepts: string[] = [ | |||||
| ]; | |||||
| localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); | |||||
| } | |||||
| if (localVarHttpHeaderAcceptSelected !== undefined) { | |||||
| localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); | |||||
| } | |||||
| let localVarHttpContext: HttpContext | undefined = options && options.context; | |||||
| if (localVarHttpContext === undefined) { | |||||
| localVarHttpContext = new HttpContext(); | |||||
| } | |||||
| let localVarTransferCache: boolean | undefined = options && options.transferCache; | |||||
| if (localVarTransferCache === undefined) { | |||||
| localVarTransferCache = true; | |||||
| } | |||||
| let responseType_: 'text' | 'json' | 'blob' = 'json'; | |||||
| if (localVarHttpHeaderAcceptSelected) { | |||||
| if (localVarHttpHeaderAcceptSelected.startsWith('text')) { | |||||
| responseType_ = 'text'; | |||||
| } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { | |||||
| responseType_ = 'json'; | |||||
| } else { | |||||
| responseType_ = 'blob'; | |||||
| } | |||||
| } | |||||
| let localVarPath = `/api/user_trip_events/${this.configuration.encodeParam({name: "id", value: id, in: "path", style: "simple", explode: false, dataType: "string", dataFormat: undefined})}`; | |||||
| return this.httpClient.request<any>('delete', `${this.configuration.basePath}${localVarPath}`, | |||||
| { | |||||
| context: localVarHttpContext, | |||||
| responseType: <any>responseType_, | |||||
| withCredentials: this.configuration.withCredentials, | |||||
| headers: localVarHeaders, | |||||
| observe: observe, | |||||
| transferCache: localVarTransferCache, | |||||
| reportProgress: reportProgress | |||||
| } | |||||
| ); | |||||
| } | |||||
| /** | /** | ||||
| * Retrieves a UserTripEvent resource. | * Retrieves a UserTripEvent resource. | ||||
| * Retrieves a UserTripEvent resource. | * Retrieves a UserTripEvent resource. | ||||
| @@ -59,7 +59,8 @@ | |||||
| "shipping_company": "Shipping Company", | "shipping_company": "Shipping Company", | ||||
| "trip": "Trip", | "trip": "Trip", | ||||
| "user": "User", | "user": "User", | ||||
| "user_trip": "User Trip" | |||||
| "user_trip": "User Trip", | |||||
| "event": "Event" | |||||
| }, | }, | ||||
| "location": | "location": | ||||
| { | { | ||||
| @@ -88,7 +88,7 @@ class UserApi | |||||
| */ | */ | ||||
| #[ApiProperty(readable: false)] | #[ApiProperty(readable: false)] | ||||
| #[Assert\NotBlank(groups: ['postValidation'])] | #[Assert\NotBlank(groups: ['postValidation'])] | ||||
| public string $password; | |||||
| public ?string $password = null; | |||||
| // Object is null ONLY during deserialization: so this allows isPublished | // Object is null ONLY during deserialization: so this allows isPublished | ||||
| // to be writable in ALL cases (which is ok because the operations are secured). | // to be writable in ALL cases (which is ok because the operations are secured). | ||||
| @@ -2,8 +2,11 @@ | |||||
| namespace App\ApiResource; | namespace App\ApiResource; | ||||
| use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; | |||||
| use ApiPlatform\Metadata\ApiFilter; | |||||
| use ApiPlatform\Metadata\ApiProperty; | use ApiPlatform\Metadata\ApiProperty; | ||||
| use ApiPlatform\Metadata\ApiResource; | use ApiPlatform\Metadata\ApiResource; | ||||
| use ApiPlatform\Metadata\Delete; | |||||
| use ApiPlatform\Metadata\Get; | use ApiPlatform\Metadata\Get; | ||||
| use ApiPlatform\Metadata\GetCollection; | use ApiPlatform\Metadata\GetCollection; | ||||
| use ApiPlatform\Metadata\Patch; | use ApiPlatform\Metadata\Patch; | ||||
| @@ -11,6 +14,8 @@ use ApiPlatform\Metadata\Post; | |||||
| use ApiPlatform\Doctrine\Orm\State\Options; | use ApiPlatform\Doctrine\Orm\State\Options; | ||||
| use App\Entity\UserTrip; | use App\Entity\UserTrip; | ||||
| use App\Entity\UserTripEvent; | use App\Entity\UserTripEvent; | ||||
| use App\Filter\CustomJsonFilter; | |||||
| use App\Filter\CustomJsonOrderFilter; | |||||
| use App\State\EntityClassDtoStateProcessor; | use App\State\EntityClassDtoStateProcessor; | ||||
| use App\State\EntityToDtoStateProvider; | use App\State\EntityToDtoStateProvider; | ||||
| use Symfony\Component\Validator\Constraints as Assert; | use Symfony\Component\Validator\Constraints as Assert; | ||||
| @@ -32,12 +37,19 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| new Patch( | new Patch( | ||||
| security: 'is_granted("ROLE_ADMIN")' | security: 'is_granted("ROLE_ADMIN")' | ||||
| ), | ), | ||||
| new Delete( | |||||
| security: 'is_granted("ROLE_ADMIN")' | |||||
| ) | |||||
| ], | ], | ||||
| security: 'is_granted("ROLE_USER")', | security: 'is_granted("ROLE_USER")', | ||||
| provider: EntityToDtoStateProvider::class, | provider: EntityToDtoStateProvider::class, | ||||
| processor: EntityClassDtoStateProcessor::class, | processor: EntityClassDtoStateProcessor::class, | ||||
| stateOptions: new Options(entityClass: UserTripEvent::class), | stateOptions: new Options(entityClass: UserTripEvent::class), | ||||
| )] | )] | ||||
| #[ApiFilter(SearchFilter::class, properties: ['userTrip' => 'exact'])] | |||||
| #[ApiFilter(CustomJsonFilter::class)] | |||||
| #[ApiFilter(CustomJsonOrderFilter::class)] | |||||
| class UserTripEventApi | class UserTripEventApi | ||||
| { | { | ||||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | #[ApiProperty(readable: false, writable: false, identifier: true)] | ||||
| @@ -40,10 +40,12 @@ class UserTripEvent | |||||
| public function __construct( | public function __construct( | ||||
| UserTrip $userTrip, | UserTrip $userTrip, | ||||
| Event $event, | Event $event, | ||||
| Location $location, | |||||
| DateTimeImmutable $date | DateTimeImmutable $date | ||||
| ) { | ) { | ||||
| $this->userTrip = $userTrip; | $this->userTrip = $userTrip; | ||||
| $this->event = $event; | $this->event = $event; | ||||
| $this->location = $location; | |||||
| $this->date = $date; | $this->date = $date; | ||||
| $this->createdAt = new DateTimeImmutable(); | $this->createdAt = new DateTimeImmutable(); | ||||
| } | } | ||||
| @@ -0,0 +1,49 @@ | |||||
| <?php | |||||
| namespace App\Mapper; | |||||
| use App\ApiResource\EventApi; | |||||
| use App\Entity\Event; | |||||
| use App\Repository\EventRepository; | |||||
| use Symfonycasts\MicroMapper\AsMapper; | |||||
| use Symfonycasts\MicroMapper\MapperInterface; | |||||
| #[AsMapper(from: EventApi::class, to: Event::class)] | |||||
| class EventApiToEntityMapper implements MapperInterface | |||||
| { | |||||
| public function __construct( | |||||
| private EventRepository $repository, | |||||
| ) { | |||||
| } | |||||
| public function load(object $from, string $toClass, array $context): object | |||||
| { | |||||
| $dto = $from; | |||||
| assert($dto instanceof EventApi); | |||||
| if ($dto->id) { | |||||
| $entity = $this->repository->find($dto->id); | |||||
| if (!$entity) { | |||||
| throw new \Exception('Event not found'); | |||||
| } | |||||
| return $entity; | |||||
| } | |||||
| return new Event(); | |||||
| } | |||||
| public function populate(object $from, object $to, array $context): object | |||||
| { | |||||
| $dto = $from; | |||||
| $entity = $to; | |||||
| assert($dto instanceof EventApi); | |||||
| assert($entity instanceof Event); | |||||
| $entity->setName($dto->name); | |||||
| $entity->setIdentifier($dto->identifier); | |||||
| $entity->setSequence($dto->sequence); | |||||
| $entity->setMandatory($dto->mandatory); | |||||
| return $entity; | |||||
| } | |||||
| } | |||||
| @@ -3,6 +3,8 @@ | |||||
| namespace App\Mapper; | namespace App\Mapper; | ||||
| use App\ApiResource\UserTripApi; | use App\ApiResource\UserTripApi; | ||||
| use App\Entity\Trip; | |||||
| use App\Entity\User; | |||||
| use App\Entity\UserTrip; | use App\Entity\UserTrip; | ||||
| use App\Repository\MediaObjectRepository; | use App\Repository\MediaObjectRepository; | ||||
| use App\Repository\UserTripRepository; | use App\Repository\UserTripRepository; | ||||
| @@ -19,7 +21,8 @@ class UserTripApiToEntityMapper implements MapperInterface | |||||
| private UserTripRepository $repository, | private UserTripRepository $repository, | ||||
| private TripRepository $tripRepository, | private TripRepository $tripRepository, | ||||
| private UserRepository $userRepository, | private UserRepository $userRepository, | ||||
| private MediaObjectRepository $mediaObjectRepository | |||||
| private MediaObjectRepository $mediaObjectRepository, | |||||
| private MicroMapperInterface $microMapper | |||||
| ) { | ) { | ||||
| } | } | ||||
| @@ -37,24 +40,25 @@ class UserTripApiToEntityMapper implements MapperInterface | |||||
| } | } | ||||
| // For new user trips, we need trip and user | // For new user trips, we need trip and user | ||||
| if (!$dto->trip?->id || !$dto->user?->id) { | |||||
| if (!$dto->tripIri || !$dto->userIri) { | |||||
| throw new \Exception('Trip and User are required for new user trips'); | throw new \Exception('Trip and User are required for new user trips'); | ||||
| } | } | ||||
| $trip = $this->tripRepository->find($dto->trip->id); | |||||
| $trip = $this->microMapper->map($dto->tripIri, Trip::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| if (!$trip) { | if (!$trip) { | ||||
| throw new \Exception('Trip not found'); | throw new \Exception('Trip not found'); | ||||
| } | } | ||||
| $user = $this->userRepository->find($dto->user->id); | |||||
| $user = $this->microMapper->map($dto->userIri, User::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| if (!$user) { | if (!$user) { | ||||
| throw new \Exception('User not found'); | throw new \Exception('User not found'); | ||||
| } | } | ||||
| return new UserTrip( | |||||
| $trip, | |||||
| $user, | |||||
| ); | |||||
| return new UserTrip($trip, $user); | |||||
| } | } | ||||
| public function populate(object $from, object $to, array $context): object | public function populate(object $from, object $to, array $context): object | ||||
| @@ -2,11 +2,13 @@ | |||||
| namespace App\Mapper; | namespace App\Mapper; | ||||
| use App\ApiResource\UserTripApi; | |||||
| use App\ApiResource\UserTripEventApi; | use App\ApiResource\UserTripEventApi; | ||||
| use App\Entity\Event; | |||||
| use App\Entity\Location; | |||||
| use App\Entity\UserTrip; | |||||
| use App\Entity\UserTripEvent; | use App\Entity\UserTripEvent; | ||||
| use App\Repository\UserTripEventRepository; | use App\Repository\UserTripEventRepository; | ||||
| use App\Repository\UserTripRepository; | |||||
| use App\Repository\EventRepository; | |||||
| use Symfonycasts\MicroMapper\AsMapper; | use Symfonycasts\MicroMapper\AsMapper; | ||||
| use Symfonycasts\MicroMapper\MapperInterface; | use Symfonycasts\MicroMapper\MapperInterface; | ||||
| use Symfonycasts\MicroMapper\MicroMapperInterface; | use Symfonycasts\MicroMapper\MicroMapperInterface; | ||||
| @@ -16,8 +18,7 @@ class UserTripEventApiToEntityMapper implements MapperInterface | |||||
| { | { | ||||
| public function __construct( | public function __construct( | ||||
| private UserTripEventRepository $repository, | private UserTripEventRepository $repository, | ||||
| private UserTripRepository $userTripRepository, | |||||
| private EventRepository $eventRepository, | |||||
| private MicroMapperInterface $microMapper | |||||
| ) { | ) { | ||||
| } | } | ||||
| @@ -35,21 +36,32 @@ class UserTripEventApiToEntityMapper implements MapperInterface | |||||
| } | } | ||||
| // For new user trip events, we need userTrip and event | // For new user trip events, we need userTrip and event | ||||
| if (!$dto->userTrip?->id || !$dto->event?->id) { | |||||
| if (!$dto->userTripIri || !$dto->eventIri || !$dto->locationIri) { | |||||
| throw new \Exception('UserTrip and Event are required for new user trip events'); | throw new \Exception('UserTrip and Event are required for new user trip events'); | ||||
| } | } | ||||
| $userTrip = $this->userTripRepository->find($dto->userTrip->id); | |||||
| $userTrip = $this->microMapper->map($dto->userTripIri, UserTrip::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| if (!$userTrip) { | if (!$userTrip) { | ||||
| throw new \Exception('UserTrip not found'); | throw new \Exception('UserTrip not found'); | ||||
| } | } | ||||
| $event = $this->eventRepository->find($dto->event->id); | |||||
| $event = $this->microMapper->map($dto->eventIri, Event::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| if (!$event) { | if (!$event) { | ||||
| throw new \Exception('Event not found'); | throw new \Exception('Event not found'); | ||||
| } | } | ||||
| return new UserTripEvent($userTrip, $event, $dto->date); | |||||
| $location = $this->microMapper->map($dto->locationIri, Location::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| if (!$location) { | |||||
| throw new \Exception('Location not found'); | |||||
| } | |||||
| return new UserTripEvent($userTrip, $event, $location, $dto->date); | |||||
| } | } | ||||
| public function populate(object $from, object $to, array $context): object | public function populate(object $from, object $to, array $context): object | ||||
| @@ -59,21 +71,29 @@ class UserTripEventApiToEntityMapper implements MapperInterface | |||||
| assert($dto instanceof UserTripEventApi); | assert($dto instanceof UserTripEventApi); | ||||
| assert($entity instanceof UserTripEvent); | assert($entity instanceof UserTripEvent); | ||||
| if ($dto->event) { | |||||
| $event = $this->eventRepository->find($dto->event->id); | |||||
| if (!$event) { | |||||
| throw new \Exception('Event not found'); | |||||
| } | |||||
| $entity->setEvent($event); | |||||
| $userTrip = $this->microMapper->map($dto->userTripIri, UserTrip::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| if (!$userTrip) { | |||||
| throw new \Exception('UserTrip not found'); | |||||
| } | } | ||||
| $entity->setUserTrip($userTrip); | |||||
| if ($dto->userTrip) { | |||||
| $userTrip = $this->userTripRepository->find($dto->userTrip->id); | |||||
| if (!$userTrip) { | |||||
| throw new \Exception('UserTrip not found'); | |||||
| } | |||||
| $entity->setUserTrip($userTrip); | |||||
| $event = $this->microMapper->map($dto->eventIri, Event::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| if (!$event) { | |||||
| throw new \Exception('Event not found'); | |||||
| } | |||||
| $entity->setEvent($event); | |||||
| $location = $this->microMapper->map($dto->locationIri, Location::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| if (!$location) { | |||||
| throw new \Exception('Location not found'); | |||||
| } | } | ||||
| $entity->setLocation($location); | |||||
| $entity->setNote($dto->note); | $entity->setNote($dto->note); | ||||
| $entity->setDate($dto->date); | $entity->setDate($dto->date); | ||||
| @@ -50,7 +50,7 @@ class UserTripEventEntityToApiMapper implements MapperInterface | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | MicroMapperInterface::MAX_DEPTH => 1, | ||||
| ]); | ]); | ||||
| $dto->locationIri = $dto->location = $this->microMapper->map($entity->getEvent(), LocationApi::class, [ | |||||
| $dto->locationIri = $dto->location = $this->microMapper->map($entity->getLocation(), LocationApi::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | MicroMapperInterface::MAX_DEPTH => 1, | ||||
| ]); | ]); | ||||