First try

This commit is contained in:
2026-01-13 11:12:51 +01:00
parent af44e50f54
commit 93485efc5c
16 changed files with 525 additions and 486 deletions

View File

@@ -4,25 +4,33 @@
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {
"skydivelogs-app": { "skydivelogs-app": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "", "root": "",
"sourceRoot": "src", "sourceRoot": "src",
"projectType": "application", "prefix": "app",
"architect": { "architect": {
"build": { "build": {
"builder": "@angular-devkit/build-angular:application", "builder": "@angular-devkit/build-angular:application",
"options": { "options": {
"outputPath": { "outputPath": "dist",
"base": "dist"
},
"index": "src/index.html", "index": "src/index.html",
"tsConfig": "src/tsconfig.app.json", "browser": "src/main.ts",
"polyfills": [ "polyfills": ["zone.js"],
"src/polyfills.ts" "tsConfig": "tsconfig.app.json",
], "inlineStyleLanguage": "scss",
"assets": [ "assets": [
"src/assets", "src/assets",
"src/config", "src/config",
"src/favicon.ico" "src/favicon.ico",
{
"glob": "**/*",
"input": "public"
}
], ],
"styles": [ "styles": [
"src/assets/css/styles-app-loading.scss", "src/assets/css/styles-app-loading.scss",
@@ -30,26 +38,28 @@
"src/assets/css/new-theme.scss", "src/assets/css/new-theme.scss",
"@angular/material/prebuilt-themes/pink-bluegrey.css" "@angular/material/prebuilt-themes/pink-bluegrey.css"
], ],
"scripts": [], "scripts": []
"extractLicenses": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true,
"browser": "src/main.ts"
}, },
"configurations": { "configurations": {
"production": { "production": {
"budgets": [ "budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{ {
"type": "anyComponentStyle", "type": "anyComponentStyle",
"maximumWarning": "6kb" "maximumWarning": "4kB",
"maximumError": "8kB"
} }
], ],
"optimization": true, "outputHashing": "all"
"outputHashing": "all", },
"sourceMap": false, "development": {
"namedChunks": false, "optimization": false,
"extractLicenses": true, "extractLicenses": false,
"sourceMap": true,
"fileReplacements": [ "fileReplacements": [
{ {
"replace": "src/environments/environment.ts", "replace": "src/environments/environment.ts",
@@ -57,91 +67,41 @@
} }
] ]
} }
} },
"defaultConfiguration": "production"
}, },
"serve": { "serve": {
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"options": {
"buildTarget": "skydivelogs-app:build"
},
"configurations": { "configurations": {
"production": { "production": {
"buildTarget": "skydivelogs-app:build:production" "buildTarget": "skydivelogs-app:build:production"
},
"development": {
"buildTarget": "skydivelogs-app:build:development"
} }
} },
"defaultConfiguration": "development"
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n", "builder": "@angular-devkit/build-angular:extract-i18n"
"options": {
"buildTarget": "skydivelogs-app:build"
}
}, },
"test": { "test": {
"builder": "@angular-devkit/build-angular:karma", "builder": "@angular-devkit/build-angular:karma",
"options": { "options": {
"main": "src/test.ts", "polyfills": ["zone.js", "zone.js/testing"],
"karmaConfig": "./karma.conf.js", "tsConfig": "tsconfig.spec.json",
"polyfills": "src/polyfills.ts", "inlineStyleLanguage": "scss",
"tsConfig": "src/tsconfig.spec.json",
"scripts": [],
"styles": [
"src/styles.css"
],
"assets": [ "assets": [
"src/assets", {
"src/favicon.ico" "glob": "**/*",
] "input": "public"
} }
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
], ],
"exclude": [ "styles": ["src/styles.scss"],
"**/node_modules/**" "scripts": []
]
}
}
}
},
"skydivelogs-app-e2e": {
"root": "e2e",
"sourceRoot": "e2e",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "./protractor.conf.js",
"devServerTarget": "skydivelogs-app:serve"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"e2e/tsconfig.e2e.json"
],
"exclude": [
"**/node_modules/**"
]
} }
} }
} }
} }
},
"schematics": {
"@schematics/angular:component": {
"prefix": "app"
},
"@schematics/angular:directive": {
"prefix": "app"
}
},
"cli": {
"analytics": false
} }
} }

View File

