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",
"projects": {
"skydivelogs-app": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": {
"base": "dist"
},
"outputPath": "dist",
"index": "src/index.html",
"tsConfig": "src/tsconfig.app.json",
"polyfills": [
"src/polyfills.ts"
],
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/assets",
"src/config",
"src/favicon.ico"
"src/favicon.ico",
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/assets/css/styles-app-loading.scss",
@@ -30,26 +38,28 @@
"src/assets/css/new-theme.scss",
"@angular/material/prebuilt-themes/pink-bluegrey.css"
],
"scripts": [],
"extractLicenses": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true,
"browser": "src/main.ts"
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
@@ -57,91 +67,41 @@
}
]
}
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"buildTarget": "skydivelogs-app:build"
},
"configurations": {
"production": {
"buildTarget": "skydivelogs-app:build:production"
},
"development": {
"buildTarget": "skydivelogs-app:build:development"
}
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "skydivelogs-app:build"
}
"builder": "@angular-devkit/build-angular:extract-i18n"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"karmaConfig": "./karma.conf.js",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"scripts": [],
"styles": [
"src/styles.css"
],
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/assets",
"src/favicon.ico"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
{
"glob": "**/*",
"input": "public"
}
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"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/**"
]
"styles": ["src/styles.scss"],
"scripts": []
}
}
}
}
},
"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/http-loader": "^8.0.0",
"chart.js": "^4.3.0",
"ng2-charts": "^5.0.2",
"ng2-charts": "^8.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
@@ -10714,19 +10714,19 @@
"license": "MIT"
},
"node_modules/ng2-charts": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-5.0.4.tgz",
"integrity": "sha512-AnOZ2KSRw7QjiMMNtXz9tdnO+XrIKP/2MX1TfqEEo2fwFU5c8LFJIYqmkMPkIzAEm/U9y/1psA5TDNmxxjEdgA==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-8.0.0.tgz",
"integrity": "sha512-nofsNHI2Zt+EAwT+BJBVg0kgOhNo9ukO4CxULlaIi7VwZSr7I1km38kWSoU41Oq6os6qqIh5srnL+CcV+RFPFA==",
"license": "MIT",
"dependencies": {
"lodash-es": "^4.17.15",
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/cdk": ">=16.0.0",
"@angular/common": ">=16.0.0",
"@angular/core": ">=16.0.0",
"@angular/platform-browser": ">=16.0.0",
"@angular/cdk": ">=19.0.0",
"@angular/common": ">=19.0.0",
"@angular/core": ">=19.0.0",
"@angular/platform-browser": ">=19.0.0",
"chart.js": "^3.4.0 || ^4.0.0",
"rxjs": "^6.5.3 || ^7.4.0"
}

View File

