瀏覽代碼

wip

master
Daniel 1 年之前
父節點
當前提交
5a9d87db66
共有 20 個文件被更改,包括 342 次插入27 次删除
  1. +1
    -1
      angular/openapi.json
  2. +33
    -0
      angular/openapi.yaml
  3. +21
    -19
      angular/src/app/_components/_abstract/abstract-data-form-component.ts
  4. +11
    -2
      angular/src/app/_forms/apiForms.ts
  5. +13
    -0
      angular/src/app/_views/user-trip/user-trip-detail/user-trip-detail.component.html
  6. +10
    -0
      angular/src/app/_views/user-trip/user-trip-detail/user-trip-detail.component.ts
  7. +48
    -0
      angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.html
  8. +0
    -0
      angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.scss
  9. +23
    -0
      angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.spec.ts
  10. +116
    -0
      angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.ts
  11. +16
    -0
      angular/src/app/_views/user-trip/user-trip-list/user-trip-list.component.ts
  12. +2
    -0
      angular/src/app/app.module.ts
  13. +1
    -0
      angular/src/app/core/api/v1/.openapi-generator/FILES
  14. +23
    -0
      angular/src/app/core/api/v1/model/mediaObject.ts
  15. +1
    -0
      angular/src/app/core/api/v1/model/models.ts
  16. +3
    -1
      angular/src/app/core/api/v1/model/userTrip.ts
  17. +3
    -1
      angular/src/app/core/api/v1/model/userTripJsonld.ts
  18. +5
    -1
      angular/src/assets/i18n/en.json
  19. +2
    -2
      httpdocs/src/ApiResource/UserTripApi.php
  20. +10
    -0
      httpdocs/src/Mapper/UserTripEntityToApiMapper.php

+ 1
- 1
angular/openapi.json
文件差異過大導致無法顯示
查看文件


+ 33
- 0
angular/openapi.yaml 查看文件

@@ -2312,6 +2312,33 @@ components:
required:
- name
- code
MediaObject:
type: object
description: ''
deprecated: false
properties:
dbId:
readOnly: true
type:
- integer
- 'null'
contentUrl:
externalDocs:
url: 'https://schema.org/contentUrl'
type:
- string
- 'null'
filePath:
readOnly: true
type:
- string
- 'null'
createdAt:
readOnly: true
type:
- string
- 'null'
format: date-time
MediaObject.jsonld:
type: object
description: ''
@@ -2788,6 +2815,8 @@ components:
- 'null'
completed:
type: boolean
signature:
$ref: '#/components/schemas/MediaObject'
signatureUrl:
readOnly: true
type:
@@ -2807,6 +2836,7 @@ components:
required:
- trip
- user
- completed
UserTrip.jsonld:
type: object
description: ''
@@ -2850,6 +2880,8 @@ components:
- 'null'
completed:
type: boolean
signature:
$ref: '#/components/schemas/MediaObject.jsonld'
signatureUrl:
readOnly: true
type:
@@ -2869,6 +2901,7 @@ components:
required:
- trip
- user
- completed
UserTripEvent:
type: object
description: ''


+ 21
- 19
angular/src/app/_components/_abstract/abstract-data-form-component.ts 查看文件

@@ -31,8 +31,8 @@ export abstract class AbstractDataFormComponent<T extends { [key: string]: any }