@@ -22,7 +22,7 @@
"@ngx-translate/core": "^15.0.0", "@ngx-translate/core": "^15.0.0",
"@ngx-translate/http-loader": "^8.0.0", "@ngx-translate/http-loader": "^8.0.0",
"chart.js": "^4.3.0", "chart.js": "^4.3.0",
"ng2-charts": "^5.0.2", "ng2-charts": "^8.0.0",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.15.0" "zone.js": "~0.15.0"
@@ -10714,19 +10714,19 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/ng2-charts": { "node_modules/ng2-charts": {
"version": "5.0.4", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-5.0.4.tgz", "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-8.0.0.tgz",
"integrity": "sha512-AnOZ2KSRw7QjiMMNtXz9tdnO+XrIKP/2MX1TfqEEo2fwFU5c8LFJIYqmkMPkIzAEm/U9y/1psA5TDNmxxjEdgA==", "integrity": "sha512-nofsNHI2Zt+EAwT+BJBVg0kgOhNo9ukO4CxULlaIi7VwZSr7I1km38kWSoU41Oq6os6qqIh5srnL+CcV+RFPFA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lodash-es": "^4.17.15", "lodash-es": "^4.17.15",
"tslib": "^2.3.0" "tslib": "^2.3.0"
}, },
"peerDependencies": { "peerDependencies": {
"@angular/cdk": ">=16.0.0", "@angular/cdk": ">=19.0.0",
"@angular/common": ">=16.0.0", "@angular/common": ">=19.0.0",
"@angular/core": ">=16.0.0", "@angular/core": ">=19.0.0",
"@angular/platform-browser": ">=16.0.0", "@angular/platform-browser": ">=19.0.0",
"chart.js": "^3.4.0 || ^4.0.0", "chart.js": "^3.4.0 || ^4.0.0",
"rxjs": "^6.5.3 || ^7.4.0" "rxjs": "^6.5.3 || ^7.4.0"
} }

View File

@@ -25,7 +25,7 @@
"@ngx-translate/core": "^15.0.0", "@ngx-translate/core": "^15.0.0",
"@ngx-translate/http-loader": "^8.0.0", "@ngx-translate/http-loader": "^8.0.0",
"chart.js": "^4.3.0", "chart.js": "^4.3.0",
"ng2-charts": "^5.0.2", "ng2-charts": "^8.0.0",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.15.0" "zone.js": "~0.15.0"

View File