@@ -25,7 +25,7 @@
"@ngx-translate/core": "^15.0.0",
"@ngx-translate/http-loader": "^8.0.0",
"chart.js": "^4.3.0",
"ng2-charts": "^5.0.2",
"ng2-charts": "^8.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.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 { MatListModule } from "@angular/material/list";
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 { ErrorInterceptor } from "../interceptor/error.interceptor";
@@ -209,7 +209,6 @@ export function initConfig(configService: ConfigurationHelper) {
MatSidenavModule,
MatListModule,
MatToolbarModule,
NgChartsModule,
],
exports: [HttpClientModule],
providers: [
@@ -229,11 +228,12 @@ export function initConfig(configService: ConfigurationHelper) {
DatePipe,
ServiceCacheApi,
provideAppInitializer(() => {
const initializerFn = (initConfig)(inject(ConfigurationHelper));
return initializerFn();
}),
const initializerFn = initConfig(inject(ConfigurationHelper));
return initializerFn();
}),
{ provide: HTTP_INTERCEPTORS, useClass: JwtAuthInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
provideCharts(withDefaultRegisterables()),
],
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>
<mat-form-field>
<mat-label>{{ 'LoginCreateUser_Firstname' | translate }}</mat-label>
<input matInput type="text" formControlName="firstname"
[ngClass]="{ 'is-invalid': submitted && formCtrls.firstname.errors }" tabindex="0" />
<mat-error *ngIf="formCtrls.firstname.hasError('required')">
{{ 'LoginCreateUser_FirstnameRequired' | translate }}
<mat-label>{{ "LoginCreateUser_Firstname" | translate }}</mat-label>
<input
matInput
type="text"
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 *ngIf="formCtrls.firstname.hasError('minlength')">
{{ 'LoginCreateUser_FirstnamePattern' | translate }}
<mat-error *ngIf="formCtrls['firstname'].hasError('minlength')">
{{ "LoginCreateUser_FirstnamePattern" | translate }}
</mat-error>
</mat-form-field>
</p>
<p>
<mat-form-field>
<mat-label>{{ 'LoginCreateUser_Lastname' | translate }}</mat-label>
<input matInput type="text" formControlName="lastname"
[ngClass]="{ 'is-invalid': submitted && formCtrls.lastname.errors }" tabindex="1" />
<mat-error *ngIf="formCtrls.lastname.hasError('required')">
{{ 'LoginCreateUser_LastnameRequired' | translate }}
<mat-label>{{ "LoginCreateUser_Lastname" | translate }}</mat-label>
<input
matInput
type="text"
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 *ngIf="formCtrls.lastname.hasError('minlength')">
{{ 'LoginCreateUser_LastnamePattern' | translate }}
<mat-error *ngIf="formCtrls['lastname'].hasError('minlength')">
{{ "LoginCreateUser_LastnamePattern" | translate }}
</mat-error>
</mat-form-field>
</p>
<p>
<mat-form-field>
<mat-label>{{ 'LoginCreateUser_Email' | translate }}</mat-label>
<input matInput type="email" formControlName="email"
[ngClass]="{ 'is-invalid': submitted && formCtrls.email.errors }" tabindex="3" />
<mat-error *ngIf="formCtrls.email.hasError('required')">
{{ 'LoginCreateUser_EmailRequired' | translate }}
<mat-label>{{ "LoginCreateUser_Email" | translate }}</mat-label>
<input
matInput
type="email"
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 *ngIf="formCtrls.email.hasError('email')">
{{ 'LoginCreateUser_EmailPattern' | translate }}
<mat-error *ngIf="formCtrls['email'].hasError('email')">
{{ "LoginCreateUser_EmailPattern" | translate }}
</mat-error>
</mat-form-field>
</p>
<p>
<mat-form-field>
<mat-label>{{ 'LoginCreateUser_Username' | translate }}</mat-label>
<input matInput type="text" formControlName="username"
[ngClass]="{ 'is-invalid': submitted && formCtrls.username.errors }" tabindex="4" />
<mat-error *ngIf="formCtrls.username.hasError('required')">
{{ 'LoginCreateUser_UsernameRequired' | translate }}
<mat-label>{{ "LoginCreateUser_Username" | translate }}</mat-label>
<input
matInput
type="text"
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 *ngIf="formCtrls.username.hasError('minlength')">
{{ 'LoginCreateUser_UsernamePattern' | translate }}
<mat-error *ngIf="formCtrls['username'].hasError('minlength')">
{{ "LoginCreateUser_UsernamePattern" | translate }}
</mat-error>
</mat-form-field>
</p>
<p>
<mat-form-field>
<mat-label>{{ 'LoginCreateUser_Password' | translate }}</mat-label>
<input matInput type="password" formControlName="password"
[ngClass]="{ 'is-invalid': submitted && formCtrls.password.errors }" tabindex="5" />
<mat-error *ngIf="formCtrls.password.hasError('required')">
{{ 'LoginCreateUser_PasswordRequired' | translate }}
<mat-label>{{ "LoginCreateUser_Password" | translate }}</mat-label>
<input
matInput
type="password"
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 *ngIf="formCtrls.password.hasError('pattern')">
{{ 'LoginCreateUser_PasswordPattern' | translate }}
<mat-error *ngIf="formCtrls['password'].hasError('pattern')">
{{ "LoginCreateUser_PasswordPattern" | translate }}
</mat-error>
</mat-form-field>
</p>
<button [disabled]="!createForm.valid" mat-raised-button color="accent">
{{ 'LoginCreateUser_BtnLogin' | translate }}
{{ "LoginCreateUser_BtnLogin" | translate }}
</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>

View File

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

View File

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

View File