constructor(
protected formConfig: FormGroup,
protected createFn: SaveFunction<T>,
protected updateFn: UpdateFunction<T>,
protected createFn?: SaveFunction<T>,
protected updateFn?: UpdateFunction<T>,
protected deleteFn?: DeleteFunction,
protected translateService?: TranslateService,
protected router?: Router
@@ -58,24 +58,26 @@ export abstract class AbstractDataFormComponent<T extends { [key: string]: any }

const formData = this.form.value as T;
const request$ = this.mode === FormMode.Create
? this.createFn(formData)
: this.updateFn(this.id!, formData);
? (this.createFn ? this.createFn(formData) : undefined)
: (this.updateFn ? this.updateFn(this.id!, formData) : undefined);

request$.subscribe({
next: (response) => {
this.submit.emit({
status: ModalStatus.Submitted,
data: response
});
},
error: (error) => {
console.error('Error saving data:', error);
this.submit.emit({
status: ModalStatus.Cancelled, // Statt Error verwenden wir Cancelled
data: null
});
}
});
if (request$ !== undefined) {
request$.subscribe({
next: (response) => {
this.submit.emit({
status: ModalStatus.Submitted,
data: response
});
},
error: (error) => {
console.error('Error saving data:', error);
this.submit.emit({
status: ModalStatus.Cancelled, // Statt Error verwenden wir Cancelled
data: null
});
}
});
}
}