@@ -72,7 +72,7 @@ import { MatRadioModule } from "@angular/material/radio";
import { MatSidenavModule } from "@angular/material/sidenav"; import { MatSidenavModule } from "@angular/material/sidenav";
import { MatListModule } from "@angular/material/list"; import { MatListModule } from "@angular/material/list";
import { MatToolbarModule } from "@angular/material/toolbar"; import { MatToolbarModule } from "@angular/material/toolbar";
import { NgChartsModule } from "ng2-charts"; import { provideCharts, withDefaultRegisterables } from "ng2-charts";
import { JwtAuthInterceptor } from "../interceptor/jwt-auth.interceptor"; import { JwtAuthInterceptor } from "../interceptor/jwt-auth.interceptor";
import { ErrorInterceptor } from "../interceptor/error.interceptor"; import { ErrorInterceptor } from "../interceptor/error.interceptor";
@@ -209,7 +209,6 @@ export function initConfig(configService: ConfigurationHelper) {
MatSidenavModule, MatSidenavModule,
MatListModule, MatListModule,
MatToolbarModule, MatToolbarModule,
NgChartsModule,
], ],
exports: [HttpClientModule], exports: [HttpClientModule],
providers: [ providers: [
@@ -229,11 +228,12 @@ export function initConfig(configService: ConfigurationHelper) {
DatePipe, DatePipe,
ServiceCacheApi, ServiceCacheApi,
provideAppInitializer(() => { provideAppInitializer(() => {
const initializerFn = (initConfig)(inject(ConfigurationHelper)); const initializerFn = initConfig(inject(ConfigurationHelper));
return initializerFn(); return initializerFn();
}), }),
{ provide: HTTP_INTERCEPTORS, useClass: JwtAuthInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: JwtAuthInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
provideCharts(withDefaultRegisterables()),
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })

View File

@@ -1,72 +1,102 @@
<form [formGroup]="createForm" (ngSubmit)="onCreateSubmit()" autocomplete="off" style="padding: 10px;"> <form
[formGroup]="createForm"
(ngSubmit)="onCreateSubmit()"
autocomplete="off"
style="padding: 10px"
>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'LoginCreateUser_Firstname' | translate }}</mat-label> <mat-label>{{ "LoginCreateUser_Firstname" | translate }}</mat-label>
<input matInput type="text" formControlName="firstname" <input
[ngClass]="{ 'is-invalid': submitted && formCtrls.firstname.errors }" tabindex="0" /> matInput
<mat-error *ngIf="formCtrls.firstname.hasError('required')"> type="text"
{{ 'LoginCreateUser_FirstnameRequired' | translate }} formControlName="firstname"
[ngClass]="{ 'is-invalid': submitted && formCtrls['firstname'].errors }"
tabindex="0"
/>
<mat-error *ngIf="formCtrls['firstname'].hasError('required')">
{{ "LoginCreateUser_FirstnameRequired" | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="formCtrls.firstname.hasError('minlength')"> <mat-error *ngIf="formCtrls['firstname'].hasError('minlength')">
{{ 'LoginCreateUser_FirstnamePattern' | translate }} {{ "LoginCreateUser_FirstnamePattern" | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'LoginCreateUser_Lastname' | translate }}</mat-label> <mat-label>{{ "LoginCreateUser_Lastname" | translate }}</mat-label>
<input matInput type="text" formControlName="lastname" <input
[ngClass]="{ 'is-invalid': submitted && formCtrls.lastname.errors }" tabindex="1" /> matInput
<mat-error *ngIf="formCtrls.lastname.hasError('required')"> type="text"
{{ 'LoginCreateUser_LastnameRequired' | translate }} formControlName="lastname"
[ngClass]="{ 'is-invalid': submitted && formCtrls['lastname'].errors }"
tabindex="1"
/>
<mat-error *ngIf="formCtrls['lastname'].hasError('required')">
{{ "LoginCreateUser_LastnameRequired" | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="formCtrls.lastname.hasError('minlength')"> <mat-error *ngIf="formCtrls['lastname'].hasError('minlength')">
{{ 'LoginCreateUser_LastnamePattern' | translate }} {{ "LoginCreateUser_LastnamePattern" | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'LoginCreateUser_Email' | translate }}</mat-label> <mat-label>{{ "LoginCreateUser_Email" | translate }}</mat-label>
<input matInput type="email" formControlName="email" <input
[ngClass]="{ 'is-invalid': submitted && formCtrls.email.errors }" tabindex="3" /> matInput
<mat-error *ngIf="formCtrls.email.hasError('required')"> type="email"
{{ 'LoginCreateUser_EmailRequired' | translate }} formControlName="email"
[ngClass]="{ 'is-invalid': submitted && formCtrls['email'].errors }"
tabindex="3"
/>
<mat-error *ngIf="formCtrls['email'].hasError('required')">
{{ "LoginCreateUser_EmailRequired" | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="formCtrls.email.hasError('email')"> <mat-error *ngIf="formCtrls['email'].hasError('email')">
{{ 'LoginCreateUser_EmailPattern' | translate }} {{ "LoginCreateUser_EmailPattern" | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'LoginCreateUser_Username' | translate }}</mat-label> <mat-label>{{ "LoginCreateUser_Username" | translate }}</mat-label>
<input matInput type="text" formControlName="username" <input
[ngClass]="{ 'is-invalid': submitted && formCtrls.username.errors }" tabindex="4" /> matInput
<mat-error *ngIf="formCtrls.username.hasError('required')"> type="text"
{{ 'LoginCreateUser_UsernameRequired' | translate }} formControlName="username"
[ngClass]="{ 'is-invalid': submitted && formCtrls['username'].errors }"
tabindex="4"
/>
<mat-error *ngIf="formCtrls['username'].hasError('required')">
{{ "LoginCreateUser_UsernameRequired" | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="formCtrls.username.hasError('minlength')"> <mat-error *ngIf="formCtrls['username'].hasError('minlength')">
{{ 'LoginCreateUser_UsernamePattern' | translate }} {{ "LoginCreateUser_UsernamePattern" | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'LoginCreateUser_Password' | translate }}</mat-label> <mat-label>{{ "LoginCreateUser_Password" | translate }}</mat-label>
<input matInput type="password" formControlName="password" <input
[ngClass]="{ 'is-invalid': submitted && formCtrls.password.errors }" tabindex="5" /> matInput
<mat-error *ngIf="formCtrls.password.hasError('required')"> type="password"
{{ 'LoginCreateUser_PasswordRequired' | translate }} formControlName="password"
[ngClass]="{ 'is-invalid': submitted && formCtrls['password'].errors }"
tabindex="5"
/>
<mat-error *ngIf="formCtrls['password'].hasError('required')">
{{ "LoginCreateUser_PasswordRequired" | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="formCtrls.password.hasError('pattern')"> <mat-error *ngIf="formCtrls['password'].hasError('pattern')">
{{ 'LoginCreateUser_PasswordPattern' | translate }} {{ "LoginCreateUser_PasswordPattern" | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</p> </p>
<button [disabled]="!createForm.valid" mat-raised-button color="accent"> <button [disabled]="!createForm.valid" mat-raised-button color="accent">
{{ 'LoginCreateUser_BtnLogin' | translate }} {{ "LoginCreateUser_BtnLogin" | translate }}
</button> </button>
<div *ngIf="error" class="alert alert-danger mt-3 mb-0">{{error}}</div> <div *ngIf="error" class="alert alert-danger mt-3 mb-0">{{ error }}</div>
</form> </form>

View File

@@ -9,10 +9,10 @@ import { AuthenticationService } from "../../services/authentication.service";
import { User } from "../../models/user"; import { User } from "../../models/user";
@Component({ @Component({
selector: "app-create-user", selector: "app-create-user",
templateUrl: "./create-user.component.html", templateUrl: "./create-user.component.html",
styleUrls: ["./create-user.component.css"], styleUrls: ["./create-user.component.css"],
standalone: false standalone: false,
}) })
export class CreateUserComponent implements OnInit { export class CreateUserComponent implements OnInit {
createForm: FormGroup; createForm: FormGroup;
@@ -21,11 +21,13 @@ export class CreateUserComponent implements OnInit {
returnUrl: string; returnUrl: string;
error = ""; error = "";
constructor(private formBuilder: FormBuilder, constructor(
private route: ActivatedRoute, private formBuilder: FormBuilder,
private router: Router, private route: ActivatedRoute,
private authenticationService: AuthenticationService, private router: Router,
private translateService: TranslateService) { private authenticationService: AuthenticationService,
private translateService: TranslateService
) {
// redirect to home if already logged in // redirect to home if already logged in
if (this.authenticationService.currentUserValue) { if (this.authenticationService.currentUserValue) {
this.router.navigate(["/"]); this.router.navigate(["/"]);
@@ -38,11 +40,16 @@ export class CreateUserComponent implements OnInit {
username: ["", [Validators.required, Validators.minLength(3)]], username: ["", [Validators.required, Validators.minLength(3)]],
password: [ password: [
"", "",
[Validators.required, Validators.pattern("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[@$!%*#?&\-_|]).{8,}$")] [
Validators.required,
Validators.pattern(
"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[@$!%*#?&-_|]).{8,}$"
),
],
], ],
firstname: ["", [Validators.required, Validators.minLength(3)]], firstname: ["", [Validators.required, Validators.minLength(3)]],
lastname: ["", [Validators.required, Validators.minLength(3)]], lastname: ["", [Validators.required, Validators.minLength(3)]],
email: ["", [Validators.required, Validators.email]] email: ["", [Validators.required, Validators.email]],
}, },
{ updateOn: "blur" } { updateOn: "blur" }
); );
@@ -65,23 +72,24 @@ export class CreateUserComponent implements OnInit {
} }
let createUser = new User(); let createUser = new User();
createUser.login = this.formCtrls.username.value; createUser.login = this.formCtrls["username"].value;
createUser.password = this.formCtrls.password.value; createUser.password = this.formCtrls["password"].value;
createUser.firstName = this.formCtrls.firstname.value; createUser.firstName = this.formCtrls["firstname"].value;
createUser.lastName = this.formCtrls.lastname.value; createUser.lastName = this.formCtrls["lastname"].value;
createUser.email = this.formCtrls.email.value; createUser.email = this.formCtrls["email"].value;
createUser.language = this.translateService.currentLang; createUser.language = this.translateService.currentLang;
this.authenticationService.create(createUser) this.authenticationService
.pipe(first()) .create(createUser)
.subscribe( .pipe(first())
data => { .subscribe(
this.router.navigate([this.returnUrl]); (data) => {
}, this.router.navigate([this.returnUrl]);
error => { },
this.error = error; (error) => {
this.invalidForm = false; this.error = error;
} this.invalidForm = false;
); }
);
} }
} }

View File

@@ -42,7 +42,6 @@
baseChart baseChart
[data]="barChartData" [data]="barChartData"
[options]="barChartOptions" [options]="barChartOptions"
[plugins]="barChartPlugins"
[legend]="barChartLegend" [legend]="barChartLegend"
[type]="barChartType" [type]="barChartType"
> >

View File

@@ -1,233 +1,253 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from "@angular/core";
import { formatDate } from '@angular/common'; import { formatDate } from "@angular/common";
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from "@ngx-translate/core";
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from "@angular/material/table";
import { ChartConfiguration, ChartData, ChartType } from 'chart.js'; import { ChartConfiguration, ChartData, ChartType } from "chart.js";
import { from, of, groupBy, mergeMap, reduce, map, Observable } from 'rxjs'; import { from, of, groupBy, mergeMap, reduce, map, Observable } from "rxjs";
import { ServiceComm } from '../../services/service-comm.service'; import { ServiceComm } from "../../services/service-comm.service";
import { TunnelFlightService } from "../../services/tunnel-flight.service"; import { TunnelFlightService } from "../../services/tunnel-flight.service";
import { DateService } from '../../services/date.service'; import { DateService } from "../../services/date.service";
import { TunnelFlight, TunnelFlightByMonth } from '../../models/tunnel-flight'; import { TunnelFlight, TunnelFlightByMonth } from "../../models/tunnel-flight";
@Component({ @Component({
selector: 'app-list-of-tunnel-flights', selector: "app-list-of-tunnel-flights",
templateUrl: './list-of-tunnel-flights.component.html', templateUrl: "./list-of-tunnel-flights.component.html",
styleUrls: ['./list-of-tunnel-flights.component.css'], styleUrls: ["./list-of-tunnel-flights.component.css"],
standalone: false standalone: false,
}) })
export class ListOfTunnelFlightsComponent implements OnInit { export class ListOfTunnelFlightsComponent implements OnInit {
public barChartLegend = true; public barChartLegend = true;
public barChartPlugins = []; public barChartData: ChartData<"bar">;
public barChartData: ChartData<'bar'>; public barChartOptions: ChartConfiguration["options"];
public barChartOptions: ChartConfiguration['options']; public barChartType: ChartType;
public barChartType: ChartType; public isLoading: boolean = false;
public isLoading: boolean = false; public selectedPeriod: string;
public selectedPeriod: string; public dataSourceTable: MatTableDataSource<TunnelFlight> =
public dataSourceTable: MatTableDataSource<TunnelFlight> = new MatTableDataSource(); new MatTableDataSource();
public displayedColumns: Array<string> = [ public displayedColumns: Array<string> = [
"id", "id",
"tunnel", "tunnel",
"jumpType", "jumpType",
"nbMinutes", "nbMinutes",
"notes", "notes",
"flightDate", "flightDate",
"actions" "actions",
]; ];
public stats: Array<{id: String|Number, values: String|Number}> = []; public stats: Array<{ id: String | Number; values: String | Number }> = [];
constructor(private serviceComm: ServiceComm, constructor(
private serviceTunnelFlight: TunnelFlightService, private serviceComm: ServiceComm,
private translateService: TranslateService, private serviceTunnelFlight: TunnelFlightService,
private dateService: DateService) { } private translateService: TranslateService,
private dateService: DateService
) {}
ngOnInit() { ngOnInit() {
this.serviceComm.forceTranslateTitle.subscribe((data) => { this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) { if (data === true) {
this.updateTitle();
}
});
this.updateTitle(); this.updateTitle();
}
});
this.updateTitle();
this.chartConfig(); this.chartConfig();
this.selectedPeriod = "currentYear" this.selectedPeriod = "currentYear";
this.getDataForGraph(); this.getDataForGraph();
} }
public onPeriodChange() { public onPeriodChange() {
this.getDataForGraph(); this.getDataForGraph();
if (this.dataSourceTable?.data.length > 0){ if (this.dataSourceTable?.data.length > 0) {
this.getDataForTable(); this.getDataForTable();
}
} }
}
public onLoadTable() { public onLoadTable() {
this.getDataForTable(); this.getDataForTable();
} }
private chartConfig() { private chartConfig() {
this.barChartType = "bar"; this.barChartType = "bar";
this.barChartOptions = { this.barChartOptions = {
responsive: true, responsive: true,
maintainAspectRatio: true, maintainAspectRatio: true,
plugins: { plugins: {
legend: { legend: {
display: true display: true,
}, },
tooltip: { tooltip: {
callbacks: { callbacks: {
footer: this.footer, footer: this.footer,
} },
} },
}, },
interaction: { interaction: {
intersect: false, intersect: false,
mode: 'nearest', mode: "nearest",
axis: 'x' axis: "x",
}, },
scales: { scales: {
x: { x: {
stacked: true stacked: true,
}, },
y: { y: {
stacked: true stacked: true,
} },
} },
};
}
private updateTitle() {
this.translateService.get("ListTunnelFlight_Title").subscribe(
data => { this.serviceComm.updatedComponentTitle(data); }
);
}
private getDataForTable(): void {
this.isLoading = true;
// Get data to show in a table
let endDate = new Date();
endDate.setHours(0, 0, 0, 0);
let beginDate = this.computeBeginDateByPeriod(this.selectedPeriod, endDate);
this.serviceTunnelFlight.getTunnelFlights(beginDate, endDate)
.subscribe((data) => {
this.dataSourceTable.data = data;
this.isLoading = false;
});
}
private getDataForGraph(): void {
this.isLoading = true;
// Get data to show in a table
let endDate = new Date();
endDate.setHours(0, 0, 0, 0);
let beginDate = this.computeBeginDateByPeriod(this.selectedPeriod, endDate);
this.serviceTunnelFlight.getTunnelFlightsByMonth(beginDate, endDate)
.subscribe((data) => {
const allMonths = this.getMontsBetweenDates(beginDate, endDate)
const cumulatedTime = this.getCumulatedTimeByTypeByMonth(data, allMonths);
this.computeTimeByType(data);
this.barChartData = {
labels: allMonths,
datasets: cumulatedTime
};
this.isLoading = false;
});
}
private computeTimeByType(stats: TunnelFlightByMonth[]) {
this.stats = [];
from(stats).pipe(
groupBy((type) => type.type, { element: (p) => p.nb }),
mergeMap((group$) =>
group$.pipe(reduce((acc, cur) => [...acc, cur], [`${group$.key}`]))
),
map((arr) => ({ id: arr[0], values: arr.slice(1).reduce((a, b) => Number(a) + Number(b), 0) }))
)
.subscribe((p) => {
console.log(p);
this.stats.push(p);
});
}
private getMontsBetweenDates(beginDate: Date, endDate: Date): Array<string> {
let results: Array<string> = [];
let tmpBeginDate = new Date(beginDate.getTime());
const tmpEndDate = new Date(endDate.getTime());
while (tmpBeginDate < tmpEndDate) {
results.push(formatDate(tmpBeginDate, "yy-MM", "en"));
tmpBeginDate.setMonth(tmpBeginDate.getMonth() + 1);
}
return results;
}
private getCumulatedTimeByTypeByMonth(stats: TunnelFlightByMonth[], allMonths: string[]): Array<any> {
let tmpResults = new Map<string, number[]>();
const disctintType = Array.from(new Set(stats.map((item) => item.type)));
disctintType.forEach(type => {
tmpResults.set(type, Array.from({length: allMonths.length}, (v, k) => 0));
});
for (let i = 0; i < allMonths.length; i++) {
const month = allMonths[i];
let filteredStats = stats.filter((d) => d.month == month);
if (filteredStats.length > 0){
filteredStats.forEach(fs => {
tmpResults.get(fs.type)[i] += fs.nb;
});
}
}
const results = Array.from(tmpResults, function (item) {
return { label: item[0], data: item[1] }
});
return results;
}
private footer = (tooltipItems) => {
let sum = 0;
tooltipItems.forEach(function (tooltipItem) {
sum += tooltipItem.parsed.y;
});
return 'Sum: ' + sum;
}; };
}
private computeBeginDateByPeriod(selectedPeriod: String, endDate: Date): Date { private updateTitle() {
let beginDate = new Date(); this.translateService.get("ListTunnelFlight_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
switch (selectedPeriod) { });
case "currentYear": }
beginDate = new Date(endDate.getFullYear(), 0, 1);
break;
case "12Months":
beginDate = this.dateService.addMonths(endDate, -12);
beginDate.setDate(1);
break;
case "all":
beginDate = this.dateService.addMonths(endDate, -120);
beginDate.setDate(1);
break;
}
return beginDate; private getDataForTable(): void {
} this.isLoading = true;
public delete(item: TunnelFlight) { // Get data to show in a table
let data: Array<TunnelFlight> = this.dataSourceTable.data; let endDate = new Date();
data = data.filter((d) => d.id !== item.id); endDate.setHours(0, 0, 0, 0);
let beginDate = this.computeBeginDateByPeriod(this.selectedPeriod, endDate);
this.serviceTunnelFlight
.getTunnelFlights(beginDate, endDate)
.subscribe((data) => {
this.dataSourceTable.data = data; this.dataSourceTable.data = data;
this.serviceTunnelFlight.deleteTunnelFlight(item); this.isLoading = false;
});
}
private getDataForGraph(): void {
this.isLoading = true;
// Get data to show in a table
let endDate = new Date();
endDate.setHours(0, 0, 0, 0);
let beginDate = this.computeBeginDateByPeriod(this.selectedPeriod, endDate);
this.serviceTunnelFlight
.getTunnelFlightsByMonth(beginDate, endDate)
.subscribe((data) => {
const allMonths = this.getMontsBetweenDates(beginDate, endDate);
const cumulatedTime = this.getCumulatedTimeByTypeByMonth(
data,
allMonths
);
this.computeTimeByType(data);
this.barChartData = {
labels: allMonths,
datasets: cumulatedTime,
};
this.isLoading = false;
});
}
private computeTimeByType(stats: TunnelFlightByMonth[]) {
this.stats = [];
from(stats)
.pipe(
groupBy((type) => type.type, { element: (p) => p.nb }),
mergeMap((group$) =>
group$.pipe(reduce((acc, cur) => [...acc, cur], [`${group$.key}`]))
),
map((arr) => ({
id: arr[0],
values: arr.slice(1).reduce((a, b) => Number(a) + Number(b), 0),
}))
)
.subscribe((p) => {
console.log(p);
this.stats.push(p);
});
}
private getMontsBetweenDates(beginDate: Date, endDate: Date): Array<string> {
let results: Array<string> = [];
let tmpBeginDate = new Date(beginDate.getTime());
const tmpEndDate = new Date(endDate.getTime());
while (tmpBeginDate < tmpEndDate) {
results.push(formatDate(tmpBeginDate, "yy-MM", "en"));
tmpBeginDate.setMonth(tmpBeginDate.getMonth() + 1);
} }
return results;
}
private getCumulatedTimeByTypeByMonth(
stats: TunnelFlightByMonth[],
allMonths: string[]
): Array<any> {
let tmpResults = new Map<string, number[]>();
const disctintType = Array.from(new Set(stats.map((item) => item.type)));
disctintType.forEach((type) => {
tmpResults.set(
type,
Array.from({ length: allMonths.length }, (v, k) => 0)
);
});
for (let i = 0; i < allMonths.length; i++) {
const month = allMonths[i];
let filteredStats = stats.filter((d) => d.month == month);
if (filteredStats.length > 0) {
filteredStats.forEach((fs) => {
tmpResults.get(fs.type)[i] += fs.nb;
});
}
}
const results = Array.from(tmpResults, function (item) {
return { label: item[0], data: item[1] };
});
return results;
}
private footer = (tooltipItems: any) => {
let sum = 0;
tooltipItems.forEach(function (tooltipItem: any) {
sum += tooltipItem.parsed.y;
});
return "Sum: " + sum;
};
private computeBeginDateByPeriod(
selectedPeriod: String,
endDate: Date
): Date {
let beginDate = new Date();
switch (selectedPeriod) {
case "currentYear":
beginDate = new Date(endDate.getFullYear(), 0, 1);
break;
case "12Months":
beginDate = this.dateService.addMonths(endDate, -12);
beginDate.setDate(1);
break;
case "all":
beginDate = this.dateService.addMonths(endDate, -120);
beginDate.setDate(1);
break;
}
return beginDate;
}
public delete(item: TunnelFlight) {
let data: Array<TunnelFlight> = this.dataSourceTable.data;
data = data.filter((d) => d.id !== item.id);
this.dataSourceTable.data = data;
this.serviceTunnelFlight.deleteTunnelFlight(item);
}
} }

View File

@@ -1,36 +1,50 @@
<form focusInvalidInput autocomplete="off" style="padding: 10px;" <form
[formGroup]="loginForm" (ngSubmit)="onLoginSubmit()"> focusInvalidInput
autocomplete="off"
style="padding: 10px"
[formGroup]="loginForm"
(ngSubmit)="onLoginSubmit()"
>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'LoginUser_Username' | translate }}</mat-label> <mat-label>{{ "LoginUser_Username" | translate }}</mat-label>
<input type="text" matInput #username="matInput" formControlName="username" <input
[ngClass]="{ 'is-invalid': submitted && formCtrls.username.errors }"> type="text"
<mat-error *ngIf="formCtrls.username.hasError('required')"> matInput
{{ 'LoginUser_UsernameRequired' | translate }} #username="matInput"
formControlName="username"
[ngClass]="{ 'is-invalid': submitted && formCtrls['username'].errors }"
/>
<mat-error *ngIf="formCtrls['username'].hasError('required')">
{{ "LoginUser_UsernameRequired" | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="formCtrls.username.hasError('minlength')"> <mat-error *ngIf="formCtrls['username'].hasError('minlength')">
{{ 'LoginUser_UsernamePattern | translate }} {{ 'LoginUser_UsernamePattern | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>{{ 'LoginUser_Password' | translate }}</mat-label> <mat-label>{{ "LoginUser_Password" | translate }}</mat-label>
<input type="password" matInput formControlName="password" <input
[ngClass]="{ 'is-invalid': submitted && formCtrls.password.errors }"> type="password"
<mat-error *ngIf="formCtrls.password.hasError('required')"> matInput
{{ 'LoginUser_PasswordRequired' | translate }} formControlName="password"
[ngClass]="{ 'is-invalid': submitted && formCtrls['password'].errors }"
/>
<mat-error *ngIf="formCtrls['password'].hasError('required')">
{{ "LoginUser_PasswordRequired" | translate }}
</mat-error> </mat-error>
<mat-error *ngIf="formCtrls.password.hasError('pattern')"> <mat-error *ngIf="formCtrls['password'].hasError('pattern')">
{{ 'LoginUser_PasswordPattern' | translate }} {{ "LoginUser_PasswordPattern" | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</p> </p>
<button [disabled]="loading" mat-raised-button color="accent"> <button [disabled]="loading" mat-raised-button color="accent">
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span> <span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
{{ 'LoginUser_BtnLogin' | translate }} {{ "LoginUser_BtnLogin" | translate }}
</button> </button>
<div *ngIf="error" class="alert alert-danger mt-3 mb-0">{{error}}</div> <div *ngIf="error" class="alert alert-danger mt-3 mb-0">{{ error }}</div>
</form> </form>

View File

@@ -1,32 +1,34 @@
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core'; import { Component, OnInit, ViewChild, AfterViewInit } from "@angular/core";
import { Router, ActivatedRoute } from '@angular/router'; import { Router, ActivatedRoute } from "@angular/router";
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { MatInput } from '@angular/material/input'; import { MatInput } from "@angular/material/input";
import { first } from 'rxjs/operators'; import { first } from "rxjs/operators";
import { AuthenticationService } from '../../services/authentication.service'; import { AuthenticationService } from "../../services/authentication.service";
@Component({ @Component({
selector: 'app-login-user', selector: "app-login-user",
templateUrl: './login-user.component.html', templateUrl: "./login-user.component.html",
styleUrls: ['./login-user.component.css'], styleUrls: ["./login-user.component.css"],
standalone: false standalone: false,
}) })
export class LoginUserComponent implements OnInit, AfterViewInit { export class LoginUserComponent implements OnInit, AfterViewInit {
loginForm: FormGroup; loginForm: FormGroup;
loading = false; loading = false;
submitted = false; submitted = false;
returnUrl: string; returnUrl: string;
error = ''; error = "";
@ViewChild('username') userNameInput: MatInput; @ViewChild("username") userNameInput: MatInput;
constructor(private formBuilder: FormBuilder, constructor(
private route: ActivatedRoute, private formBuilder: FormBuilder,
private router: Router, private route: ActivatedRoute,
private authenticationService: AuthenticationService) { private router: Router,
private authenticationService: AuthenticationService
) {
if (this.authenticationService.currentUserValue) { if (this.authenticationService.currentUserValue) {
this.router.navigate(['/']); this.router.navigate(["/"]);
} }
} }
@@ -37,15 +39,14 @@ export class LoginUserComponent implements OnInit, AfterViewInit {
ngOnInit() { ngOnInit() {
this.loginForm = this.formBuilder.group( this.loginForm = this.formBuilder.group(
{ {
username: ['', [Validators.required, Validators.minLength(3)]], username: ["", [Validators.required, Validators.minLength(3)]],
password: ['', [Validators.required]] password: ["", [Validators.required]],
}, },
{ updateOn: 'submit' } { updateOn: "submit" }
); );
// get return url from route parameters or default to '/' // get return url from route parameters or default to '/'
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; this.returnUrl = this.route.snapshot.queryParams["returnUrl"] || "/";
} }
get formCtrls() { get formCtrls() {
@@ -57,17 +58,21 @@ export class LoginUserComponent implements OnInit, AfterViewInit {
if (this.loginForm.valid) { if (this.loginForm.valid) {
this.loading = true; this.loading = true;
this.authenticationService.login(this.formCtrls.username.value, this.formCtrls.password.value) this.authenticationService
.pipe(first()) .login(
.subscribe( this.formCtrls["username"].value,
data => { this.formCtrls["password"].value
this.router.navigate([this.returnUrl]); )
}, .pipe(first())
error => { .subscribe(
this.error = error; (data) => {
this.loading = false; this.router.navigate([this.returnUrl]);
} },
); (error) => {
this.error = error;
this.loading = false;
}
);
} }
} }
} }

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from "@angular/core";
import { HttpRequest, HttpResponse } from '@angular/common/http'; import { HttpRequest, HttpResponse } from "@angular/common/http";
@Injectable() @Injectable()
export class RequestCache { export class RequestCache {
@@ -10,8 +10,8 @@ export class RequestCache {
} }
get(req: HttpRequest<any>): HttpResponse<any> | undefined { get(req: HttpRequest<any>): HttpResponse<any> | undefined {
const splittedUrl = req.urlWithParams.split('/'); const splittedUrl = req.urlWithParams.split("/");
const url = splittedUrl[splittedUrl.length - 1] + '_' + req.method; const url = splittedUrl[splittedUrl.length - 1] + "_" + req.method;
this.clearExpiredEntries(); this.clearExpiredEntries();
const cached = this.cache.get(url); const cached = this.cache.get(url);
@@ -24,8 +24,8 @@ export class RequestCache {
} }
put(req: HttpRequest<any>, response: HttpResponse<any>): void { put(req: HttpRequest<any>, response: HttpResponse<any>): void {
const splittedUrl = req.urlWithParams.split('/'); const splittedUrl = req.urlWithParams.split("/");
const url = splittedUrl[splittedUrl.length - 1] + '_' + req.method; const url = splittedUrl[splittedUrl.length - 1] + "_" + req.method;
const entry = { url, response, lastRead: Date.now() }; const entry = { url, response, lastRead: Date.now() };
this.cache.set(url, entry); this.cache.set(url, entry);
@@ -36,7 +36,7 @@ export class RequestCache {
const maxAge = 30000; const maxAge = 30000;
const expired = Date.now() - maxAge; const expired = Date.now() - maxAge;
this.cache.forEach(expiredEntry => { this.cache.forEach((expiredEntry) => {
if (expiredEntry.lastRead < expired) { if (expiredEntry.lastRead < expired) {
this.cache.delete(expiredEntry.url); this.cache.delete(expiredEntry.url);
} }

View File

@@ -1,12 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
"types": []
},
"files": [
"main.ts",
"polyfills.ts"
]
}

View File

@@ -1,19 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"baseUrl": "./",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts",
"polyfills.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts"
],
"include": [
"src/**/*.d.ts"
]
}

View File

@@ -1,25 +1,29 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{ {
"compileOnSave": false, "compileOnSave": false,
"angularCompilerOptions": {
"strictTemplates": true,
},
"compilerOptions": { "compilerOptions": {
"importHelpers": true,
"outDir": "./dist/out-tsc", "outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"esModuleInterop": true, "esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"experimentalDecorators": true, "experimentalDecorators": true,
"target": "es2015", "moduleResolution": "bundler",
"typeRoots": [ "importHelpers": true,
"node_modules/@types" "target": "ES2022",
], "module": "ES2022",
"lib": [ "strictPropertyInitialization": false,
"es2017", "strictNullChecks": false
"dom" },
], "angularCompilerOptions": {
"module": "esnext", "enableI18nLegacyMessageIdFormat": false,
"baseUrl": "./" "strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
} }
} }

View File

@@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}