@@ -1,233 +1,253 @@
import { Component, OnInit } from '@angular/core';
import { formatDate } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
import { MatTableDataSource } from '@angular/material/table';
import { ChartConfiguration, ChartData, ChartType } from 'chart.js';
import { from, of, groupBy, mergeMap, reduce, map, Observable } from 'rxjs';
import { Component, OnInit } from "@angular/core";
import { formatDate } from "@angular/common";
import { TranslateService } from "@ngx-translate/core";
import { MatTableDataSource } from "@angular/material/table";
import { ChartConfiguration, ChartData, ChartType } from "chart.js";
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 { DateService } from '../../services/date.service';
import { TunnelFlight, TunnelFlightByMonth } from '../../models/tunnel-flight';
import { DateService } from "../../services/date.service";
import { TunnelFlight, TunnelFlightByMonth } from "../../models/tunnel-flight";
@Component({
selector: 'app-list-of-tunnel-flights',
templateUrl: './list-of-tunnel-flights.component.html',
styleUrls: ['./list-of-tunnel-flights.component.css'],
standalone: false
selector: "app-list-of-tunnel-flights",
templateUrl: "./list-of-tunnel-flights.component.html",
styleUrls: ["./list-of-tunnel-flights.component.css"],
standalone: false,
})
export class ListOfTunnelFlightsComponent implements OnInit {
public barChartLegend = true;
public barChartPlugins = [];
public barChartData: ChartData<'bar'>;
public barChartOptions: ChartConfiguration['options'];
public barChartType: ChartType;
public isLoading: boolean = false;
public selectedPeriod: string;
public dataSourceTable: MatTableDataSource<TunnelFlight> = new MatTableDataSource();
public displayedColumns: Array<string> = [
"id",
"tunnel",
"jumpType",
"nbMinutes",
"notes",
"flightDate",
"actions"
];
public stats: Array<{id: String|Number, values: String|Number}> = [];
public barChartLegend = true;
public barChartData: ChartData<"bar">;
public barChartOptions: ChartConfiguration["options"];
public barChartType: ChartType;
public isLoading: boolean = false;
public selectedPeriod: string;
public dataSourceTable: MatTableDataSource<TunnelFlight> =
new MatTableDataSource();
public displayedColumns: Array<string> = [
"id",
"tunnel",
"jumpType",
"nbMinutes",
"notes",
"flightDate",
"actions",
];
public stats: Array<{ id: String | Number; values: String | Number }> = [];
constructor(private serviceComm: ServiceComm,
private serviceTunnelFlight: TunnelFlightService,
private translateService: TranslateService,
private dateService: DateService) { }
constructor(
private serviceComm: ServiceComm,
private serviceTunnelFlight: TunnelFlightService,
private translateService: TranslateService,
private dateService: DateService
) {}
ngOnInit() {
this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) {
this.updateTitle();
}
});
ngOnInit() {
this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) {
this.updateTitle();
}
});
this.updateTitle();
this.chartConfig();
this.selectedPeriod = "currentYear"
this.getDataForGraph();
}
public onPeriodChange() {
this.getDataForGraph();
if (this.dataSourceTable?.data.length > 0){
this.getDataForTable();
}
this.chartConfig();
this.selectedPeriod = "currentYear";
this.getDataForGraph();
}
public onPeriodChange() {
this.getDataForGraph();
if (this.dataSourceTable?.data.length > 0) {
this.getDataForTable();
}
}
public onLoadTable() {
this.getDataForTable();
}
public onLoadTable() {
this.getDataForTable();
}
private chartConfig() {
this.barChartType = "bar";
this.barChartOptions = {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
display: true
},
tooltip: {
callbacks: {
footer: this.footer,
}
}
},
interaction: {
intersect: false,
mode: 'nearest',
axis: 'x'
},
scales: {
x: {
stacked: true
},
y: {
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 chartConfig() {
this.barChartType = "bar";
this.barChartOptions = {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
display: true,
},
tooltip: {
callbacks: {
footer: this.footer,
},
},
},
interaction: {
intersect: false,
mode: "nearest",
axis: "x",
},
scales: {
x: {
stacked: true,
},
y: {
stacked: true,
},
},
};
}
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;
}
private updateTitle() {
this.translateService.get("ListTunnelFlight_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
return beginDate;
}
private getDataForTable(): void {
this.isLoading = true;
public delete(item: TunnelFlight) {
let data: Array<TunnelFlight> = this.dataSourceTable.data;
data = data.filter((d) => d.id !== item.id);
// 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.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;"
[formGroup]="loginForm" (ngSubmit)="onLoginSubmit()">
<form
focusInvalidInput
autocomplete="off"
style="padding: 10px"
[formGroup]="loginForm"
(ngSubmit)="onLoginSubmit()"
>
<p>
<mat-form-field>
<mat-label>{{ 'LoginUser_Username' | translate }}</mat-label>
<input type="text" matInput #username="matInput" formControlName="username"
[ngClass]="{ 'is-invalid': submitted && formCtrls.username.errors }">
<mat-error *ngIf="formCtrls.username.hasError('required')">
{{ 'LoginUser_UsernameRequired' | translate }}
<mat-label>{{ "LoginUser_Username" | translate }}</mat-label>
<input
type="text"
matInput
#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 *ngIf="formCtrls.username.hasError('minlength')">
<mat-error *ngIf="formCtrls['username'].hasError('minlength')">
{{ 'LoginUser_UsernamePattern | translate }}
</mat-error>
</mat-form-field>
</p>
<p>
<mat-form-field>
<mat-label>{{ 'LoginUser_Password' | translate }}</mat-label>
<input type="password" matInput formControlName="password"
[ngClass]="{ 'is-invalid': submitted && formCtrls.password.errors }">
<mat-error *ngIf="formCtrls.password.hasError('required')">
{{ 'LoginUser_PasswordRequired' | translate }}
<mat-label>{{ "LoginUser_Password" | translate }}</mat-label>
<input
type="password"
matInput
formControlName="password"
[ngClass]="{ 'is-invalid': submitted && formCtrls['password'].errors }"
/>
<mat-error *ngIf="formCtrls['password'].hasError('required')">
{{ "LoginUser_PasswordRequired" | translate }}
</mat-error>
<mat-error *ngIf="formCtrls.password.hasError('pattern')">
{{ 'LoginUser_PasswordPattern' | translate }}
<mat-error *ngIf="formCtrls['password'].hasError('pattern')">
{{ "LoginUser_PasswordPattern" | translate }}
</mat-error>
</mat-form-field>
</p>
<button [disabled]="loading" mat-raised-button color="accent">
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
{{ 'LoginUser_BtnLogin' | translate }}
{{ "LoginUser_BtnLogin" | translate }}
</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>

View File

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

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { HttpRequest, HttpResponse } from '@angular/common/http';
import { Injectable } from "@angular/core";
import { HttpRequest, HttpResponse } from "@angular/common/http";
@Injectable()
export class RequestCache {
@@ -10,8 +10,8 @@ export class RequestCache {
}
get(req: HttpRequest<any>): HttpResponse<any> | undefined {
const splittedUrl = req.urlWithParams.split('/');
const url = splittedUrl[splittedUrl.length - 1] + '_' + req.method;
const splittedUrl = req.urlWithParams.split("/");
const url = splittedUrl[splittedUrl.length - 1] + "_" + req.method;
this.clearExpiredEntries();
const cached = this.cache.get(url);
@@ -24,8 +24,8 @@ export class RequestCache {
}
put(req: HttpRequest<any>, response: HttpResponse<any>): void {
const splittedUrl = req.urlWithParams.split('/');
const url = splittedUrl[splittedUrl.length - 1] + '_' + req.method;
const splittedUrl = req.urlWithParams.split("/");
const url = splittedUrl[splittedUrl.length - 1] + "_" + req.method;
const entry = { url, response, lastRead: Date.now() };
this.cache.set(url, entry);
@@ -36,7 +36,7 @@ export class RequestCache {
const maxAge = 30000;
const expired = Date.now() - maxAge;
this.cache.forEach(expiredEntry => {
this.cache.forEach((expiredEntry) => {
if (expiredEntry.lastRead < expired) {
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,
"angularCompilerOptions": {
"strictTemplates": true,
},
"compilerOptions": {
"importHelpers": true,
"outDir": "./dist/out-tsc",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"experimentalDecorators": true,
"target": "es2015",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"dom"
],
"module": "esnext",
"baseUrl": "./"
"moduleResolution": "bundler",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"strictPropertyInitialization": false,
"strictNullChecks": false
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"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"
]
}