onDelete(): void {


+ 11
- 2
angular/src/app/_forms/apiForms.ts 查看文件

@@ -31,6 +31,13 @@ export const locationJsonldForm = new FormGroup({
createdAt: new FormControl(null, [])
});

export const mediaObjectForm = new FormGroup({
dbId: new FormControl(null, []),
contentUrl: new FormControl(null, []),
filePath: new FormControl(null, []),
createdAt: new FormControl(null, [])
});

export const mediaObjectJsonldForm = new FormGroup({
dbId: new FormControl(null, []),
contentUrl: new FormControl(null, []),
@@ -133,7 +140,8 @@ export const userTripForm = new FormGroup({
trip: new FormControl(null, [Validators.required]),
user: new FormControl(null, [Validators.required]),
captainName: new FormControl(null, []),
completed: new FormControl(null, []),
completed: new FormControl(null, [Validators.required]),
signature: new FormControl(null, []),
signatureUrl: new FormControl(null, []),
completedDate: new FormControl(null, []),
createdAt: new FormControl(null, [])
@@ -144,7 +152,8 @@ export const userTripJsonldForm = new FormGroup({
trip: new FormControl(null, [Validators.required]),
user: new FormControl(null, [Validators.required]),
captainName: new FormControl(null, []),
completed: new FormControl(null, []),
completed: new FormControl(null, [Validators.required]),
signature: new FormControl(null, []),
signatureUrl: new FormControl(null, []),
completedDate: new FormControl(null, []),
createdAt: new FormControl(null, [])


+ 13
- 0
angular/src/app/_views/user-trip/user-trip-detail/user-trip-detail.component.html 查看文件

@@ -5,5 +5,18 @@
{{ userTrip.trip.pilotageReference }} ({{ userTrip.user.fullName }})</h2>
</div>
</div>
<mat-tab-group>
<mat-tab label="{{ 'model.user_trip' | translate }}">
<app-user-trip-form
[data]="userTrip"
[mode]="FormMode.Edit"
[id]="appHelperService.extractId(userTrip.id!)"
(submit)="onFormUpdate($event)"
>
</app-user-trip-form>
</mat-tab>
<mat-tab label="{{ 'user_trip.events' | translate }}">

</mat-tab>
</mat-tab-group>
}

+ 10
- 0
angular/src/app/_views/user-trip/user-trip-detail/user-trip-detail.component.ts 查看文件

@@ -10,6 +10,8 @@ import {
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";

@Component({
selector: 'app-user-trip-detail',
@@ -18,6 +20,7 @@ import {FormBuilder} from "@angular/forms";
})
export class UserTripDetailComponent {
protected userTrip!: UserTripJsonld;
protected readonly FormMode = FormMode;

constructor(
private tripService: TripService,
@@ -39,4 +42,11 @@ export class UserTripDetailComponent {
);
});
}

onFormUpdate(event: FormSubmitEvent<UserTripJsonld>) {
if (event.status === ModalStatus.Submitted && event.data) {
this.userTrip = event.data;
}
}

}

+ 48
- 0
angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.html 查看文件

@@ -0,0 +1,48 @@
<div class="spt-container">
<div class="spt-form">
<form [formGroup]="userTripForm" (ngSubmit)="onSubmit()">

<input type="hidden" formControlName="trip" />
<input type="hidden" formControlName="user" />
<input type="hidden" formControlName="signature" />
<div class="mb-3">
<label for="captainName" class="form-label">{{ 'trip.captain_name' | translate }}:</label>
<input type="text" class="form-control" id="captainName" formControlName="captainName"/>
</div>
<div class="col-12 col-sm-6 col-md-4 col-lg-3 mb-3 switch-widget">
<p class="form-label">{{ 'user_trip.completed' | translate }}:</p>
<label class="switch">
<input type="checkbox" formControlName="completed" [disabled]="data ? data.completed : false">
<span class="slider round"></span>
</label>
</div>

<!-- Neues File-Upload-Feld für MediaObject -->
<div class="mb-3">
<label for="mediaFile" class="form-label">{{ 'user_trip.signature' | translate }}:</label>
<input type="file" class="form-control" id="mediaFile" (change)="onFileSelected($event)"/>
@if (selectedFile) {
<small class="text-muted">{{ selectedFile.name }}</small>
}
@if (data?.signature?.contentUrl) {
<div class="mt-2">
<small>{{ 'user_trip.current_file' | translate }}: {{ getFileNameFromUrl(data?.signature?.contentUrl) }}</small>
</div>
}
</div>
</form>
</div>

<div class="flex gap-2">
<!-- <button type="submit" class="btn btn-primary" [disabled]="form.invalid" (click)="onSubmit()">-->
<button type="submit" class="btn btn-primary" (click)="onSubmit()">
{{ 'basic.save' | translate }}
</button>

@if (isEditMode()) {
<button type="button" class="ms-3 btn btn-primary" (click)="onDelete()">
{{ 'basic.delete' | translate }} {{ 'model.user_trip' | translate }}
</button>
}
</div>
</div>

+ 0
- 0
angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.scss 查看文件


+ 23
- 0
angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.spec.ts 查看文件

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { UserTripFormComponent } from './user-trip-form.component';

describe('UserTripFormComponent', () => {
let component: UserTripFormComponent;
let fixture: ComponentFixture<UserTripFormComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [UserTripFormComponent]
})
.compileComponents();
fixture = TestBed.createComponent(UserTripFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 116
- 0
angular/src/app/_views/user-trip/user-trip-form/user-trip-form.component.ts 查看文件

@@ -0,0 +1,116 @@
import { Component } from '@angular/core';
import {
AbstractDataFormComponent,
FormMode,
FormSubmitEvent
} from "@app/_components/_abstract/abstract-data-form-component";
import {
LocationService, MediaObjectJsonld, MediaObjectService, TripJsonld,
UserTripJsonld,
UserTripService,
VesselService
} from "@app/core/api/v1";
import { tripForm, userTripForm } from "@app/_forms/apiForms";
import { TranslateService } from "@ngx-translate/core";
import { Router } from "@angular/router";
import { ROUTE_USER_TRIPS } from "@app/app-routing.module";
import { ModalStatus } from "@app/_helpers/modal.states";
import { firstValueFrom } from 'rxjs';

@Component({
selector: 'app-user-trip-form',
templateUrl: './user-trip-form.component.html',
styleUrl: './user-trip-form.component.scss'
})
export class UserTripFormComponent extends AbstractDataFormComponent<UserTripJsonld> {

protected readonly userTripForm = userTripForm;
selectedFile: File | null = null;

constructor(
private userTripService: UserTripService,
private mediaObjectService: MediaObjectService,
translateService: TranslateService,
router: Router
) {
super(
userTripForm,
undefined,
(id: string | number, data: UserTripJsonld) => this.userTripService.userTripsIdPatch(id.toString(), data),
(id: string | number) => this.userTripService.userTripsIdDelete(id.toString()),
translateService,
router
);
this.redirectAfterDelete = '/' + ROUTE_USER_TRIPS;
}

override ngOnInit(): void {
super.ngOnInit();

// Debug-Ausgabe für Formularvalidierung
this.form.statusChanges.subscribe(status => {
console.log('Form status:', status);
console.log('Form errors:', this.form.errors);

Object.keys(this.form.controls).forEach(key => {
const control = this.form.get(key);
if (control?.invalid) {
console.log(`Control '${key}' is invalid:`, control.errors);
}
});
});
}

onFileSelected(event: Event): void {
const element = event.target as HTMLInputElement;
if (element.files && element.files.length > 0) {
this.selectedFile = element.files[0];
}
}

getFileNameFromUrl(url: string | null | undefined): string {
if (!url) return '';
return url.split('/').pop() || '';
}

override onSubmit(): void {
//if (!this.form.valid) return;
if (!this.form.valid) {
console.log('Form is invalid:', this.form.errors);
Object.keys(this.form.controls).forEach(key => {
const control = this.form.get(key);
if (control?.invalid) {
console.log(`Control '${key}' is invalid:`, control.errors);
}
});
return;
}

// Wenn keine Datei ausgewählt wurde, rufe direkt die Elternmethode auf
if (!this.selectedFile) {
super.onSubmit();
return;
}

// 1. Handle file upload if a file is selected
// Blob direkt verwenden, keine FormData erforderlich
this.mediaObjectService.mediaObjectsPost(this.selectedFile).subscribe({
next: (mediaObject) => {
// 2. Update the form data with the new mediaObject
this.form.patchValue({
signature: mediaObject
});

// 3. Call the parent method to handle the standard save process
super.onSubmit();
},
error: (error) => {
console.error('Error uploading file:', error);
this.submit.emit({
status: ModalStatus.Cancelled,
data: null
});
}
});
}
}

+ 16
- 0
angular/src/app/_views/user-trip/user-trip-list/user-trip-list.component.ts 查看文件

@@ -74,6 +74,22 @@ export class UserTripListComponent {
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_DATE,
} as ListColDefinition,
{
name: 'completed',
text: 'user_trip.completed',
type: ListComponent.COLUMN_TYPE_BOOLEAN,
field: 'completed',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_BOOLEAN,
} as ListColDefinition,
{
name: 'completedDate',
text: 'user_trip.completed_at',
type: ListComponent.COLUMN_TYPE_DATE,
field: 'completedDate',
sortable: true,
filterType: FilterBarComponent.FILTER_TYPE_DATE,
} as ListColDefinition,
];
}



+ 2
- 0
angular/src/app/app.module.ts 查看文件

@@ -66,6 +66,7 @@ import {DatetimePickerComponent} from "@app/_components/datetime-picker/datetime
import { UserTripComponent } from './_views/user-trip/user-trip.component';
import { UserTripListComponent } from './_views/user-trip/user-trip-list/user-trip-list.component';
import { UserTripDetailComponent } from './_views/user-trip/user-trip-detail/user-trip-detail.component';
import { UserTripFormComponent } from './_views/user-trip/user-trip-form/user-trip-form.component';

registerLocaleData(localeDe, 'de-DE');

@@ -157,6 +158,7 @@ export function HttpLoaderFactory(http: HttpClient) {
UserTripComponent,
UserTripListComponent,
UserTripDetailComponent,
UserTripFormComponent,
],
providers: [
{provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true},


+ 1
- 0
angular/src/app/core/api/v1/.openapi-generator/FILES 查看文件

@@ -39,6 +39,7 @@ model/eventJsonldContext.ts
model/eventJsonldContextOneOf.ts
model/location.ts
model/locationJsonld.ts
model/mediaObject.ts
model/mediaObjectJsonld.ts
model/models.ts
model/shippingCompany.ts


+ 23
- 0
angular/src/app/core/api/v1/model/mediaObject.ts 查看文件

@@ -0,0 +1,23 @@
/**
* Imaq Platform
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/


/**
*
*/
export interface MediaObject {
readonly dbId?: number | null;
contentUrl?: string | null;
readonly filePath?: string | null;
readonly createdAt?: string | null;
}


+ 1
- 0
angular/src/app/core/api/v1/model/models.ts 查看文件

@@ -19,6 +19,7 @@ export * from './eventJsonldContext';
export * from './eventJsonldContextOneOf';
export * from './location';
export * from './locationJsonld';
export * from './mediaObject';
export * from './mediaObjectJsonld';
export * from './shippingCompany';
export * from './shippingCompanyJsonld';


+ 3
- 1
angular/src/app/core/api/v1/model/userTrip.ts 查看文件

@@ -11,6 +11,7 @@
*/
import { Trip } from './trip';
import { User } from './user';
import { MediaObject } from './mediaObject';


/**
@@ -21,7 +22,8 @@ export interface UserTrip {
trip: Trip;
user: User;
captainName?: string | null;
completed?: boolean;
completed: boolean;
signature?: MediaObject;
readonly signatureUrl?: string | null;
completedDate?: string | null;
readonly createdAt?: string | null;


+ 3
- 1
angular/src/app/core/api/v1/model/userTripJsonld.ts 查看文件

@@ -10,6 +10,7 @@
* Do not edit the class manually.
*/
import { TripJsonld } from './tripJsonld';
import { MediaObjectJsonld } from './mediaObjectJsonld';
import { UserJsonld } from './userJsonld';
import { EventJsonldContext } from './eventJsonldContext';

@@ -25,7 +26,8 @@ export interface UserTripJsonld {
trip: TripJsonld;
user: UserJsonld;
captainName?: string | null;
completed?: boolean;
completed: boolean;
signature?: MediaObjectJsonld;
readonly signatureUrl?: string | null;
completedDate?: string | null;
readonly createdAt?: string | null;


+ 5
- 1
angular/src/assets/i18n/en.json 查看文件

@@ -38,7 +38,11 @@
},
"user_trip":
{
"view": "User Trips"
"view": "User Trips",
"events": "Events",
"completed": "Completed",
"completed_at": "Completed at",
"signature": "Signature Image"
},
"base_data":
{


+ 2
- 2
httpdocs/src/ApiResource/UserTripApi.php 查看文件

@@ -95,6 +95,7 @@ class UserTripApi

public ?string $captainName = null;

#[Assert\NotBlank]
public bool $completed;

/**
@@ -111,8 +112,7 @@ class UserTripApi
)
]
)]
#[Assert\NotBlank]
public ?MediaObjectApi $mediaObject = null;
public ?MediaObjectApi $signature = null;

#[ApiProperty(writable: false)]
public ?string $signatureUrl = null;


+ 10
- 0
httpdocs/src/Mapper/UserTripEntityToApiMapper.php 查看文件

@@ -2,6 +2,7 @@

namespace App\Mapper;

use App\ApiResource\MediaObjectApi;
use App\ApiResource\TripApi;
use App\ApiResource\UserApi;
use App\ApiResource\UserTripApi;
@@ -41,6 +42,7 @@ class UserTripEntityToApiMapper implements MapperInterface
$dto->dbId = $entity->getId();
$dto->captainName = $entity->getCaptainName();
$dto->signatureUrl = $this->fileUrlService->getFileUrl($entity->getSignature());
$dto->completed = $entity->isCompleted();
$dto->completedDate = $entity->getCompletedDate();
$dto->createdAt = $entity->getCreatedAt();

@@ -52,6 +54,14 @@ class UserTripEntityToApiMapper implements MapperInterface
MicroMapperInterface::MAX_DEPTH => 1,
]);

$dto->signatureUrl = null;
if ($entity->getSignature() !== null) {
$dto->signatureUrl = $this->microMapper->map($entity->getSignature(), MediaObjectApi::class, [
MicroMapperInterface::MAX_DEPTH => 1,
]);
}


return $dto;
}
}

Loading…
取消
儲存