update Angular to v20

Reviewed-on: #4
Co-authored-by: sandre <perso@sebastienandre.com>
Co-committed-by: sandre <perso@sebastienandre.com>
This commit was merged in pull request #4.
This commit is contained in:
2026-01-22 13:21:51 +00:00
committed by sandre
parent 137b2ab1fc
commit 701a684911
73 changed files with 6648 additions and 11197 deletions

View File

@@ -345,6 +345,7 @@ namespace skydiveLogs_api.DomainBusiness
myStats.ForLastMonthByJumpType = resetStats.ForLastMonthByJumpType; myStats.ForLastMonthByJumpType = resetStats.ForLastMonthByJumpType;
myStats.ForLastYearByDz = resetStats.ForLastYearByDz; myStats.ForLastYearByDz = resetStats.ForLastYearByDz;
myStats.ForLastYearByJumpType = resetStats.ForLastYearByJumpType; myStats.ForLastYearByJumpType = resetStats.ForLastYearByJumpType;
myStats.ByYearByJumpType = resetStats.ByYearByJumpType;
_userStatsRepository.Update(myStats); _userStatsRepository.Update(myStats);
} }
@@ -358,8 +359,10 @@ namespace skydiveLogs_api.DomainBusiness
var allStats = _userStatsRepository.GetAll(_identityService.ConnectedUser); var allStats = _userStatsRepository.GetAll(_identityService.ConnectedUser);
if (allStats == null) if (allStats == null)
{ {
allStats = new UserStats(); allStats = new UserStats
allStats.User = _identityService.ConnectedUser; {
User = _identityService.ConnectedUser
};
_userStatsRepository.Add(allStats); _userStatsRepository.Add(allStats);
} }

View File

@@ -2,15 +2,17 @@
# Use the official Microsoft ASP.NET Core image to build the backend # Use the official Microsoft ASP.NET Core image to build the backend
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-backend FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-backend
WORKDIR "/src/backend" WORKDIR /src/backend
COPY Back/ . COPY Back .
RUN dotnet restore "skydiveLogs-api/skydiveLogs-api.csproj" RUN dotnet restore skydiveLogs-api/skydiveLogs-api.csproj
RUN dotnet publish "skydiveLogs-api/skydiveLogs-api.csproj" -c Release -o /app/publish RUN dotnet publish skydiveLogs-api/skydiveLogs-api.csproj -c Release -o /app/publish
# Use the official node image to build the Angular app # Use the official node image to build the Angular app
FROM node:20-alpine AS build-frontend FROM node:22-alpine AS build-frontend
WORKDIR /app WORKDIR /app
COPY ["Front/skydivelogs-app/package.json", "Front/skydivelogs-app/package-lock.json*", "./"] # COPY ["../Front/skydivelogs-app/package.json", "../Front/skydivelogs-app/package-lock.json*", "./"]
COPY Front/skydivelogs-app/package.json .
COPY Front/skydivelogs-app/package-lock.json .
RUN npm install RUN npm install
COPY --exclude=Front/skydivelogs-app/node_modules/* Front/skydivelogs-app/ . COPY --exclude=Front/skydivelogs-app/node_modules/* Front/skydivelogs-app/ .
RUN npm run build RUN npm run build
@@ -21,13 +23,12 @@ WORKDIR /app
# Install nginx # Install nginx
RUN apt-get update && apt-get install -y nginx curl && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y nginx curl && rm -rf /var/lib/apt/lists/*
# Copy custom nginx configuration # Copy custom nginx configuration
COPY nginx.conf /etc/nginx/sites-available/default COPY nginx.conf /etc/nginx/sites-available/default
RUN rm -rf /usr/share/nginx/html/* RUN rm -rf /usr/share/nginx/html/*
# Copy frontend dist folder to nginx html directory # Copy frontend dist folder to nginx html directory
COPY --from=build-frontend /app/dist/browser /usr/share/nginx/html COPY --from=build-frontend --exclude=/app/dist/browser/config/* /app/dist/browser /usr/share/nginx/html
RUN mkdir -p /usr/share/nginx/html/config
# Copy backend from the correct build stage # Copy backend from the correct build stage
COPY --from=build-backend /app/publish /app COPY --from=build-backend /app/publish /app

View File

@@ -4,7 +4,7 @@ root = true
[*] [*]
charset = utf-8 charset = utf-8
indent_style = space indent_style = space
indent_size = 2 indent_size = 4
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true

View File

@@ -1,123 +1,145 @@
{ {
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1, "version": 1,
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {
"skydivelogs-app": { "skydivelogs-app": {
"projectType": "application", "projectType": "application",
"schematics": { "schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/assets",
"src/config",
"src/favicon.ico"
],
"styles": [
"src/assets/css/styles-app-loading.scss",
"src/assets/css/styles.css",
"src/assets/css/new-theme.scss",
"@angular/material/prebuilt-themes/pink-bluegrey.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "bundle",
"name": "main",
"baseline": "1mb",
"maximumWarning": "1.5mb",
"maximumError": "2mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all",
"optimization": true,
"extractLicenses": true,
"sourceMap": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
]
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "skydivelogs-app:build:production"
},
"development": {
"buildTarget": "skydivelogs-app:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular/build:extract-i18n"
},
"test": {
"builder": "@angular/build:karma",
"options": {
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": ["src/styles.scss"],
"scripts": []
}
}
}
}
},
"schematics": {
"@schematics/angular:component": { "@schematics/angular:component": {
"style": "scss" "type": "component"
},
"@schematics/angular:directive": {
"type": "directive"
},
"@schematics/angular:service": {
"type": "service"
},
"@schematics/angular:guard": {
"typeSeparator": "."
},
"@schematics/angular:interceptor": {
"typeSeparator": "."
},
"@schematics/angular:module": {
"typeSeparator": "."
},
"@schematics/angular:pipe": {
"typeSeparator": "."
},
"@schematics/angular:resolver": {
"typeSeparator": "."
} }
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/assets",
"src/config",
"src/favicon.ico",
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/assets/css/styles-app-loading.scss",
"src/assets/css/styles.css",
"src/assets/css/new-theme.scss",
"@angular/material/prebuilt-themes/pink-bluegrey.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "bundle",
"name": "main",
"baseline": "1mb",
"maximumWarning": "1.5mb",
"maximumError": "2mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all",
"optimization": true,
"extractLicenses": true,
"sourceMap": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
]
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "skydivelogs-app:build:production"
},
"development": {
"buildTarget": "skydivelogs-app:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": ["src/styles.scss"],
"scripts": []
}
}
}
} }
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +1,46 @@
{ {
"name": "skydivelogs-app", "name": "skydivelogs-app",
"version": "0.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
"build": "ng build", "build": "ng build --configuration production",
"test": "ng test", "test": "ng test",
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e" "e2e": "ng e2e"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^19.2.6", "@angular/animations": "^20.3.16",
"@angular/cdk": "^19.2.9", "@angular/cdk": "^20.2.14",
"@angular/common": "^19.2.6", "@angular/common": "^20.3.16",
"@angular/compiler": "^19.2.6", "@angular/compiler": "^20.3.16",
"@angular/core": "^19.2.6", "@angular/core": "^20.3.16",
"@angular/forms": "^19.2.6", "@angular/forms": "^20.3.16",
"@angular/material": "^19.2.9", "@angular/material": "^20.2.14",
"@angular/platform-browser": "^19.2.6", "@angular/platform-browser": "^20.3.16",
"@angular/platform-browser-dynamic": "^19.2.6", "@angular/platform-browser-dynamic": "^20.3.16",
"@angular/router": "^19.2.6", "@angular/router": "^20.3.16",
"@ngx-translate/core": "^17.0.0", "@ngx-translate/core": "^17.0.0",
"@ngx-translate/http-loader": "^17.0.0", "@ngx-translate/http-loader": "^17.0.0",
"chart.js": "^4.3.0", "chart.js": "^4.3.0",
"ng2-charts": "^8.0.0", "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"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^19.2.7", "@angular/build": "^20.3.14",
"@angular/cli": "~19.2.7", "@angular/cli": "~20.3.14",
"@angular/compiler-cli": "^19.2.6", "@angular/compiler-cli": "^20.3.16",
"@types/jasmine": "~4.3.0", "@types/jasmine": "~4.3.0",
"jasmine-core": "~5.1.0", "jasmine-core": "~5.1.0",
"karma": "~6.4.0", "karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0", "karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0", "karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0", "karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0", "karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.5.4" "typescript": "~5.9.3"
} }
} }

View File

@@ -1,33 +0,0 @@
import { inject, OnInit } from "@angular/core";
import { IconResolver, MatIconRegistry } from "@angular/material/icon";
import { DomSanitizer } from "@angular/platform-browser";
import { TranslateService } from "@ngx-translate/core";
import { ServiceComm } from "../services/service-comm.service";
@decorator
export class BaseComponent implements OnInit {
private titleId: string = "???";
protected serviceComm: ServiceComm;
protected translateService: TranslateService;
constructor(titleId: string) {
let iconRegistry = inject(MatIconRegistry);
let sanitizer = inject(DomSanitizer);
const resolver: IconResolver = (name) =>
sanitizer.bypassSecurityTrustResourceUrl(`/assets/icon/${name}.svg`);
iconRegistry.addSvgIconResolver(resolver);
this.serviceComm = inject(ServiceComm);
this.translateService = inject(TranslateService);
this.titleId = titleId;
}
ngOnInit() {
this.translateService.get(this.titleId).subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
// this.serviceComm.componentTitle.subscribe((title) => (this.title = data));
});
}
}
function decorator(target: typeof BaseComponent): void | typeof BaseComponent {}

View File

@@ -1,56 +1,44 @@
/* .hamburger__icon {
position: relative;
height: 1rem;
margin-right: 1rem;
cursor: pointer;
fill: #ffff;
}
.hamburger__icon__fill {
fill: #424242
} */
.navigation.side-menu-active { .navigation.side-menu-active {
left: 0px; left: 0px;
} }
.navigation { .navigation {
position: absolute; position: absolute;
width: 200px; width: 200px;
z-index: 101; z-index: 101;
background-color: grey; background-color: grey;
padding: 5px; padding: 5px;
left: -220px; left: -220px;
transition: 0.5s linear left; transition: 0.5s linear left;
-webkit-transition: 0.5s linear left; -webkit-transition: 0.5s linear left;
} }
.navigation ul { .navigation ul {
list-style: none; list-style: none;
padding: 0; padding: 0;
} }
.navigation ul li { .navigation ul li {
margin: 10px; margin: 10px;
} }
.navigation ul li a { .navigation ul li a {
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
color: #424242; color: #424242;
vertical-align: middle; vertical-align: middle;
} }
.navigation .mat-icon { .navigation .mat-icon {
vertical-align: middle; vertical-align: middle;
} }
.navigation .splitter { .navigation .splitter {
width: 100%; width: 100%;
margin-top: 20px; margin-top: 20px;
margin-bottom: 20px; margin-bottom: 20px;
} }
.mat-icon { .mat-icon {
margin-right: 15px; margin-right: 15px;
} }

View File

@@ -1,190 +1,200 @@
<mat-toolbar *ngIf="this.show()"> @if (this.show()) {
<mat-icon svgIcon="menu" (click)="snav.toggle()"></mat-icon> <mat-toolbar>
<h2>{{ translatedTitle }}</h2> <h2 (click)="snav.toggle()">
<mat-icon svgIcon="menu"></mat-icon>
<mat-select {{ translatedTitle }}
[(value)]="selectedLanguageFlag" </h2>
(selectionChange)="switchLang($event)" <mat-select
style="margin-left: 50px; width: 100px" [(value)]="selectedLanguageFlag"
> (selectionChange)="switchLang($event)"
<mat-select-trigger> style="margin-left: 50px; width: 100px"
<img >
src="{{ 'assets/img/' + selectedLanguageFlag + '.svg' }}" <mat-select-trigger>
style="width: 30px" <img
/> src="{{ 'assets/img/' + selectedLanguageFlag + '.svg' }}"
</mat-select-trigger> style="width: 30px"
<mat-option value="fr"> />
<img src="assets/img/fr.svg" style="width: 30px" /> </mat-select-trigger>
</mat-option> <mat-option value="fr">
<mat-option value="en"> <img src="assets/img/fr.svg" style="width: 30px" />
<img src="assets/img/en.svg" style="width: 30px" /> </mat-option>
</mat-option> <mat-option value="en">
</mat-select> <img src="assets/img/en.svg" style="width: 30px" />
</mat-toolbar> </mat-option>
</mat-select>
</mat-toolbar>
}
<mat-sidenav-container> <mat-sidenav-container>
<mat-sidenav #snav mode="over" style="padding: 0 20px 0 10px"> <mat-sidenav #snav mode="over" style="padding: 0 20px 0 10px">
<mat-nav-list> <mat-nav-list>
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="Summary" aria-label="Summary"
svgIcon="summary" svgIcon="summary"
></mat-icon> ></mat-icon>
<a <a
routerLink="/summary" routerLink="/summary"
routerLinkActive="active" routerLinkActive="active"
(click)="snav.toggle()" (click)="snav.toggle()"
skipLocationChange skipLocationChange
>{{ "App_Nav_Summary" | translate }}</a >{{ "App_Nav_Summary" | translate }}</a
> >
<hr class="splitter" /> <hr class="splitter" />
</mat-nav-list> </mat-nav-list>
<mat-nav-list> <mat-nav-list>
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="List of jumps" aria-label="List of jumps"
svgIcon="list" svgIcon="list"
></mat-icon> ></mat-icon>
<a <a
routerLink="/jumps" routerLink="/jumps"
routerLinkActive="active" routerLinkActive="active"
(click)="snav.toggle()" (click)="snav.toggle()"
skipLocationChange skipLocationChange
>{{ "App_Nav_Jumps" | translate }}</a >{{ "App_Nav_Jumps" | translate }}</a
> >
</mat-nav-list> </mat-nav-list>
<mat-nav-list> <mat-nav-list>
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="Add jumps" aria-label="Add jumps"
svgIcon="add" svgIcon="add"
></mat-icon> ></mat-icon>
<a <a
routerLink="/newjump" routerLink="/newjump"
routerLinkActive="active" routerLinkActive="active"
(click)="snav.toggle()" (click)="snav.toggle()"
skipLocationChange skipLocationChange
>{{ "App_Nav_NewJump" | translate }}</a >{{ "App_Nav_NewJump" | translate }}</a
> >
<hr class="splitter" /> <hr class="splitter" />
</mat-nav-list> </mat-nav-list>
<mat-nav-list> <mat-nav-list>
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="List of tunnel flights" aria-label="List of tunnel flights"
svgIcon="list" svgIcon="list"
></mat-icon> ></mat-icon>
<a <a
routerLink="/tunnelFlights" routerLink="/tunnelFlights"
routerLinkActive="active" routerLinkActive="active"
(click)="snav.toggle()" (click)="snav.toggle()"
skipLocationChange skipLocationChange
>{{ "App_Nav_TunnelFlights" | translate }}</a >{{ "App_Nav_TunnelFlights" | translate }}</a
> >
</mat-nav-list> </mat-nav-list>
<mat-nav-list> <mat-nav-list>
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="Add flights in tunnel" aria-label="Add flights in tunnel"
svgIcon="add" svgIcon="add"
></mat-icon> ></mat-icon>
<a <a
routerLink="/newTunnelFlight" routerLink="/newTunnelFlight"
routerLinkActive="active" routerLinkActive="active"
(click)="snav.toggle()" (click)="snav.toggle()"
skipLocationChange skipLocationChange
>{{ "App_Nav_NewTunnelFlight" | translate }}</a >{{ "App_Nav_NewTunnelFlight" | translate }}</a
> >
<hr class="splitter" /> <hr class="splitter" />
</mat-nav-list> </mat-nav-list>
<mat-nav-list> <mat-nav-list>
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="Dropzones" aria-label="Dropzones"
svgIcon="dz" svgIcon="dz"
></mat-icon> ></mat-icon>
<a <a
routerLink="/dzs" routerLink="/dzs"
routerLinkActive="active" routerLinkActive="active"
(click)="snav.toggle()" (click)="snav.toggle()"
skipLocationChange skipLocationChange
>{{ "App_Nav_Dzs" | translate }}</a >{{ "App_Nav_Dzs" | translate }}</a
> >
</mat-nav-list> </mat-nav-list>
<mat-nav-list> <mat-nav-list>
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="Aircrafts" aria-label="Aircrafts"
svgIcon="aircraft" svgIcon="aircraft"
></mat-icon> ></mat-icon>
<a <a
routerLink="/aircrafts" routerLink="/aircrafts"
routerLinkActive="active" routerLinkActive="active"
(click)="snav.toggle()" (click)="snav.toggle()"
skipLocationChange skipLocationChange
>{{ "App_Nav_Aircrafts" | translate }}</a >{{ "App_Nav_Aircrafts" | translate }}</a
> >
</mat-nav-list> </mat-nav-list>
<mat-nav-list> <mat-nav-list>
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="List of jump types" aria-label="List of jump types"
svgIcon="flight_land" svgIcon="flight_land"
></mat-icon> ></mat-icon>
<a <a
routerLink="/jumpTypes" routerLink="/jumpTypes"
routerLinkActive="active" routerLinkActive="active"
(click)="snav.toggle()" (click)="snav.toggle()"
skipLocationChange skipLocationChange
>{{ "App_Nav_JumpTypes" | translate }}</a >{{ "App_Nav_JumpTypes" | translate }}</a
> >
</mat-nav-list> </mat-nav-list>
<mat-nav-list> <mat-nav-list>
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="List of gears" aria-label="List of gears"
svgIcon="gear" svgIcon="gear"
></mat-icon> ></mat-icon>
<a <a
routerLink="/gears" routerLink="/gears"
routerLinkActive="active" routerLinkActive="active"
(click)="snav.toggle()" (click)="snav.toggle()"
skipLocationChange skipLocationChange
>{{ "App_Nav_Gears" | translate }}</a >{{ "App_Nav_Gears" | translate }}</a
> >
</mat-nav-list> </mat-nav-list>
<mat-nav-list *ngIf="currentUser"> @if (currentUser) {
<hr class="splitter" /> <mat-nav-list>
<mat-icon <hr class="splitter" />
aria-hidden="false" <mat-icon
aria-label="User account" aria-hidden="false"
svgIcon="account" aria-label="User account"
></mat-icon> svgIcon="account"
<a ></mat-icon>
routerLink="/user" <a
routerLinkActive="active" routerLink="/user"
(click)="snav.toggle()" routerLinkActive="active"
skipLocationChange (click)="snav.toggle()"
> skipLocationChange
{{ this.currentUser.firstName }} {{ this.currentUser.lastName }} >
</a> {{ this.currentUser.firstName }}
</mat-nav-list> {{ this.currentUser.lastName }}
<mat-nav-list *ngIf="currentUser"> </a>
<mat-icon </mat-nav-list>
aria-hidden="false" }
aria-label="To logout" @if (currentUser) {
svgIcon="logout" <mat-nav-list>
></mat-icon> <mat-icon
<span (click)="snav.toggle(); logout()" style="cursor: pointer">{{ aria-hidden="false"
"App_Nav_Logout" | translate aria-label="To logout"
}}</span> svgIcon="logout"
</mat-nav-list> ></mat-icon>
</mat-sidenav> <span
(click)="snav.toggle(); logout()"
style="cursor: pointer"
>{{ "App_Nav_Logout" | translate }}</span
>
</mat-nav-list>
}
</mat-sidenav>
<mat-sidenav-content> <mat-sidenav-content>
<router-outlet></router-outlet> <router-outlet></router-outlet>
<footer style="text-align: right"> <footer style="text-align: right">
{{ "App_Footer" | translate }}{{ version }} - &#64;Séb {{ "App_Footer" | translate }}{{ version }} - &#64;Séb
</footer> </footer>
</mat-sidenav-content> </mat-sidenav-content>
</mat-sidenav-container> </mat-sidenav-container>

View File

@@ -1,21 +1,21 @@
import { Component, inject, OnInit } from "@angular/core"; import { Component, inject, OnInit } from "@angular/core";
import { Router, RouterLink, RouterOutlet } from "@angular/router"; import { Router, RouterLink, RouterOutlet } from "@angular/router";
import { CommonModule } from "@angular/common";
import { DomSanitizer } from "@angular/platform-browser"; import { DomSanitizer } from "@angular/platform-browser";
import { MatToolbarModule } from "@angular/material/toolbar"; import { MatToolbarModule } from "@angular/material/toolbar";
import { import {
IconResolver, IconResolver,
MatIconModule, MatIconModule,
MatIconRegistry, MatIconRegistry,
} from "@angular/material/icon"; } from "@angular/material/icon";
import { MatSelectModule } from "@angular/material/select"; import { MatSelectModule } from "@angular/material/select";
import { MatOptionModule } from "@angular/material/core"; import { MatOptionModule } from "@angular/material/core";
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 { import {
TranslateService, TranslateService,
TranslateModule, TranslateModule,
TranslatePipe, TranslatePipe,
} from "@ngx-translate/core"; } from "@ngx-translate/core";
import { User } from "../models/user"; import { User } from "../models/user";
@@ -27,80 +27,82 @@ import { ConfigurationHelper } from "../services/configuration-helper";
import { ServiceCacheApi } from "../services/service-cache-api.service"; import { ServiceCacheApi } from "../services/service-cache-api.service";
@Component({ @Component({
selector: "app-root", selector: "app-root",
templateUrl: "./app.component.html", templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"], styleUrls: ["./app.component.css"],
imports: [ imports: [
CommonModule, MatToolbarModule,
MatToolbarModule, MatIconModule,
MatIconModule, MatSelectModule,
MatSelectModule, MatOptionModule,
MatOptionModule, MatSidenavModule,
MatSidenavModule, MatListModule,
MatListModule, RouterOutlet,
RouterOutlet, RouterLink,
RouterLink, TranslateModule,
TranslateModule, TranslatePipe,
TranslatePipe, ],
],
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
public translatedTitle = "???"; public translatedTitle = "???";
public currentUser: User; public currentUser: User;
public version: string; public version: string;
public selectedLanguageFlag: string; public selectedLanguageFlag: string;
constructor( constructor(
private router: Router, private router: Router,
private authenticationService: AuthenticationService, private authenticationService: AuthenticationService,
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
private serviceCacheApi: ServiceCacheApi, private serviceCacheApi: ServiceCacheApi,
private translateService: TranslateService, private translateService: TranslateService,
) { ) {
const sanitizer = inject(DomSanitizer); const sanitizer = inject(DomSanitizer);
const resolver: IconResolver = (name) => const resolver: IconResolver = (name) =>
sanitizer.bypassSecurityTrustResourceUrl(`/assets/icon/${name}.svg`); sanitizer.bypassSecurityTrustResourceUrl(
const iconRegistry = inject(MatIconRegistry); `/assets/icon/${name}.svg`,
iconRegistry.addSvgIconResolver(resolver); );
const iconRegistry = inject(MatIconRegistry);
iconRegistry.addSvgIconResolver(resolver);
this.authenticationService.currentUser.subscribe((user) => { this.authenticationService.currentUser.subscribe((user) => {
if (user) { if (user) {
this.currentUser = user; this.currentUser = user;
this.translateService.addLangs(["en", "fr"]); this.translateService.addLangs(["en", "fr"]);
this.translateService.use(user.language); this.translateService.use(user.language);
this.selectedLanguageFlag = user.language; this.selectedLanguageFlag = user.language;
} }
}); });
ConfigurationHelper.settings.subscribe((settings) => { ConfigurationHelper.settings.subscribe((settings) => {
if (settings != null) { if (settings != null) {
this.version = settings.version; this.version = settings.version;
} }
}); });
} }
ngOnInit() { ngOnInit() {}
this.serviceComm.componentTitle.subscribe( ngAfterViewInit() {
(title) => (this.translatedTitle = title), this.serviceComm.componentTitle.subscribe(
); (title) => (this.translatedTitle = title),
} );
}
public show() { public show() {
return this.authenticationService.currentUserValue != undefined; return this.authenticationService.currentUserValue != undefined;
} }
public logout() { public logout() {
this.serviceCacheApi.delete(CacheApiKey.Dropzone); this.serviceCacheApi.delete(CacheApiKey.Dropzone);
this.authenticationService.logout(); this.authenticationService.logout();
this.router.navigate(["/login"], { skipLocationChange: true }); this.router.navigate(["/login"], { skipLocationChange: true });
} }
public switchLang(event: any) { public switchLang(event: any) {
this.translateService.use(event.value); this.translateService.use(event.value);
this.currentUser.language = event.value; this.currentUser.language = event.value;
this.authenticationService.currentUserValue = this.currentUser; this.authenticationService.currentUserValue = this.currentUser;
this.selectedLanguageFlag = event.value; this.selectedLanguageFlag = event.value;
this.serviceComm.forceTranslate(); this.serviceComm.forceTranslate();
} }
} }

View File

@@ -32,42 +32,44 @@ import { provideTranslateHttpLoader } from "@ngx-translate/http-loader";
// Déclaration de la fonction d'initialisation de la configuration // Déclaration de la fonction d'initialisation de la configuration
export function initConfig(configService: ConfigurationHelper) { export function initConfig(configService: ConfigurationHelper) {
return () => configService.load(environment.env); return () => configService.load(environment.env);
} }
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
TunnelService, TunnelService,
TunnelFlightService, TunnelFlightService,
ImageService, ImageService,
AircraftService, AircraftService,
DropzoneService, DropzoneService,
GearService, GearService,
JumpService, JumpService,
JumpTypeService, JumpTypeService,
StatsService, StatsService,
ServiceComm, ServiceComm,
DateService, DateService,
RequestCache, RequestCache,
ConfigurationHelper, ConfigurationHelper,
DatePipe, DatePipe,
ServiceCacheApi, ServiceCacheApi,
provideAppInitializer(() => { provideAppInitializer(() => {
const initializerFn = initConfig(inject(ConfigurationHelper)); const initializerFn = initConfig(inject(ConfigurationHelper));
return initializerFn(); return initializerFn();
}), }),
provideHttpClient(withInterceptors([JwtAuthInterceptor, ErrorInterceptor])), provideHttpClient(
provideCharts(withDefaultRegisterables()), withInterceptors([JwtAuthInterceptor, ErrorInterceptor]),
provideZoneChangeDetection({ eventCoalescing: true }), ),
provideRouter(routes), provideCharts(withDefaultRegisterables()),
provideHttpClient(), provideZoneChangeDetection({ eventCoalescing: true }),
provideTranslateService({ provideRouter(routes),
loader: provideTranslateHttpLoader({ provideHttpClient(),
prefix: "/assets/i18n/", provideTranslateService({
suffix: ".json", loader: provideTranslateHttpLoader({
}), prefix: "/assets/i18n/",
fallbackLang: "en", suffix: ".json",
lang: "en", }),
}), fallbackLang: "en",
], lang: "en",
}),
],
}; };

View File

@@ -3,91 +3,97 @@ import { Routes } from "@angular/router";
import { AuthGuardService } from "../services/auth-guard.service"; import { AuthGuardService } from "../services/auth-guard.service";
export const routes: Routes = [ export const routes: Routes = [
{ {
path: "", path: "",
loadComponent: () => loadComponent: () =>
import("./default/default.component").then((m) => m.DefaultComponent), import("./default/default.component").then(
canActivate: [AuthGuardService], (m) => m.DefaultComponent,
}, ),
{ canActivate: [AuthGuardService],
path: "summary", },
loadComponent: () => {
import("./summary/summary.component").then((m) => m.SummaryComponent), path: "summary",
canActivate: [AuthGuardService], loadComponent: () =>
}, import("./summary/summary.component").then(
{ (m) => m.SummaryComponent,
path: "jumps", ),
loadComponent: () => canActivate: [AuthGuardService],
import("./list-of-jumps/list-of-jumps.component").then( },
(m) => m.ListOfJumpsComponent, {
), path: "jumps",
canActivate: [AuthGuardService], loadComponent: () =>
}, import("./list-of-jumps/list-of-jumps.component").then(
{ (m) => m.ListOfJumpsComponent,
path: "dzs", ),
loadComponent: () => canActivate: [AuthGuardService],
import("./list-of-dzs/list-of-dzs.component").then( },
(m) => m.ListOfDzsComponent, {
), path: "dzs",
canActivate: [AuthGuardService], loadComponent: () =>
}, import("./list-of-dzs/list-of-dzs.component").then(
{ (m) => m.ListOfDzsComponent,
path: "newjump", ),
loadComponent: () => canActivate: [AuthGuardService],
import("./new-jump/new-jump.component").then((m) => m.NewJumpComponent), },
canActivate: [AuthGuardService], {
}, path: "newjump",
{ loadComponent: () =>
path: "aircrafts", import("./new-jump/new-jump.component").then(
loadComponent: () => (m) => m.NewJumpComponent,
import("./list-of-aircrafts/list-of-aircrafts.component").then( ),
(m) => m.ListOfAircraftsComponent, canActivate: [AuthGuardService],
), },
canActivate: [AuthGuardService], {
}, path: "aircrafts",
{ loadComponent: () =>
path: "jumpTypes", import("./list-of-aircrafts/list-of-aircrafts.component").then(
loadComponent: () => (m) => m.ListOfAircraftsComponent,
import("./list-of-jump-types/list-of-jump-types.component").then( ),
(m) => m.ListOfJumpTypesComponent, canActivate: [AuthGuardService],
), },
canActivate: [AuthGuardService], {
}, path: "jumpTypes",
{ loadComponent: () =>
path: "gears", import("./list-of-jump-types/list-of-jump-types.component").then(
loadComponent: () => (m) => m.ListOfJumpTypesComponent,
import("./list-of-gears/list-of-gears.component").then( ),
(m) => m.ListOfGearsComponent, canActivate: [AuthGuardService],
), },
canActivate: [AuthGuardService], {
}, path: "gears",
{ loadComponent: () =>
path: "user", import("./list-of-gears/list-of-gears.component").then(
loadComponent: () => (m) => m.ListOfGearsComponent,
import("./user-profile/user-profile.component").then( ),
(m) => m.UserProfileComponent, canActivate: [AuthGuardService],
), },
canActivate: [AuthGuardService], {
}, path: "user",
{ loadComponent: () =>
path: "newTunnelFlight", import("./user-profile/user-profile.component").then(
loadComponent: () => (m) => m.UserProfileComponent,
import("./new-tunnel-flight/new-tunnel-flight.component").then( ),
(m) => m.NewTunnelFlightComponent, canActivate: [AuthGuardService],
), },
canActivate: [AuthGuardService], {
}, path: "newTunnelFlight",
{ loadComponent: () =>
path: "tunnelFlights", import("./new-tunnel-flight/new-tunnel-flight.component").then(
loadComponent: () => (m) => m.NewTunnelFlightComponent,
import("./list-of-tunnel-flights/list-of-tunnel-flights.component").then( ),
(m) => m.ListOfTunnelFlightsComponent, canActivate: [AuthGuardService],
), },
canActivate: [AuthGuardService], {
}, path: "tunnelFlights",
{ loadComponent: () =>
path: "login", import("./list-of-tunnel-flights/list-of-tunnel-flights.component").then(
loadComponent: () => (m) => m.ListOfTunnelFlightsComponent,
import("./login/login.component").then((m) => m.LoginComponent), ),
}, canActivate: [AuthGuardService],
},
{
path: "login",
loadComponent: () =>
import("./login/login.component").then((m) => m.LoginComponent),
},
]; ];

View File

@@ -1,104 +1,136 @@
<form <form
focusInvalidInput focusInvalidInput
autocomplete="off" autocomplete="off"
style="padding: 10px" style="padding: 10px"
(ngSubmit)="onCreateSubmit()" (ngSubmit)="onCreateSubmit()"
[formGroup]="createForm" [formGroup]="createForm"
> >
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>{{ "LoginCreateUser_Firstname" | translate }}</mat-label> <mat-label>{{ "CreateUser_Firstname" | translate }}</mat-label>
<input <input
type="text" type="text"
matInput matInput
#firstname="matInput" #firstname="matInput"
formControlName="firstname" formControlName="firstname"
[ngClass]="{ 'is-invalid': submitted && formCtrls['firstname'].errors }" [ngClass]="{
tabindex="0" 'is-invalid': submitted && formCtrls['firstname'].errors,
/> }"
<mat-error *ngIf="formCtrls['firstname'].hasError('required')"> tabindex="0"
{{ "LoginCreateUser_FirstnameRequired" | translate }} />
</mat-error> @if (formCtrls["firstname"].hasError("required")) {
<mat-error *ngIf="formCtrls['firstname'].hasError('minlength')"> <mat-error>
{{ "LoginCreateUser_FirstnamePattern" | translate }} {{ "CreateUser_FirstnameRequired" | translate }}
</mat-error> </mat-error>
</mat-form-field> }
</p> @if (formCtrls["firstname"].hasError("minlength")) {
<p> <mat-error>
<mat-form-field> {{ "CreateUser_FirstnamePattern" | translate }}
<mat-label>{{ "LoginCreateUser_Lastname" | translate }}</mat-label> </mat-error>
<input }
matInput </mat-form-field>
type="text" </p>
formControlName="lastname" <p>
[ngClass]="{ 'is-invalid': submitted && formCtrls['lastname'].errors }" <mat-form-field>
tabindex="1" <mat-label>{{ "CreateUser_Lastname" | translate }}</mat-label>
/> <input
<mat-error *ngIf="formCtrls['lastname'].hasError('required')"> matInput
{{ "LoginCreateUser_LastnameRequired" | translate }} type="text"
</mat-error> formControlName="lastname"
<mat-error *ngIf="formCtrls['lastname'].hasError('minlength')"> [ngClass]="{
{{ "LoginCreateUser_LastnamePattern" | translate }} 'is-invalid': submitted && formCtrls['lastname'].errors,
</mat-error> }"
</mat-form-field> tabindex="1"
</p> />
<p> @if (formCtrls["lastname"].hasError("required")) {
<mat-form-field> <mat-error>
<mat-label>{{ "LoginCreateUser_Email" | translate }}</mat-label> {{ "CreateUser_LastnameRequired" | translate }}
<input </mat-error>
matInput }
type="email" @if (formCtrls["lastname"].hasError("minlength")) {
formControlName="email" <mat-error>
[ngClass]="{ 'is-invalid': submitted && formCtrls['email'].errors }" {{ "CreateUser_LastnamePattern" | translate }}
tabindex="3" </mat-error>
/> }
<mat-error *ngIf="formCtrls['email'].hasError('required')"> </mat-form-field>
{{ "LoginCreateUser_EmailRequired" | translate }} </p>
</mat-error> <p>
<mat-error *ngIf="formCtrls['email'].hasError('email')"> <mat-form-field>
{{ "LoginCreateUser_EmailPattern" | translate }} <mat-label>{{ "CreateUser_Email" | translate }}</mat-label>
</mat-error> <input
</mat-form-field> matInput
</p> type="email"
<p> formControlName="email"
<mat-form-field> [ngClass]="{
<mat-label>{{ "LoginCreateUser_Username" | translate }}</mat-label> 'is-invalid': submitted && formCtrls['email'].errors,
<input }"
matInput tabindex="3"
type="text" />
formControlName="username" @if (formCtrls["email"].hasError("required")) {
[ngClass]="{ 'is-invalid': submitted && formCtrls['username'].errors }" <mat-error>
tabindex="4" {{ "CreateUser_EmailRequired" | translate }}
/> </mat-error>
<mat-error *ngIf="formCtrls['username'].hasError('required')"> }
{{ "LoginCreateUser_UsernameRequired" | translate }} @if (formCtrls["email"].hasError("email")) {
</mat-error> <mat-error>
<mat-error *ngIf="formCtrls['username'].hasError('minlength')"> {{ "CreateUser_EmailPattern" | 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>{{ "CreateUser_Username" | translate }}</mat-label>
<input <input
matInput matInput
type="password" type="text"
formControlName="password" formControlName="username"
[ngClass]="{ 'is-invalid': submitted && formCtrls['password'].errors }" [ngClass]="{
tabindex="5" 'is-invalid': submitted && formCtrls['username'].errors,
/> }"
<mat-error *ngIf="formCtrls['password'].hasError('required')"> tabindex="4"
{{ "LoginCreateUser_PasswordRequired" | translate }} />
</mat-error> @if (formCtrls["username"].hasError("required")) {
<mat-error *ngIf="formCtrls['password'].hasError('pattern')"> <mat-error>
{{ "LoginCreateUser_PasswordPattern" | translate }} {{ "CreateUser_UsernameRequired" | translate }}
</mat-error> </mat-error>
</mat-form-field> }
</p> @if (formCtrls["username"].hasError("minlength")) {
<mat-error>
{{ "CreateUser_UsernamePattern" | translate }}
</mat-error>
}
</mat-form-field>
</p>
<p>
<mat-form-field>
<mat-label>{{ "CreateUser_Password" | translate }}</mat-label>
<input
matInput
type="password"
formControlName="password"
[ngClass]="{
'is-invalid': submitted && formCtrls['password'].errors,
}"
tabindex="5"
/>
@if (formCtrls["password"].hasError("required")) {
<mat-error>
{{ "CreateUser_PasswordRequired" | translate }}
</mat-error>
}
@if (formCtrls["password"].hasError("pattern")) {
<mat-error>
{{ "CreateUser_PasswordPattern" | translate }}
</mat-error>
}
</mat-form-field>
</p>
<button [disabled]="!createForm.valid" mat-raised-button color="accent"> <button [disabled]="!createForm.valid" mat-raised-button color="accent">
{{ "LoginCreateUser_BtnLogin" | translate }} {{ "CreateUser_BtnLogin" | translate }}
</button> </button>
<div *ngIf="error" class="alert alert-danger mt-3 mb-0">{{ error }}</div> @if (error) {
<div class="alert alert-danger mt-3 mb-0">{{ error }}</div>
}
</form> </form>

View File

@@ -1,15 +1,15 @@
import { Component, OnInit, ViewChild } from "@angular/core"; import { Component, OnInit, ViewChild } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router"; import { Router, ActivatedRoute } from "@angular/router";
import { import {
FormBuilder, FormBuilder,
FormGroup, FormGroup,
ReactiveFormsModule, ReactiveFormsModule,
Validators, Validators,
} from "@angular/forms"; } from "@angular/forms";
import { import {
TranslateModule, TranslateModule,
TranslatePipe, TranslatePipe,
TranslateService, TranslateService,
} from "@ngx-translate/core"; } from "@ngx-translate/core";
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { MatFormFieldModule } from "@angular/material/form-field"; import { MatFormFieldModule } from "@angular/material/form-field";
@@ -22,90 +22,90 @@ 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"],
imports: [ imports: [
CommonModule, CommonModule,
MatFormFieldModule, MatFormFieldModule,
ReactiveFormsModule, ReactiveFormsModule,
MatInputModule, MatInputModule,
MatButtonModule, MatButtonModule,
TranslateModule, TranslateModule,
TranslatePipe, TranslatePipe,
], ],
}) })
export class CreateUserComponent implements OnInit { export class CreateUserComponent implements OnInit {
public createForm: FormGroup; public createForm: FormGroup;
public invalidForm = true; public invalidForm = true;
public submitted = false; public submitted = false;
public returnUrl: string; public returnUrl: string;
public error: string = ""; public error: string = "";
constructor( constructor(
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private authenticationService: AuthenticationService, private authenticationService: AuthenticationService,
private translateService: TranslateService, 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(["/"]);
}
} }
}
ngOnInit() { ngOnInit() {
this.createForm = this.formBuilder.group( this.createForm = this.formBuilder.group(
{ {
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]],
username: ["", [Validators.required, Validators.minLength(3)]], username: ["", [Validators.required, Validators.minLength(3)]],
password: [ password: [
"", "",
[ [
Validators.required, Validators.required,
Validators.pattern( Validators.pattern(
"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[@$!%*#?&-_|]).{8,}$", "^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[@$!%*#?&-_|]).{8,}$",
), ),
], ],
], ],
}, },
{ updateOn: "blur" }, { updateOn: "blur" },
); );
// 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() {
return this.createForm.controls; return this.createForm.controls;
} }
onCreateSubmit() { onCreateSubmit() {
this.invalidForm = false; this.invalidForm = false;
this.submitted = true; this.submitted = true;
if (this.createForm.invalid) { if (this.createForm.invalid) {
this.invalidForm = true; this.invalidForm = true;
return; return;
}
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.language = this.translateService.getCurrentLang();
this.authenticationService
.create(createUser)
.pipe(first())
.subscribe({
complete: () => this.router.navigate([this.returnUrl]),
error: (error) => {
this.error = error.message;
this.invalidForm = false;
},
});
} }
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.language = this.translateService.getCurrentLang();
this.authenticationService
.create(createUser)
.pipe(first())
.subscribe({
complete: () => this.router.navigate([this.returnUrl]),
error: (error) => {
this.error = error.message;
this.invalidForm = false;
},
});
}
} }

View File

@@ -1,11 +1,11 @@
.content { .content {
height: 90vh; height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
} }
p { p {
margin: 0; margin: 0;
} }

View File

@@ -1,62 +1,62 @@
<div class="content"> <div class="content">
<p> <p>
<a <a
class="nostyle" class="nostyle"
routerLink="/summary" routerLink="/summary"
routerLinkActive="active" routerLinkActive="active"
skipLocationChange skipLocationChange
> >
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="Summary" aria-label="Summary"
style="width: 128px; height: 128px; font-size: 128px" style="width: 128px; height: 128px; font-size: 128px"
svgIcon="summary" svgIcon="summary"
></mat-icon> ></mat-icon>
</a> </a>
</p> </p>
<p> <p>
<a <a
class="nostyle" class="nostyle"
routerLink="/newjump" routerLink="/newjump"
routerLinkActive="active" routerLinkActive="active"
skipLocationChange skipLocationChange
> >
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="Add jumps" aria-label="Add jumps"
style="width: 128px; height: 128px; font-size: 128px" style="width: 128px; height: 128px; font-size: 128px"
svgIcon="add" svgIcon="add"
></mat-icon> ></mat-icon>
</a> </a>
</p> </p>
<p> <p>
<a <a
class="nostyle" class="nostyle"
routerLink="/jumps" routerLink="/jumps"
routerLinkActive="active" routerLinkActive="active"
skipLocationChange skipLocationChange
> >
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="List of jumps" aria-label="List of jumps"
style="width: 128px; height: 128px; font-size: 128px" style="width: 128px; height: 128px; font-size: 128px"
svgIcon="list" svgIcon="list"
></mat-icon> ></mat-icon>
</a> </a>
</p> </p>
<p> <p>
<a <a
class="nostyle" class="nostyle"
routerLink="/tunnelFlights" routerLink="/tunnelFlights"
routerLinkActive="active" routerLinkActive="active"
skipLocationChange skipLocationChange
> >
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="List of tunnel flights" aria-label="List of tunnel flights"
style="width: 128px; height: 128px; font-size: 128px" style="width: 128px; height: 128px; font-size: 128px"
svgIcon="wind" svgIcon="wind"
></mat-icon> ></mat-icon>
</a> </a>
</p> </p>
</div> </div>

View File

@@ -12,48 +12,48 @@ import { JumpTypeService } from "../../services/jump-type.service";
import { ServiceComm } from "../../services/service-comm.service"; import { ServiceComm } from "../../services/service-comm.service";
@Component({ @Component({
selector: "app-default", selector: "app-default",
templateUrl: "./default.component.html", templateUrl: "./default.component.html",
styleUrls: ["./default.component.css"], styleUrls: ["./default.component.css"],
imports: [TranslateModule, MatIconModule, RouterLink], imports: [TranslateModule, MatIconModule, RouterLink],
}) })
export class DefaultComponent implements OnInit { export class DefaultComponent implements OnInit {
constructor( constructor(
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
private translateService: TranslateService, private translateService: TranslateService,
private authenticationService: AuthenticationService, private authenticationService: AuthenticationService,
private serviceApiAircraft: AircraftService, private serviceApiAircraft: AircraftService,
private serviceApiJumpType: JumpTypeService, private serviceApiJumpType: JumpTypeService,
private serviceApiDropzone: DropzoneService, private serviceApiDropzone: DropzoneService,
private serviceApiGear: GearService, private serviceApiGear: GearService,
) {} ) {}
ngOnInit() { ngOnInit() {
this.authenticationService.alwaysLogin(); this.authenticationService.alwaysLogin();
this.putToCacheRefDatas().subscribe(() => { this.putToCacheRefDatas().subscribe(() => {
console.log("Push to cache the referentiel datas"); console.log("Push to cache the referentiel datas");
}); });
this.updateTitle();
this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) {
this.updateTitle(); this.updateTitle();
} this.serviceComm.forceTranslateTitle.subscribe((data) => {
}); if (data === true) {
} this.updateTitle();
}
});
}
private putToCacheRefDatas(): Observable<any[]> { private putToCacheRefDatas(): Observable<any[]> {
var aircraftResp = this.serviceApiAircraft.getListOfAircrafts(false); var aircraftResp = this.serviceApiAircraft.getListOfAircrafts(false);
var jumpTypeResp = this.serviceApiJumpType.getListOfJumpTypes(); var jumpTypeResp = this.serviceApiJumpType.getListOfJumpTypes();
var dzResp = this.serviceApiDropzone.getListOfDropZones(false); var dzResp = this.serviceApiDropzone.getListOfDropZones(false);
var gearResp = this.serviceApiGear.getListOfGears(); var gearResp = this.serviceApiGear.getListOfGears();
return forkJoin([aircraftResp, jumpTypeResp, dzResp, gearResp]); return forkJoin([aircraftResp, jumpTypeResp, dzResp, gearResp]);
} }
private updateTitle() { private updateTitle() {
this.translateService.get("Default_Title").subscribe((data) => { this.translateService.get("Default_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data); this.serviceComm.updatedComponentTitle(data);
}); });
} }
} }

View File

@@ -1,34 +1,36 @@
<form (ngSubmit)="updateJump()"> <form (ngSubmit)="updateJump()">
<p> <p>
<span>Gear : {{ jump.gear.name }} ({{ jump.gear.mainCanopy }})</span> <span>Gear : {{ jump.gear.name }} ({{ jump.gear.mainCanopy }})</span>
</p> </p>
<p> <p>
<mat-checkbox <mat-checkbox
[(ngModel)]="jump.isSpecial" [(ngModel)]="jump.isSpecial"
name="isSpecial" name="isSpecial"
labelPosition="before" labelPosition="before"
>Special jump</mat-checkbox >Special jump</mat-checkbox
> >
</p> </p>
<p> <p>
<mat-checkbox <mat-checkbox
[(ngModel)]="jump.withCutaway" [(ngModel)]="jump.withCutaway"
name="withCutaway" name="withCutaway"
labelPosition="before" labelPosition="before"
>Cutaway</mat-checkbox >Cutaway</mat-checkbox
> >
</p> </p>
<mat-form-field> <mat-form-field>
<textarea <textarea
matInput matInput
placeholder="Comments" placeholder="Comments"
name="comments" name="comments"
[(ngModel)]="jump.notes" [(ngModel)]="jump.notes"
name="comments" name="comments"
type="text" type="text"
></textarea> ></textarea>
</mat-form-field> </mat-form-field>
<br /> <br />
<button mat-raised-button color="accent" *ngIf="editMode">Update</button> @if (editMode) {
<button mat-raised-button color="accent">Update</button>
}
</form> </form>

View File

@@ -1,5 +1,5 @@
import { Component, Inject, OnInit } from "@angular/core"; import { Component, Inject, OnInit } from "@angular/core";
import { CommonModule } from "@angular/common";
import { MAT_DIALOG_DATA } from "@angular/material/dialog"; import { MAT_DIALOG_DATA } from "@angular/material/dialog";
import { TranslateModule } from "@ngx-translate/core"; import { TranslateModule } from "@ngx-translate/core";
import { MatCheckboxModule } from "@angular/material/checkbox"; import { MatCheckboxModule } from "@angular/material/checkbox";
@@ -15,45 +15,44 @@ import { JumpService } from "../../services/jump.service";
import { ServiceComm } from "../../services/service-comm.service"; import { ServiceComm } from "../../services/service-comm.service";
@Component({ @Component({
selector: "app-jump-infos", selector: "app-jump-infos",
templateUrl: "./jump-infos.component.html", templateUrl: "./jump-infos.component.html",
styleUrls: ["./jump-infos.component.css"], styleUrls: ["./jump-infos.component.css"],
imports: [ imports: [
TranslateModule, TranslateModule,
CommonModule, FormsModule,
FormsModule, MatCheckboxModule,
MatCheckboxModule, MatFormFieldModule,
MatFormFieldModule, ReactiveFormsModule,
ReactiveFormsModule, MatInputModule,
MatInputModule, MatButtonModule,
MatButtonModule, ],
],
}) })
export class JumpInfosComponent implements OnInit { export class JumpInfosComponent implements OnInit {
public editMode: boolean; public editMode: boolean;
public jump: JumpResp; public jump: JumpResp;
constructor( constructor(
@Inject(MAT_DIALOG_DATA) public data: any, @Inject(MAT_DIALOG_DATA) public data: any,
private serviceJump: JumpService, private serviceJump: JumpService,
private serviceComm: ServiceComm private serviceComm: ServiceComm,
) { ) {
this.jump = new JumpResp(data.jump); this.jump = new JumpResp(data.jump);
this.editMode = data.editMode; this.editMode = data.editMode;
} }
ngOnInit(): void {} ngOnInit(): void {}
public updateJump() { public updateJump() {
this.serviceJump this.serviceJump
.updateJump( .updateJump(
this.jump.id, this.jump.id,
this.jump.isSpecial, this.jump.isSpecial,
this.jump.withCutaway, this.jump.withCutaway,
this.jump.notes this.jump.notes,
) )
.subscribe(() => { .subscribe(() => {
this.serviceComm.refreshData(AddAction.Jump); this.serviceComm.refreshData(AddAction.Jump);
}); });
} }
} }

View File

@@ -1,22 +1,22 @@
table { table {
width: 100%; width: 100%;
} }
.mat-row td { .mat-row td {
padding: 15px; padding: 15px;
} }
.spanWithBreakWord { .spanWithBreakWord {
display: inline-block; display: inline-block;
min-width: 200px; min-width: 200px;
word-wrap: break-word; word-wrap: break-word;
} }
.content { .content {
min-height: 90vh; min-height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
padding-top: 25px; padding-top: 25px;
} }

View File

@@ -1,49 +1,54 @@
<div class="content"> <div class="content">
<div *ngIf="dataSourceTable != null; else loading"> @if (dataSourceTable != null) {
<button <div>
mat-raised-button @if (isUserAdmin == true) {
color="accent" <button
(click)="openDialogToAdd()" mat-raised-button
*ngIf="isUserAdmin == true" color="accent"
> (click)="openDialogToAdd()"
{{ "ListAircrafts_Add" | translate }} >
</button> {{ "ListAircrafts_Add" | translate }}
</button>
<table mat-table [dataSource]="dataSourceTable"> }
<ng-container matColumnDef="id"> <table mat-table [dataSource]="dataSourceTable">
<th mat-header-cell *matHeaderCellDef> <ng-container matColumnDef="id">
{{ "ListAircrafts_Header_Id" | translate }} <th mat-header-cell *matHeaderCellDef>
</th> {{ "ListAircrafts_Header_Id" | translate }}
<td mat-cell *matCellDef="let element">{{ element.id }}</td> </th>
</ng-container> <td mat-cell *matCellDef="let element">{{ element.id }}</td>
</ng-container>
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> <th mat-header-cell *matHeaderCellDef>
{{ "ListAircrafts_Header_Name" | translate }} {{ "ListAircrafts_Header_Name" | translate }}
</th> </th>
<td mat-cell *matCellDef="let element">{{ element.name }}</td> <td mat-cell *matCellDef="let element">
</ng-container> {{ element.name }}
</td>
<ng-container matColumnDef="imageData"> </ng-container>
<th mat-header-cell *matHeaderCellDef> <ng-container matColumnDef="imageData">
{{ "ListAircrafts_Header_Image" | translate }} <th mat-header-cell *matHeaderCellDef>
</th> {{ "ListAircrafts_Header_Image" | translate }}
<td mat-cell *matCellDef="let element"> </th>
<img <td mat-cell *matCellDef="let element">
src="{{ element.imageData }}" <img
alt="No image" src="{{ element.imageData }}"
style="width: 128px" alt="No image"
/> style="width: 128px"
</td> />
</ng-container> </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> <tr
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> mat-header-row
</table> *matHeaderRowDef="displayedColumns; sticky: true"
</div> ></tr>
<mat-paginator [length]="resultsLength" [pageSize]="10"></mat-paginator> <tr
mat-row
<ng-template #loading> *matRowDef="let row; columns: displayedColumns"
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner> ></tr>
</ng-template> </table>
</div>
} @else {
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner>
}
<mat-paginator [length]="resultsLength" [pageSize]="10"></mat-paginator>
</div> </div>

View File

@@ -3,7 +3,7 @@ import { MatPaginator, MatPaginatorModule } from "@angular/material/paginator";
import { MatTableDataSource, MatTableModule } from "@angular/material/table"; import { MatTableDataSource, MatTableModule } from "@angular/material/table";
import { MatDialog } from "@angular/material/dialog"; import { MatDialog } from "@angular/material/dialog";
import { TranslateModule, TranslateService } from "@ngx-translate/core"; import { TranslateModule, TranslateService } from "@ngx-translate/core";
import { CommonModule } from "@angular/common";
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
import { MatButtonModule } from "@angular/material/button"; import { MatButtonModule } from "@angular/material/button";
@@ -15,71 +15,72 @@ import { AddAction } from "../../models/add-action.enum";
import { AircraftResp } from "../../models/aircraft"; import { AircraftResp } from "../../models/aircraft";
@Component({ @Component({
selector: "app-list-of-aircrafts", selector: "app-list-of-aircrafts",
templateUrl: "./list-of-aircrafts.component.html", templateUrl: "./list-of-aircrafts.component.html",
styleUrls: ["./list-of-aircrafts.component.css"], styleUrls: ["./list-of-aircrafts.component.css"],
imports: [ imports: [
TranslateModule, TranslateModule,
CommonModule, MatPaginatorModule,
MatPaginatorModule, MatProgressSpinnerModule,
MatProgressSpinnerModule, MatTableModule,
MatTableModule, MatButtonModule,
MatButtonModule, ],
],
}) })
export class ListOfAircraftsComponent implements OnInit { export class ListOfAircraftsComponent implements OnInit {
public displayedColumns: Array<string> = ["name", "imageData"]; public displayedColumns: Array<string> = ["name", "imageData"];
public dataSourceTable: MatTableDataSource<AircraftResp>; public dataSourceTable: MatTableDataSource<AircraftResp>;
public resultsLength = 0; public resultsLength = 0;
public isUserAdmin: boolean; public isUserAdmin: boolean;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
constructor( constructor(
private serviceApi: AircraftService, private serviceApi: AircraftService,
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
private authenticationService: AuthenticationService, private authenticationService: AuthenticationService,
public dialog: MatDialog, public dialog: MatDialog,
private translateService: TranslateService private translateService: TranslateService,
) { ) {
this.isUserAdmin = this.isUserAdmin =
this.authenticationService.currentUserValue.roles === "admin"; this.authenticationService.currentUserValue.roles === "admin";
} }
ngOnInit() {
this.serviceComm.refreshRequest.subscribe((action) => {
if (action === AddAction.Aircraft) {
this.dialog.closeAll();
this.getListOfAircrafts();
}
});
this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) {
this.updateTitle();
}
});
ngOnInit() {
this.serviceComm.refreshRequest.subscribe((action) => {
if (action === AddAction.Aircraft) {
this.dialog.closeAll();
this.getListOfAircrafts();
}
});
this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) {
this.updateTitle(); this.updateTitle();
} this.getListOfAircrafts();
}); }
this.updateTitle(); private getListOfAircrafts() {
this.getListOfAircrafts(); this.serviceApi.getListOfAircrafts().subscribe((data) => {
} setTimeout(() => {
data.sort((a, b) => a.name.localeCompare(b.name));
this.dataSourceTable = new MatTableDataSource<AircraftResp>(
data,
);
this.dataSourceTable.paginator = this.paginator;
this.resultsLength = data.length;
}, 500);
});
}
private getListOfAircrafts() { openDialogToAdd() {
this.serviceApi.getListOfAircrafts().subscribe((data) => { this.dialog.open(NewAircraftComponent);
setTimeout(() => { }
data.sort((a, b) => a.name.localeCompare(b.name));
this.dataSourceTable = new MatTableDataSource<AircraftResp>(data);
this.dataSourceTable.paginator = this.paginator;
this.resultsLength = data.length;
}, 500);
});
}
openDialogToAdd() { private updateTitle() {
this.dialog.open(NewAircraftComponent); this.translateService.get("ListAircrafts_Title").subscribe((data) => {
} this.serviceComm.updatedComponentTitle(data);
});
private updateTitle() { }
this.translateService.get("ListAircrafts_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
} }

View File

@@ -1,22 +1,22 @@
table { table {
width: 100%; width: 100%;
} }
.mat-row td { .mat-row td {
padding: 15px; padding: 15px;
} }
.spanWithBreakWord { .spanWithBreakWord {
display: inline-block; display: inline-block;
min-width: 200px; min-width: 200px;
word-wrap: break-word; word-wrap: break-word;
} }
.content { .content {
min-height: 90vh; min-height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
padding-top: 25px; padding-top: 25px;
} }

View File

@@ -1,118 +1,138 @@
<div class="content"> <div class="content">
<div *ngIf="dataSourceTable != null; else loading"> @if (dataSourceTable != null) {
<button <div>
mat-raised-button @if (isUserAdmin == true) {
color="accent" <button
(click)="openDialogToAdd()" mat-raised-button
*ngIf="isUserAdmin == true" color="accent"
> (click)="openDialogToAdd()"
{{ "ListDz_Add" | translate }} >
</button> {{ "ListDz_Add" | translate }}
</button>
<mat-form-field> }
<mat-label>{{ "ListDz_Filter" | translate }}</mat-label> <mat-form-field>
<input <mat-label>{{ "ListDz_Filter" | translate }}</mat-label>
matInput <input
(keyup)="applyFilter($event)" matInput
placeholder="{{ 'ListDz_Filter_PlaceHolder' | translate }}" (keyup)="applyFilter($event)"
#input placeholder="{{ 'ListDz_Filter_PlaceHolder' | translate }}"
/> #input
</mat-form-field> />
</mat-form-field>
<table mat-table [dataSource]="dataSourceTable"> <table mat-table [dataSource]="dataSourceTable">
<ng-container matColumnDef="isfavorite"> <ng-container matColumnDef="isfavorite">
<th mat-header-cell *matHeaderCellDef style="min-width: 144px"></th> <th
<td mat-cell *matCellDef="let element" style="text-align: left"> mat-header-cell
<mat-icon *matHeaderCellDef
aria-hidden="false" style="min-width: 144px"
aria-label="Favorite" ></th>
*ngIf="element.isFavorite === true" <td
(click)="removeToFavorite(element)" mat-cell
color="primary" *matCellDef="let element"
style="cursor: pointer" style="text-align: left"
svgIcon="favorite" >
></mat-icon> @if (element.isFavorite === true) {
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="Not favorite" aria-label="Favorite"
*ngIf="element.isFavorite === false" (click)="removeToFavorite(element)"
(click)="setToFavorite(element)" color="primary"
style="cursor: pointer" style="cursor: pointer"
svgIcon="not_favorite" svgIcon="favorite"
></mat-icon> ></mat-icon>
<a href="http://{{ element.website }}" target="_blank"> }
<mat-icon @if (element.isFavorite === false) {
aria-hidden="false" <mat-icon
aria-label="URL to the DZ website" aria-hidden="false"
style="color: white" aria-label="Not favorite"
svgIcon="link" (click)="setToFavorite(element)"
></mat-icon> style="cursor: pointer"
</a> svgIcon="not_favorite"
<a ></mat-icon>
href="https://www.openstreetmap.org/?mlat={{ }
element.latitude <a href="http://{{ element.website }}" target="_blank">
}}&mlon={{ element.longitude }}#map=14/{{ element.latitude }}/{{ <mat-icon
element.longitude aria-hidden="false"
}}" aria-label="URL to the DZ website"
target="_blank" style="color: white"
> svgIcon="link"
<mat-icon ></mat-icon>
aria-hidden="false" </a>
aria-label="Location of the DZ" <a
style="color: white" href="https://www.openstreetmap.org/?mlat={{
svgIcon="map" element.latitude
></mat-icon> }}&mlon={{ element.longitude }}#map=14/{{
</a> element.latitude
<a href="mailto:{{ element.email }}" *ngIf="element.email"> }}/{{ element.longitude }}"
<mat-icon target="_blank"
aria-hidden="false" >
aria-label="Contact mail of the DZ" <mat-icon
style="color: white" aria-hidden="false"
svgIcon="mail" aria-label="Location of the DZ"
></mat-icon> style="color: white"
</a> svgIcon="map"
</td> ></mat-icon>
</ng-container> </a>
@if (element.email) {
<ng-container matColumnDef="id"> <a href="mailto:{{ element.email }}">
<th mat-header-cell *matHeaderCellDef> <mat-icon
{{ "ListDz_Header_ID" | translate }} aria-hidden="false"
</th> aria-label="Contact mail of the DZ"
<td mat-cell *matCellDef="let element">{{ element.id }}</td> style="color: white"
</ng-container> svgIcon="mail"
></mat-icon>
<ng-container matColumnDef="name"> </a>
<th mat-header-cell *matHeaderCellDef> }
{{ "ListDz_Header_Name" | translate }} </td>
</th> </ng-container>
<td mat-cell *matCellDef="let element"> <ng-container matColumnDef="id">
<span class="spanWithBreakWord" [innerHTML]="element.name"></span> <th mat-header-cell *matHeaderCellDef>
</td> {{ "ListDz_Header_ID" | translate }}
</ng-container> </th>
<td mat-cell *matCellDef="let element">{{ element.id }}</td>
<ng-container matColumnDef="address"> </ng-container>
<th mat-header-cell *matHeaderCellDef> <ng-container matColumnDef="name">
{{ "ListDz_Header_Address" | translate }} <th mat-header-cell *matHeaderCellDef>
</th> {{ "ListDz_Header_Name" | translate }}
<td mat-cell *matCellDef="let element"> </th>
<span class="spanWithBreakWord" [innerHTML]="element.address"></span> <td mat-cell *matCellDef="let element">
</td> <span
</ng-container> class="spanWithBreakWord"
[innerHTML]="element.name"
<ng-container matColumnDef="type"> ></span>
<th mat-header-cell *matHeaderCellDef> </td>
{{ "ListDz_Header_Type" | translate }} </ng-container>
</th> <ng-container matColumnDef="address">
<td mat-cell *matCellDef="let element">{{ element.type }}</td> <th mat-header-cell *matHeaderCellDef>
</ng-container> {{ "ListDz_Header_Address" | translate }}
</th>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> <td mat-cell *matCellDef="let element">
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> <span
</table> class="spanWithBreakWord"
</div> [innerHTML]="element.address"
<mat-paginator [length]="resultsLength" [pageSize]="20"></mat-paginator> ></span>
</td>
<ng-template #loading> </ng-container>
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner> <ng-container matColumnDef="type">
</ng-template> <th mat-header-cell *matHeaderCellDef>
{{ "ListDz_Header_Type" | translate }}
</th>
<td mat-cell *matCellDef="let element">
{{ element.type }}
</td>
</ng-container>
<tr
mat-header-row
*matHeaderRowDef="displayedColumns; sticky: true"
></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
></tr>
</table>
</div>
} @else {
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner>
}
<mat-paginator [length]="resultsLength" [pageSize]="20"></mat-paginator>
</div> </div>

View File

@@ -3,7 +3,7 @@ import { MatPaginator, MatPaginatorModule } from "@angular/material/paginator";
import { MatTableDataSource, MatTableModule } from "@angular/material/table"; import { MatTableDataSource, MatTableModule } from "@angular/material/table";
import { MatDialog } from "@angular/material/dialog"; import { MatDialog } from "@angular/material/dialog";
import { TranslateModule, TranslateService } from "@ngx-translate/core"; import { TranslateModule, TranslateService } from "@ngx-translate/core";
import { CommonModule } from "@angular/common";
import { MatIconModule } from "@angular/material/icon"; import { MatIconModule } from "@angular/material/icon";
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
import { MatFormFieldModule } from "@angular/material/form-field"; import { MatFormFieldModule } from "@angular/material/form-field";
@@ -19,112 +19,113 @@ import { AuthenticationService } from "../../services/authentication.service";
import { NewDropZoneComponent } from "../new-drop-zone/new-drop-zone.component"; import { NewDropZoneComponent } from "../new-drop-zone/new-drop-zone.component";
@Component({ @Component({
selector: "app-list-of-dzs", selector: "app-list-of-dzs",
templateUrl: "./list-of-dzs.component.html", templateUrl: "./list-of-dzs.component.html",
styleUrls: ["./list-of-dzs.component.css"], styleUrls: ["./list-of-dzs.component.css"],
imports: [ imports: [
TranslateModule, TranslateModule,
CommonModule, MatIconModule,
MatIconModule, MatPaginatorModule,
MatPaginatorModule, MatProgressSpinnerModule,
MatProgressSpinnerModule, MatTableModule,
MatTableModule, MatFormFieldModule,
MatFormFieldModule, ReactiveFormsModule,
ReactiveFormsModule, MatInputModule,
MatInputModule, MatButtonModule,
MatButtonModule, ],
],
}) })
export class ListOfDzsComponent implements OnInit { export class ListOfDzsComponent implements OnInit {
public displayedColumns: Array<string> = [ public displayedColumns: Array<string> = [
"isfavorite", "isfavorite",
"name", "name",
"address", "address",
"type", "type",
]; ];
public dataSourceTable: MatTableDataSource<DropZoneResp>; public dataSourceTable: MatTableDataSource<DropZoneResp>;
public isUserAdmin: boolean; public isUserAdmin: boolean;
public resultsLength = 0; public resultsLength = 0;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
constructor( constructor(
private serviceApi: DropzoneService, private serviceApi: DropzoneService,
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
private authenticationService: AuthenticationService, private authenticationService: AuthenticationService,
public dialog: MatDialog, public dialog: MatDialog,
private translateService: TranslateService private translateService: TranslateService,
) { ) {
this.isUserAdmin = this.isUserAdmin =
this.authenticationService.currentUserValue.roles === "admin"; this.authenticationService.currentUserValue.roles === "admin";
} }
ngOnInit() {
this.serviceComm.refreshRequest.subscribe((action) => {
if (action === AddAction.Dropzone) {
this.dialog.closeAll();
this.getListOfDropZones();
}
});
this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) {
this.updateTitle();
}
});
ngOnInit() {
this.serviceComm.refreshRequest.subscribe((action) => {
if (action === AddAction.Dropzone) {
this.dialog.closeAll();
this.getListOfDropZones();
}
});
this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) {
this.updateTitle(); this.updateTitle();
} this.getListOfDropZones();
}); }
this.updateTitle(); private getListOfDropZones() {
this.getListOfDropZones(); this.serviceApi.getListOfDropZones().subscribe((data) => {
} setTimeout(() => {
data.sort(
(a, b) =>
(b.isFavorite ? 1 : 0) - (a.isFavorite ? 1 : 0) ||
a.name.localeCompare(b.name),
);
this.dataSourceTable = new MatTableDataSource<DropZoneResp>(
data,
);
this.dataSourceTable.paginator = this.paginator;
this.resultsLength = data.length;
}, 500);
});
}
private getListOfDropZones() { public setToFavorite(dropzone: DropZoneResp) {
this.serviceApi.getListOfDropZones().subscribe((data) => { dropzone.isFavorite = true;
setTimeout(() => { this.serviceApi.setFavoriteDropZone(dropzone);
data.sort(
(a, b) => const data = this.dataSourceTable.data;
(b.isFavorite ? 1 : 0) - (a.isFavorite ? 1 : 0) || data.sort((a, b) => (b.isFavorite ? 1 : 0) - (a.isFavorite ? 1 : 0));
a.name.localeCompare(b.name)
);
this.dataSourceTable = new MatTableDataSource<DropZoneResp>(data); this.dataSourceTable = new MatTableDataSource<DropZoneResp>(data);
this.dataSourceTable.paginator = this.paginator; this.dataSourceTable.paginator = this.paginator;
this.resultsLength = data.length; }
}, 500);
});
}
public setToFavorite(dropzone: DropZoneResp) { public removeToFavorite(dropzone: DropZoneResp) {
dropzone.isFavorite = true; dropzone.isFavorite = false;
this.serviceApi.setFavoriteDropZone(dropzone); this.serviceApi.removeFavoriteDropZone(dropzone);
const data = this.dataSourceTable.data; const data = this.dataSourceTable.data;
data.sort((a, b) => (b.isFavorite ? 1 : 0) - (a.isFavorite ? 1 : 0)); data.sort((a, b) => (b.isFavorite ? 1 : 0) - (a.isFavorite ? 1 : 0));
this.dataSourceTable = new MatTableDataSource<DropZoneResp>(data); this.dataSourceTable = new MatTableDataSource<DropZoneResp>(data);
this.dataSourceTable.paginator = this.paginator; this.dataSourceTable.paginator = this.paginator;
} }
public removeToFavorite(dropzone: DropZoneResp) { openDialogToAdd() {
dropzone.isFavorite = false; this.dialog.open(NewDropZoneComponent, {
this.serviceApi.removeFavoriteDropZone(dropzone); height: "400px",
width: "600px",
});
}
const data = this.dataSourceTable.data; applyFilter(event: Event) {
data.sort((a, b) => (b.isFavorite ? 1 : 0) - (a.isFavorite ? 1 : 0)); const filterValue = (event.target as HTMLInputElement).value;
this.dataSourceTable = new MatTableDataSource<DropZoneResp>(data); this.dataSourceTable.filter = filterValue.trim().toLowerCase();
this.dataSourceTable.paginator = this.paginator; }
}
openDialogToAdd() { private updateTitle() {
this.dialog.open(NewDropZoneComponent, { this.translateService.get("ListDz_Title").subscribe((data) => {
height: "400px", this.serviceComm.updatedComponentTitle(data);
width: "600px", });
}); }
}
applyFilter(event: Event) {
const filterValue = (event.target as HTMLInputElement).value;
this.dataSourceTable.filter = filterValue.trim().toLowerCase();
}
private updateTitle() {
this.translateService.get("ListDz_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
} }

View File

@@ -1,26 +1,26 @@
table { table {
width: 100%; width: 100%;
} }
.mat-row td { .mat-row td {
padding: 15px; padding: 15px;
} }
.spanWithBreakWord { .spanWithBreakWord {
display: inline-block; display: inline-block;
min-width: 200px; min-width: 200px;
word-wrap: break-word; word-wrap: break-word;
} }
.content { .content {
min-height: 90vh; min-height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
padding-top: 25px; padding-top: 25px;
} }
th.mat-header-cell { th.mat-header-cell {
text-align: center; text-align: center;
} }

View File

@@ -1,68 +1,88 @@
<div class="content"> <div class="content">
<div *ngIf="dataSourceTable != null; else loading"> @if (dataSourceTable != null) {
<button mat-raised-button color="accent" (click)="openDialogToAdd()"> <div>
{{ "ListGears_Add" | translate }} <button
</button> mat-raised-button
color="accent"
<table mat-table [dataSource]="dataSourceTable"> (click)="openDialogToAdd()"
<ng-container matColumnDef="id"> >
<th mat-header-cell *matHeaderCellDef> {{ "ListGears_Add" | translate }}
{{ "ListGears_Header_Id" | translate }} </button>
</th> <table mat-table [dataSource]="dataSourceTable">
<td mat-cell *matCellDef="let element">{{ element.id }}</td> <ng-container matColumnDef="id">
</ng-container> <th mat-header-cell *matHeaderCellDef>
{{ "ListGears_Header_Id" | translate }}
<ng-container matColumnDef="name"> </th>
<th mat-header-cell *matHeaderCellDef style="min-width: 130px"> <td mat-cell *matCellDef="let element">{{ element.id }}</td>
{{ "ListGears_Header_Name" | translate }} </ng-container>
</th> <ng-container matColumnDef="name">
<td mat-cell *matCellDef="let element">{{ element.name }}</td> <th
</ng-container> mat-header-cell
*matHeaderCellDef
<ng-container matColumnDef="manufacturer"> style="min-width: 130px"
<th mat-header-cell *matHeaderCellDef> >
{{ "ListGears_Header_Manufacturer" | translate }} {{ "ListGears_Header_Name" | translate }}
</th> </th>
<td mat-cell *matCellDef="let element">{{ element.manufacturer }}</td> <td mat-cell *matCellDef="let element">
</ng-container> {{ element.name }}
</td>
<ng-container matColumnDef="maxSize"> </ng-container>
<th mat-header-cell *matHeaderCellDef style="min-width: 90px"> <ng-container matColumnDef="manufacturer">
{{ "ListGears_Header_CanopySize" | translate }} <th mat-header-cell *matHeaderCellDef>
</th> {{ "ListGears_Header_Manufacturer" | translate }}
<td mat-cell *matCellDef="let element"> </th>
{{ element.minSize }} - {{ element.maxSize }} <td mat-cell *matCellDef="let element">
</td> {{ element.manufacturer }}
</ng-container> </td>
</ng-container>
<ng-container matColumnDef="aad"> <ng-container matColumnDef="maxSize">
<th mat-header-cell *matHeaderCellDef> <th
{{ "ListGears_Header_Aad" | translate }} mat-header-cell
</th> *matHeaderCellDef
<td mat-cell *matCellDef="let element">{{ element.aad }}</td> style="min-width: 90px"
</ng-container> >
{{ "ListGears_Header_CanopySize" | translate }}
<ng-container matColumnDef="mainCanopy"> </th>
<th mat-header-cell *matHeaderCellDef> <td mat-cell *matCellDef="let element">
{{ "ListGears_Header_Main" | translate }} {{ element.minSize }} - {{ element.maxSize }}
</th> </td>
<td mat-cell *matCellDef="let element">{{ element.mainCanopy }}</td> </ng-container>
</ng-container> <ng-container matColumnDef="aad">
<th mat-header-cell *matHeaderCellDef>
<ng-container matColumnDef="reserveCanopy"> {{ "ListGears_Header_Aad" | translate }}
<th mat-header-cell *matHeaderCellDef> </th>
{{ "ListGears_Header_Reserve" | translate }} <td mat-cell *matCellDef="let element">
</th> {{ element.aad }}
<td mat-cell *matCellDef="let element">{{ element.reserveCanopy }}</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="mainCanopy">
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> <th mat-header-cell *matHeaderCellDef>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> {{ "ListGears_Header_Main" | translate }}
</table> </th>
</div> <td mat-cell *matCellDef="let element">
<mat-paginator [length]="resultsLength" [pageSize]="10"></mat-paginator> {{ element.mainCanopy }}
</td>
<ng-template #loading> </ng-container>
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner> <ng-container matColumnDef="reserveCanopy">
</ng-template> <th mat-header-cell *matHeaderCellDef>
{{ "ListGears_Header_Reserve" | translate }}
</th>
<td mat-cell *matCellDef="let element">
{{ element.reserveCanopy }}
</td>
</ng-container>
<tr
mat-header-row
*matHeaderRowDef="displayedColumns; sticky: true"
></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
></tr>
</table>
</div>
} @else {
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner>
}
<mat-paginator [length]="resultsLength" [pageSize]="10"></mat-paginator>
</div> </div>

View File

@@ -3,7 +3,7 @@ import { MatPaginator, MatPaginatorModule } from "@angular/material/paginator";
import { MatTableDataSource, MatTableModule } from "@angular/material/table"; import { MatTableDataSource, MatTableModule } from "@angular/material/table";
import { MatDialog } from "@angular/material/dialog"; import { MatDialog } from "@angular/material/dialog";
import { TranslateModule, TranslateService } from "@ngx-translate/core"; import { TranslateModule, TranslateService } from "@ngx-translate/core";
import { CommonModule } from "@angular/common";
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
import { MatButtonModule } from "@angular/material/button"; import { MatButtonModule } from "@angular/material/button";
@@ -14,76 +14,75 @@ import { AddAction } from "../../models/add-action.enum";
import { NewGearComponent } from "../new-gear/new-gear.component"; import { NewGearComponent } from "../new-gear/new-gear.component";
@Component({ @Component({
selector: "app-list-of-gears", selector: "app-list-of-gears",
templateUrl: "./list-of-gears.component.html", templateUrl: "./list-of-gears.component.html",
styleUrls: ["./list-of-gears.component.css"], styleUrls: ["./list-of-gears.component.css"],
imports: [ imports: [
TranslateModule, TranslateModule,
CommonModule, MatPaginatorModule,
MatPaginatorModule, MatProgressSpinnerModule,
MatProgressSpinnerModule, MatTableModule,
MatTableModule, MatButtonModule,
MatButtonModule, ],
],
}) })
export class ListOfGearsComponent implements OnInit { export class ListOfGearsComponent implements OnInit {
public displayedColumns: Array<string> = [ public displayedColumns: Array<string> = [
"name", "name",
"manufacturer", "manufacturer",
"maxSize", "maxSize",
"aad", "aad",
"mainCanopy", "mainCanopy",
"reserveCanopy", "reserveCanopy",
]; ];
public dataSourceTable: MatTableDataSource<GearResp>; public dataSourceTable: MatTableDataSource<GearResp>;
public resultsLength = 0; public resultsLength = 0;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
constructor( constructor(
private serviceApi: GearService, private serviceApi: GearService,
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
public dialog: MatDialog, public dialog: MatDialog,
private translateService: TranslateService private translateService: TranslateService,
) {} ) {}
ngOnInit() {
this.serviceComm.refreshRequest.subscribe((action) => {
if (action === AddAction.Gear) {
this.dialog.closeAll();
this.getListOfGears();
}
});
this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) {
this.updateTitle();
}
});
ngOnInit() {
this.serviceComm.refreshRequest.subscribe((action) => {
if (action === AddAction.Gear) {
this.dialog.closeAll();
this.getListOfGears();
}
});
this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) {
this.updateTitle(); this.updateTitle();
} this.getListOfGears();
}); }
this.updateTitle(); getListOfGears() {
this.getListOfGears(); this.serviceApi.getListOfGears().subscribe((data) => {
} setTimeout(() => {
data.sort((a, b) => b.id - a.id);
this.dataSourceTable = new MatTableDataSource<GearResp>(data);
this.dataSourceTable.paginator = this.paginator;
this.resultsLength = data.length;
}, 500);
});
}
getListOfGears() { openDialogToAdd() {
this.serviceApi.getListOfGears().subscribe((data) => { this.dialog.open(NewGearComponent, {
setTimeout(() => { height: "400px",
data.sort((a, b) => b.id - a.id); width: "600px",
this.dataSourceTable = new MatTableDataSource<GearResp>(data); });
this.dataSourceTable.paginator = this.paginator; }
this.resultsLength = data.length;
}, 500);
});
}
openDialogToAdd() { private updateTitle() {
this.dialog.open(NewGearComponent, { this.translateService.get("ListGears_Title").subscribe((data) => {
height: "400px", this.serviceComm.updatedComponentTitle(data);
width: "600px", });
}); }
}
private updateTitle() {
this.translateService.get("ListGears_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
} }

View File

@@ -1,45 +1,45 @@
.imgmodal { .imgmodal {
display: none; display: none;
position: fixed; position: fixed;
z-index: 1000; z-index: 1000;
padding-top: 10px; padding-top: 10px;
left: 0; left: 0;
top: 0; top: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
background-color: #000000; background-color: #000000;
} }
.imgbox { .imgbox {
display: grid; display: grid;
height: 100%; height: 100%;
} }
.center-fit { .center-fit {
max-width: 100%; max-width: 100%;
max-height: 100vh; max-height: 100vh;
margin: auto; margin: auto;
} }
.close { .close {
color: white; color: white;
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 25px; right: 25px;
font-size: 35px; font-size: 35px;
font-weight: bold; font-weight: bold;
} }
.rotate { .rotate {
color: white; color: white;
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 65px; right: 65px;
font-size: 35px; font-size: 35px;
font-weight: bold; font-weight: bold;
} }
.cursor { .cursor {
cursor: pointer; cursor: pointer;
} }

View File

@@ -1,87 +1,103 @@
<div> <div>
<form <form
[formGroup]="imgForm" [formGroup]="imgForm"
(ngSubmit)="onSubmit(imgForm.value)" (ngSubmit)="onSubmit(imgForm.value)"
autocomplete="off" autocomplete="off"
style="padding: 10px" style="padding: 10px"
> >
<p> <p>
<input <input
type="file" type="file"
#fileUpload #fileUpload
id="fileUpload" id="fileUpload"
name="fileUpload" name="fileUpload"
accept="image/*" accept="image/*"
formControlName="image" formControlName="image"
(change)="onFileChanged($event)" (change)="onFileChanged($event)"
/> />
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Comment about the image</mat-label> <mat-label>Comment about the image</mat-label>
<input matInput type="text" formControlName="comment" /> <input matInput type="text" formControlName="comment" />
</mat-form-field> </mat-form-field>
</p> </p>
<button mat-icon-button color="warn" type="submit"> <button mat-icon-button color="warn" type="submit">
<mat-icon svgIcon="file_upload"></mat-icon> <mat-icon svgIcon="file_upload"></mat-icon>
Upload image Upload image
</button> </button>
<label>{{ imageError }}</label> <label>{{ imageError }}</label>
</form> </form>
</div> </div>
<div *ngIf="resultsLength > 0"> @if (resultsLength > 0) {
<table mat-table [dataSource]="dataSourceTable"> <div>
<ng-container matColumnDef="comment"> <table mat-table [dataSource]="dataSourceTable">
<th mat-header-cell *matHeaderCellDef style="text-align: center"> <ng-container matColumnDef="comment">
Comments <th
</th> mat-header-cell
<td mat-cell *matCellDef="let element" style="text-align: left"> *matHeaderCellDef
<span style="white-space: nowrap">{{ element.comment }}</span> style="text-align: center"
</td> >
</ng-container> Comments
</th>
<ng-container matColumnDef="data"> <td mat-cell *matCellDef="let element" style="text-align: left">
<th mat-header-cell *matHeaderCellDef style="text-align: center"> <span style="white-space: nowrap">{{
Image element.comment
</th> }}</span>
<td mat-cell *matCellDef="let element" style="text-align: center"> </td>
<img </ng-container>
src="{{ element.data }}" <ng-container matColumnDef="data">
alt="image" <th
style="width: 50%" mat-header-cell
(click)="openModal(element)" *matHeaderCellDef
class="cursor" style="text-align: center"
/> >
</td> Image
</ng-container> </th>
<td
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> mat-cell
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> *matCellDef="let element"
</table> style="text-align: center"
>
<mat-paginator [length]="resultsLength" [pageSize]="10"></mat-paginator> <img
</div> src="{{ element.data }}"
alt="image"
style="width: 50%"
(click)="openModal(element)"
class="cursor"
/>
</td>
</ng-container>
<tr
mat-header-row
*matHeaderRowDef="displayedColumns; sticky: true"
></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<mat-paginator [length]="resultsLength" [pageSize]="10"></mat-paginator>
</div>
}
<div <div
class="imgmodal" class="imgmodal"
[ngStyle]="{ display: showPopin === true ? 'block' : 'none' }" [ngStyle]="{ display: showPopin === true ? 'block' : 'none' }"
> >
<span class="close cursor" (click)="closeModal()">&times;</span> <span class="close cursor" (click)="closeModal()">&times;</span>
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="Rotation" aria-label="Rotation"
(click)="rotate()" (click)="rotate()"
class="rotate cursor" class="rotate cursor"
svgIcon="rotate" svgIcon="rotate"
></mat-icon> ></mat-icon>
<div class="imgbox"> <div class="imgbox">
<img <img
class="center-fit cursor" class="center-fit cursor"
src="{{ popinImage }}" src="{{ popinImage }}"
(click)="closeModal()" (click)="closeModal()"
[@rotatedState]="stateRotation" [@rotatedState]="stateRotation"
/> />
</div> </div>
</div> </div>

View File

@@ -1,9 +1,9 @@
import { Component, OnInit, ViewChild } from "@angular/core"; import { Component, OnInit, ViewChild } from "@angular/core";
import { import {
FormGroup, FormGroup,
FormControl, FormControl,
Validators, Validators,
ReactiveFormsModule, ReactiveFormsModule,
} from "@angular/forms"; } from "@angular/forms";
import { MatTableDataSource, MatTableModule } from "@angular/material/table"; import { MatTableDataSource, MatTableModule } from "@angular/material/table";
import { MatPaginator, MatPaginatorModule } from "@angular/material/paginator"; import { MatPaginator, MatPaginatorModule } from "@angular/material/paginator";
@@ -21,139 +21,144 @@ import { ImageResp } from "../../models/image";
import { AddAction } from "../../models/add-action.enum"; import { AddAction } from "../../models/add-action.enum";
@Component({ @Component({
selector: "app-list-of-images", selector: "app-list-of-images",
templateUrl: "./list-of-images.component.html", templateUrl: "./list-of-images.component.html",
styleUrls: ["./list-of-images.component.css"], styleUrls: ["./list-of-images.component.css"],
animations: [ animations: [
trigger("rotatedState", [ trigger("rotatedState", [
state("default", style({ transform: "rotate(0)" })), state("default", style({ transform: "rotate(0)" })),
state("rot90", style({ transform: "rotate(-90deg)" })), state("rot90", style({ transform: "rotate(-90deg)" })),
state("rot180", style({ transform: "rotate(-180deg)" })), state("rot180", style({ transform: "rotate(-180deg)" })),
state("rot270", style({ transform: "rotate(-270deg)" })), state("rot270", style({ transform: "rotate(-270deg)" })),
]), ]),
], ],
imports: [ imports: [
TranslateModule, TranslateModule,
CommonModule, CommonModule,
MatIconModule, MatIconModule,
MatPaginatorModule, MatPaginatorModule,
MatFormFieldModule, MatFormFieldModule,
ReactiveFormsModule, ReactiveFormsModule,
MatTableModule, MatTableModule,
MatButtonModule, MatButtonModule,
MatInputModule, MatInputModule,
], ],
}) })
export class ListOfImagesComponent implements OnInit { export class ListOfImagesComponent implements OnInit {
public displayedColumns: Array<string> = ["comment", "data"]; public displayedColumns: Array<string> = ["comment", "data"];
public imgForm: FormGroup; public imgForm: FormGroup;
public imageError: string; public imageError: string;
private selectedFile: string; private selectedFile: string;
public popinImage: string; public popinImage: string;
public showPopin: boolean; public showPopin: boolean;
public dataSourceTable: MatTableDataSource<ImageResp>; public dataSourceTable: MatTableDataSource<ImageResp>;
public resultsLength = 0; public resultsLength = 0;
public stateRotation: string = "default"; public stateRotation: string = "default";
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
constructor( constructor(
private serviceApi: ImageService, private serviceApi: ImageService,
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
this.serviceComm.refreshRequest.subscribe((action) => { this.serviceComm.refreshRequest.subscribe((action) => {
if (action === AddAction.Gear) { if (action === AddAction.Gear) {
this.getListOfImages();
}
});
this.getListOfImages(); this.getListOfImages();
}
});
this.getListOfImages();
this.imgForm = new FormGroup({ this.imgForm = new FormGroup({
comment: new FormControl("", Validators.required), comment: new FormControl("", Validators.required),
image: new FormControl("", Validators.required), image: new FormControl("", Validators.required),
}); });
}
private getListOfImages() {
this.serviceApi.getListOfImages().subscribe((data) => {
setTimeout(() => {
this.dataSourceTable = new MatTableDataSource<ImageResp>(data);
this.dataSourceTable.paginator = this.paginator;
this.resultsLength = data.length;
}, 500);
});
}
public onFileChanged(fileInput: any) {
const file = fileInput.dataTransfer
? fileInput.dataTransfer.files[0]
: fileInput.target.files[0];
const allowed_types = ["image/png", "image/jpeg"];
const max_size = 20971520;
if (!allowed_types.includes(file.type)) {
this.imageError = "Only Images are allowed ( JPG | PNG )";
} else if (file.size > max_size) {
this.imageError = "Maximum size allowed is " + max_size / 1000 + "Mb";
} else {
const reader = new FileReader();
reader.onload = this.checkAndExtractDataToBase64.bind(this);
reader.readAsDataURL(fileInput.target.files[0]);
}
}
private checkAndExtractDataToBase64(e: any) {
const max_height = 15200;
const max_width = 25600;
const image = new Image();
image.src = e.target.result;
image.onload = (rs) => {
const img_height = rs.currentTarget["height"];
const img_width = rs.currentTarget["width"];
if (img_height > max_height && img_width > max_width) {
this.imageError =
"Maximum dimentions allowed " + max_height + "*" + max_width + "px";
} else {
const imgBase64Path = e.target.result;
this.selectedFile = imgBase64Path;
this.imageError = "OK";
}
};
}
onSubmit(formData) {
if (formData.invalid) {
return;
} }
this.serviceApi private getListOfImages() {
.addImage(formData.comment, this.selectedFile) this.serviceApi.getListOfImages().subscribe((data) => {
.subscribe(() => { setTimeout(() => {
this.getListOfImages(); this.dataSourceTable = new MatTableDataSource<ImageResp>(data);
}); this.dataSourceTable.paginator = this.paginator;
} this.resultsLength = data.length;
}, 500);
openModal(image: ImageResp) { });
this.popinImage = image.data; }
this.showPopin = true;
} public onFileChanged(fileInput: any) {
const file = fileInput.dataTransfer
closeModal() { ? fileInput.dataTransfer.files[0]
this.showPopin = false; : fileInput.target.files[0];
} const allowed_types = ["image/png", "image/jpeg"];
const max_size = 20971520;
rotate() {
if (this.stateRotation === "default") { if (!allowed_types.includes(file.type)) {
this.stateRotation = "rot90"; this.imageError = "Only Images are allowed ( JPG | PNG )";
} else if (this.stateRotation === "rot90") { } else if (file.size > max_size) {
this.stateRotation = "rot180"; this.imageError =
} else if (this.stateRotation === "rot180") { "Maximum size allowed is " + max_size / 1000 + "Mb";
this.stateRotation = "rot270"; } else {
} else { const reader = new FileReader();
this.stateRotation = "default"; reader.onload = this.checkAndExtractDataToBase64.bind(this);
reader.readAsDataURL(fileInput.target.files[0]);
}
}
private checkAndExtractDataToBase64(e: any) {
const max_height = 15200;
const max_width = 25600;
const image = new Image();
image.src = e.target.result;
image.onload = (rs) => {
const img_height = rs.currentTarget["height"];
const img_width = rs.currentTarget["width"];
if (img_height > max_height && img_width > max_width) {
this.imageError =
"Maximum dimentions allowed " +
max_height +
"*" +
max_width +
"px";
} else {
const imgBase64Path = e.target.result;
this.selectedFile = imgBase64Path;
this.imageError = "OK";
}
};
}
onSubmit(formData) {
if (formData.invalid) {
return;
}
this.serviceApi
.addImage(formData.comment, this.selectedFile)
.subscribe(() => {
this.getListOfImages();
});
}
openModal(image: ImageResp) {
this.popinImage = image.data;
this.showPopin = true;
}
closeModal() {
this.showPopin = false;
}
rotate() {
if (this.stateRotation === "default") {
this.stateRotation = "rot90";
} else if (this.stateRotation === "rot90") {
this.stateRotation = "rot180";
} else if (this.stateRotation === "rot180") {
this.stateRotation = "rot270";
} else {
this.stateRotation = "default";
}
} }
}
} }

View File

@@ -1,22 +1,22 @@
table { table {
width: 100%; width: 100%;
} }
.mat-row td { .mat-row td {
padding: 15px; padding: 15px;
} }
.spanWithBreakWord { .spanWithBreakWord {
display: inline-block; display: inline-block;
min-width: 200px; min-width: 200px;
word-wrap: break-word; word-wrap: break-word;
} }
.content { .content {
min-height: 90vh; min-height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
padding-top: 25px; padding-top: 25px;
} }

View File

@@ -1,36 +1,42 @@
<div class="content"> <div class="content">
<div *ngIf="dataSourceTable != null; else loading"> @if (dataSourceTable != null) {
<button <div>
mat-raised-button @if (isUserAdmin == true) {
color="accent" <button
(click)="openDialogToAdd()" mat-raised-button
*ngIf="isUserAdmin == true" color="accent"
> (click)="openDialogToAdd()"
{{ "ListJumpType_Add" | translate }} >
</button> {{ "ListJumpType_Add" | translate }}
</button>
<table mat-table [dataSource]="dataSourceTable"> }
<ng-container matColumnDef="id"> <table mat-table [dataSource]="dataSourceTable">
<th mat-header-cell *matHeaderCellDef> <ng-container matColumnDef="id">
{{ "ListJumpType_Header_Id" | translate }} <th mat-header-cell *matHeaderCellDef>
</th> {{ "ListJumpType_Header_Id" | translate }}
<td mat-cell *matCellDef="let element">{{ element.id }}</td> </th>
</ng-container> <td mat-cell *matCellDef="let element">{{ element.id }}</td>
</ng-container>
<ng-container matColumnDef="name"> <ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> <th mat-header-cell *matHeaderCellDef>
{{ "ListJumpType_Header_Name" | translate }} {{ "ListJumpType_Header_Name" | translate }}
</th> </th>
<td mat-cell *matCellDef="let element">{{ element.name }}</td> <td mat-cell *matCellDef="let element">
</ng-container> {{ element.name }}
</td>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> </ng-container>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> <tr
</table> mat-header-row
</div> *matHeaderRowDef="displayedColumns; sticky: true"
<mat-paginator [length]="resultsLength" [pageSize]="20"></mat-paginator> ></tr>
<tr
<ng-template #loading> mat-row
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner> *matRowDef="let row; columns: displayedColumns"
</ng-template> ></tr>
</table>
</div>
} @else {
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner>
}
<mat-paginator [length]="resultsLength" [pageSize]="20"></mat-paginator>
</div> </div>

View File

@@ -3,7 +3,7 @@ import { MatPaginator, MatPaginatorModule } from "@angular/material/paginator";
import { MatTableDataSource, MatTableModule } from "@angular/material/table"; import { MatTableDataSource, MatTableModule } from "@angular/material/table";
import { MatDialog } from "@angular/material/dialog"; import { MatDialog } from "@angular/material/dialog";
import { TranslateModule, TranslateService } from "@ngx-translate/core"; import { TranslateModule, TranslateService } from "@ngx-translate/core";
import { CommonModule } from "@angular/common";
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner"; import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
import { MatButtonModule } from "@angular/material/button"; import { MatButtonModule } from "@angular/material/button";
@@ -15,71 +15,72 @@ import { AuthenticationService } from "../../services/authentication.service";
import { NewJumpTypeComponent } from "../new-jump-type/new-jump-type.component"; import { NewJumpTypeComponent } from "../new-jump-type/new-jump-type.component";
@Component({ @Component({
selector: "app-list-of-jump-types", selector: "app-list-of-jump-types",
templateUrl: "./list-of-jump-types.component.html", templateUrl: "./list-of-jump-types.component.html",
styleUrls: ["./list-of-jump-types.component.css"], styleUrls: ["./list-of-jump-types.component.css"],
imports: [ imports: [
TranslateModule, TranslateModule,
CommonModule, MatPaginatorModule,
MatPaginatorModule, MatProgressSpinnerModule,
MatProgressSpinnerModule, MatTableModule,
MatTableModule, MatButtonModule,
MatButtonModule, ],
],
}) })
export class ListOfJumpTypesComponent implements OnInit { export class ListOfJumpTypesComponent implements OnInit {
public displayedColumns: Array<string> = ["name"]; public displayedColumns: Array<string> = ["name"];
public dataSourceTable: MatTableDataSource<JumpTypeResp>; public dataSourceTable: MatTableDataSource<JumpTypeResp>;
public isUserAdmin: boolean; public isUserAdmin: boolean;
public resultsLength = 0; public resultsLength = 0;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
constructor( constructor(
private serviceApi: JumpTypeService, private serviceApi: JumpTypeService,
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
private authenticationService: AuthenticationService, private authenticationService: AuthenticationService,
public dialog: MatDialog, public dialog: MatDialog,
private translateService: TranslateService private translateService: TranslateService,
) { ) {
this.isUserAdmin = this.isUserAdmin =
this.authenticationService.currentUserValue.roles === "admin"; this.authenticationService.currentUserValue.roles === "admin";
} }
ngOnInit() {
this.serviceComm.refreshRequest.subscribe((action) => {
if (action === AddAction.JumpType) {
this.dialog.closeAll();
this.getListOfJumpTypes();
}
});
this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) {
this.updateTitle();
}
});
ngOnInit() {
this.serviceComm.refreshRequest.subscribe((action) => {
if (action === AddAction.JumpType) {
this.dialog.closeAll();
this.getListOfJumpTypes();
}
});
this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) {
this.updateTitle(); this.updateTitle();
} this.getListOfJumpTypes();
}); }
this.updateTitle(); getListOfJumpTypes() {
this.getListOfJumpTypes(); this.serviceApi.getListOfJumpTypes().subscribe((data) => {
} setTimeout(() => {
data.sort((a, b) => a.name.localeCompare(b.name));
this.dataSourceTable = new MatTableDataSource<JumpTypeResp>(
data,
);
this.dataSourceTable.paginator = this.paginator;
this.resultsLength = data.length;
}, 500);
});
}
getListOfJumpTypes() { openDialogToAdd() {
this.serviceApi.getListOfJumpTypes().subscribe((data) => { this.dialog.open(NewJumpTypeComponent);
setTimeout(() => { }
data.sort((a, b) => a.name.localeCompare(b.name));
this.dataSourceTable = new MatTableDataSource<JumpTypeResp>(data);
this.dataSourceTable.paginator = this.paginator;
this.resultsLength = data.length;
}, 500);
});
}
openDialogToAdd() { private updateTitle() {
this.dialog.open(NewJumpTypeComponent); this.translateService.get("ListJumpTypes_Title").subscribe((data) => {
} this.serviceComm.updatedComponentTitle(data);
});
private updateTitle() { }
this.translateService.get("ListJumpTypes_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
} }

View File

@@ -1,32 +1,32 @@
table { table {
width: 100%; width: 100%;
} }
.mat-row td { .mat-row td {
padding: 15px; padding: 15px;
} }
.spanWithBreakWord { .spanWithBreakWord {
display: inline-block; display: inline-block;
min-width: 200px; min-width: 200px;
word-wrap: break-word; word-wrap: break-word;
} }
.smallSpanWithBreakWord { .smallSpanWithBreakWord {
display: inline-block; display: inline-block;
min-width: 50px; min-width: 50px;
word-wrap: break-word; word-wrap: break-word;
} }
.content { .content {
min-height: 90vh; min-height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
padding-top: 25px; padding-top: 25px;
} }
th.mat-header-cell { th.mat-header-cell {
text-align: center; text-align: center;
} }

View File

@@ -1,165 +1,176 @@
<div class="content"> <div class="content">
<div> <div>
<button <button
mat-raised-button mat-raised-button
color="accent" color="accent"
[routerLink]="['/newjump']" [routerLink]="['/newjump']"
[routerLinkActive]="['active']" [routerLinkActive]="['active']"
skipLocationChange skipLocationChange
>
{{ "ListJump_Add" | translate }}
</button>
</div>
<mat-progress-bar
[mode]="'indeterminate'"
*ngIf="isLoading"
></mat-progress-bar>
<div>
<table mat-table [dataSource]="dataSourceTable">
<ng-container matColumnDef="infos">
<th
mat-header-cell
*matHeaderCellDef
style="min-width: 80px; text-wrap: nowrap"
></th>
<td
mat-cell
*matCellDef="let element"
style="text-align: left; text-wrap: nowrap"
> >
<mat-icon {{ "ListJump_Add" | translate }}
aria-hidden="false" </button>
aria-label="Additional informations of the jump" </div>
style="cursor: pointer"
(click)="openDialog(element, false)"
svgIcon="info"
></mat-icon>
<mat-icon
aria-hidden="false"
aria-label="Notes"
[style.visibility]="
element.notes != undefined ? 'visible' : 'hidden'
"
svgIcon="note"
></mat-icon>
<mat-icon
aria-hidden="false"
aria-label="Special jump"
[style.visibility]="element.isSpecial ? 'visible' : 'hidden'"
svgIcon="celebration"
></mat-icon>
</td>
</ng-container>
<ng-container matColumnDef="id"> @if (isLoading) {
<th mat-header-cell *matHeaderCellDef style="min-width: 70px"> <mat-progress-bar [mode]="'indeterminate'"></mat-progress-bar>
{{ "ListJump_Header_Num" | translate }} }
</th>
<td mat-cell *matCellDef="let element; let i = index">
{{
paginator.length - (paginator.pageIndex * paginator.pageSize + i)
}}
</td>
</ng-container>
<ng-container matColumnDef="jumpDate"> <div>
<th mat-header-cell *matHeaderCellDef> <table mat-table [dataSource]="dataSourceTable">
{{ "ListJump_Header_Date" | translate }} <ng-container matColumnDef="infos">
</th> <th
<td mat-cell *matCellDef="let element"> mat-header-cell
<span *matHeaderCellDef
class="smallSpanWithBreakWord" style="min-width: 80px; text-wrap: nowrap"
[innerHTML]="element.jumpDate | date: 'yyyy-MM-dd'" ></th>
></span> <td
</td> mat-cell
</ng-container> *matCellDef="let element"
style="text-align: left; text-wrap: nowrap"
>
<mat-icon
aria-hidden="false"
aria-label="Additional informations of the jump"
style="cursor: pointer"
(click)="openDialog(element, false)"
svgIcon="info"
></mat-icon>
<mat-icon
aria-hidden="false"
aria-label="Notes"
[style.visibility]="
element.notes != undefined ? 'visible' : 'hidden'
"
svgIcon="note"
></mat-icon>
<mat-icon
aria-hidden="false"
aria-label="Special jump"
[style.visibility]="
element.isSpecial ? 'visible' : 'hidden'
"
svgIcon="celebration"
></mat-icon>
</td>
</ng-container>
<ng-container matColumnDef="jumpType"> <ng-container matColumnDef="id">
<th <th mat-header-cell *matHeaderCellDef style="min-width: 70px">
mat-header-cell {{ "ListJump_Header_Num" | translate }}
*matHeaderCellDef </th>
style="min-width: 100px; text-wrap: nowrap" <td mat-cell *matCellDef="let element; let i = index">
> {{
{{ "ListJump_Header_JumpType" | translate }} paginator.length -
</th> (paginator.pageIndex * paginator.pageSize + i)
<td mat-cell *matCellDef="let element" style="text-wrap: nowrap"> }}
<span </td>
class="smallSpanWithBreakWord" </ng-container>
[innerHTML]="element.jumpType.name"
></span>
</td>
</ng-container>
<ng-container matColumnDef="aircraft"> <ng-container matColumnDef="jumpDate">
<th mat-header-cell *matHeaderCellDef style="min-width: 110px"> <th mat-header-cell *matHeaderCellDef>
{{ "ListJump_Header_Aircraft" | translate }} {{ "ListJump_Header_Date" | translate }}
</th> </th>
<td mat-cell *matCellDef="let element"> <td mat-cell *matCellDef="let element">
<span <span
class="smallSpanWithBreakWord" class="smallSpanWithBreakWord"
[innerHTML]="element.aircraft.name" [innerHTML]="element.jumpDate | date: 'yyyy-MM-dd'"
></span> ></span>
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="dropZone"> <ng-container matColumnDef="jumpType">
<th mat-header-cell *matHeaderCellDef> <th
{{ "ListJump_Header_Dz" | translate }} mat-header-cell
</th> *matHeaderCellDef
<td mat-cell *matCellDef="let element"> style="min-width: 100px; text-wrap: nowrap"
<span >
class="spanWithBreakWord" {{ "ListJump_Header_JumpType" | translate }}
[innerHTML]="element.dropZone.name" </th>
></span> <td
</td> mat-cell
</ng-container> *matCellDef="let element"
style="text-wrap: nowrap"
>
<span
class="smallSpanWithBreakWord"
[innerHTML]="element.jumpType.name"
></span>
</td>
</ng-container>
<ng-container matColumnDef="gear"> <ng-container matColumnDef="aircraft">
<th mat-header-cell *matHeaderCellDef> <th mat-header-cell *matHeaderCellDef style="min-width: 110px">
{{ "ListJump_Header_Id" | translate }} {{ "ListJump_Header_Aircraft" | translate }}
</th> </th>
<td mat-cell *matCellDef="let element">{{ element.gear.name }}</td> <td mat-cell *matCellDef="let element">
</ng-container> <span
class="smallSpanWithBreakWord"
[innerHTML]="element.aircraft.name"
></span>
</td>
</ng-container>
<ng-container matColumnDef="actions"> <ng-container matColumnDef="dropZone">
<th <th mat-header-cell *matHeaderCellDef>
mat-header-cell {{ "ListJump_Header_Dz" | translate }}
*matHeaderCellDef </th>
style="min-width: 80px; text-wrap: nowrap" <td mat-cell *matCellDef="let element">
></th> <span
<td class="spanWithBreakWord"
mat-cell [innerHTML]="element.dropZone.name"
*matCellDef="let element" ></span>
style="text-align: left; text-wrap: nowrap" </td>
> </ng-container>
<mat-icon
aria-hidden="false"
aria-label="Delete this jump"
style="cursor: pointer"
(click)="delete(element)"
svgIcon="delete"
></mat-icon>
<mat-icon
aria-hidden="false"
aria-label="Update some informations of the jump"
style="cursor: pointer; margin-left: 10px"
(click)="openDialog(element, true)"
svgIcon="edit"
></mat-icon>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr> <ng-container matColumnDef="gear">
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> <th mat-header-cell *matHeaderCellDef>
</table> {{ "ListJump_Header_Id" | translate }}
</div> </th>
<td mat-cell *matCellDef="let element">
{{ element.gear.name }}
</td>
</ng-container>
<mat-paginator <ng-container matColumnDef="actions">
#paginator <th
[pageSize]="20" mat-header-cell
(page)="pageChanged($event)" *matHeaderCellDef
showFirstLastButtons style="min-width: 80px; text-wrap: nowrap"
></mat-paginator> ></th>
<td
mat-cell
*matCellDef="let element"
style="text-align: left; text-wrap: nowrap"
>
<mat-icon
aria-hidden="false"
aria-label="Delete this jump"
style="cursor: pointer"
(click)="delete(element)"
svgIcon="delete"
></mat-icon>
<mat-icon
aria-hidden="false"
aria-label="Update some informations of the jump"
style="cursor: pointer; margin-left: 10px"
(click)="openDialog(element, true)"
svgIcon="edit"
></mat-icon>
</td>
</ng-container>
<tr
mat-header-row
*matHeaderRowDef="displayedColumns; sticky: true"
></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
<mat-paginator
#paginator
[pageSize]="20"
(page)="pageChanged($event)"
showFirstLastButtons
></mat-paginator>
</div> </div>

View File

@@ -1,9 +1,9 @@
import { Component, OnInit, ViewChild } from "@angular/core"; import { Component, OnInit, ViewChild } from "@angular/core";
import { RouterLink, RouterModule } from "@angular/router"; import { RouterLink, RouterModule } from "@angular/router";
import { import {
MatPaginator, MatPaginator,
MatPaginatorModule, MatPaginatorModule,
PageEvent, PageEvent,
} from "@angular/material/paginator"; } from "@angular/material/paginator";
import { MatTableDataSource, MatTableModule } from "@angular/material/table"; import { MatTableDataSource, MatTableModule } from "@angular/material/table";
import { MatDialog } from "@angular/material/dialog"; import { MatDialog } from "@angular/material/dialog";
@@ -25,108 +25,108 @@ import { JumpInfosComponent } from "../jump-infos/jump-infos.component";
import { StatsService } from "../../services/stats.service"; import { StatsService } from "../../services/stats.service";
@Component({ @Component({
selector: "app-list-of-jumps", selector: "app-list-of-jumps",
templateUrl: "./list-of-jumps.component.html", templateUrl: "./list-of-jumps.component.html",
styleUrls: ["./list-of-jumps.component.css"], styleUrls: ["./list-of-jumps.component.css"],
imports: [ imports: [
TranslateModule, TranslateModule,
CommonModule, CommonModule,
RouterLink, RouterLink,
RouterModule, RouterModule,
MatIconModule, MatIconModule,
MatPaginatorModule, MatPaginatorModule,
MatProgressSpinnerModule, MatProgressSpinnerModule,
MatProgressBarModule, MatProgressBarModule,
MatTableModule, MatTableModule,
MatFormFieldModule, MatFormFieldModule,
ReactiveFormsModule, ReactiveFormsModule,
MatInputModule, MatInputModule,
MatButtonModule, MatButtonModule,
], ],
}) })
export class ListOfJumpsComponent implements OnInit { export class ListOfJumpsComponent implements OnInit {
public displayedColumns: Array<string> = [ public displayedColumns: Array<string> = [
"infos", "infos",
"id", "id",
"jumpDate", "jumpDate",
"jumpType", "jumpType",
"aircraft", "aircraft",
"dropZone", "dropZone",
"actions", "actions",
]; ];
public dataSourceTable: MatTableDataSource<Jump> = new MatTableDataSource(); public dataSourceTable: MatTableDataSource<Jump> = new MatTableDataSource();
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
public isLoading: boolean = false; public isLoading: boolean = false;
constructor( constructor(
private serviceApi: JumpService, private serviceApi: JumpService,
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
public dialog: MatDialog, public dialog: MatDialog,
private translateService: TranslateService, private translateService: TranslateService,
private statsService: StatsService private statsService: StatsService,
) {} ) {}
ngAferViewInit(): void { ngAferViewInit(): void {
this.dataSourceTable.paginator = this.paginator; this.dataSourceTable.paginator = this.paginator;
} }
ngOnInit() {
this.serviceComm.refreshRequest.subscribe((action) => {
if (action === AddAction.Jump) {
this.dialog.closeAll();
this.getListOfJumps();
}
});
this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) {
this.updateTitle();
}
});
ngOnInit() {
this.serviceComm.refreshRequest.subscribe((action) => {
if (action === AddAction.Jump) {
this.dialog.closeAll();
this.getListOfJumps();
}
});
this.serviceComm.forceTranslateTitle.subscribe((data) => {
if (data === true) {
this.updateTitle(); this.updateTitle();
} this.getListOfJumps();
}); }
this.updateTitle(); getListOfJumps(pageSize: number = 20, pageIndex: number = 0) {
this.getListOfJumps(); this.isLoading = true;
}
getListOfJumps(pageSize: number = 20, pageIndex: number = 0) { this.serviceApi
this.isLoading = true; .getJumps(pageIndex * pageSize, pageIndex * pageSize + pageSize)
.subscribe((data) => {
this.dataSourceTable.data = data.rows;
setTimeout(() => {
this.paginator.length = data.count;
this.paginator.pageIndex = pageIndex;
this.isLoading = false;
}, 500);
});
}
this.serviceApi openDialog(item: Jump, editMode: boolean) {
.getJumps(pageIndex * pageSize, pageIndex * pageSize + pageSize) this.dialog.open(JumpInfosComponent, {
.subscribe((data) => { data: { jump: item, editMode: editMode },
this.dataSourceTable.data = data.rows; maxHeight: "400px",
setTimeout(() => { minWidth: "350px",
this.paginator.length = data.count; });
this.paginator.pageIndex = pageIndex; }
this.isLoading = false;
}, 500);
});
}
openDialog(item: Jump, editMode: boolean) { delete(item: Jump) {
this.dialog.open(JumpInfosComponent, { let data: Array<Jump> = this.dataSourceTable.data;
data: { jump: item, editMode: editMode }, data = data.filter((d) => d.id !== item.id);
maxHeight: "400px",
minWidth: "350px",
});
}
delete(item: Jump) { this.dataSourceTable.data = data;
let data: Array<Jump> = this.dataSourceTable.data;
data = data.filter((d) => d.id !== item.id);
this.dataSourceTable.data = data; this.serviceApi.deleteJump(item);
this.statsService.resetStats();
}
this.serviceApi.deleteJump(item); pageChanged(event: PageEvent) {
this.statsService.resetStats(); this.getListOfJumps(event.pageSize, event.pageIndex);
} }
pageChanged(event: PageEvent) { private updateTitle() {
this.getListOfJumps(event.pageSize, event.pageIndex); this.translateService.get("ListJumps_Title").subscribe((data) => {
} this.serviceComm.updatedComponentTitle(data);
});
private updateTitle() { }
this.translateService.get("ListJumps_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
} }

View File

@@ -1,24 +1,24 @@
.formListTunnelFlight { .formListTunnelFlight {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 150px; min-width: 150px;
max-width: 500px; max-width: 500px;
width: 100%; width: 100%;
} }
.content { .content {
min-height: 90vh; min-height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
padding-top: 25px; padding-top: 25px;
} }
.chart-container { .chart-container {
position: relative; position: relative;
margin: auto; margin: auto;
height: 80vh; height: 80vh;
width: 80vw; width: 80vw;
} }

View File

@@ -1,136 +1,147 @@
<div class="content"> <div class="content">
<div> <div>
<button <button
mat-raised-button mat-raised-button
color="accent" color="accent"
[routerLink]="['/newTunnelFlight']" [routerLink]="['/newTunnelFlight']"
[routerLinkActive]="['active']" [routerLinkActive]="['active']"
skipLocationChange skipLocationChange
>
{{ "ListTunnelFlight_Add" | translate }}
</button>
</div>
@if (isLoading) {
<mat-progress-bar [mode]="'indeterminate'"></mat-progress-bar>
}
<mat-radio-group
[(ngModel)]="selectedPeriod"
(ngModelChange)="onPeriodChange()"
> >
{{ "ListTunnelFlight_Add" | translate }} <mat-radio-button value="currentYear">{{
</button> "ListTunnelFlight_CurrentYear" | translate
</div> }}</mat-radio-button>
<mat-radio-button value="12Months">{{
"ListTunnelFlight_12Months" | translate
}}</mat-radio-button>
<mat-radio-button value="all">{{
"ListTunnelFlight_AllFlights" | translate
}}</mat-radio-button>
</mat-radio-group>
<mat-progress-bar <mat-nav-list>
[mode]="'indeterminate'" @for (stat of stats; track stat) {
*ngIf="isLoading" <mat-list-item matListItemLine>
></mat-progress-bar> <label>{{ stat.id }} : {{ stat.values }}</label>
</mat-list-item>
}
</mat-nav-list>
<mat-radio-group <div class="chart-container">
[(ngModel)]="selectedPeriod" <!-- https://www.freecodecamp.org/news/how-to-make-bar-and-line-charts-using-chartjs-in-angular/ -->
(ngModelChange)="onPeriodChange()" <canvas
> baseChart
<mat-radio-button value="currentYear">{{ [data]="barChartData"
"ListTunnelFlight_CurrentYear" | translate [options]="barChartOptions"
}}</mat-radio-button> [legend]="barChartLegend"
<mat-radio-button value="12Months">{{ [type]="barChartType"
"ListTunnelFlight_12Months" | translate >
}}</mat-radio-button> </canvas>
<mat-radio-button value="all">{{ </div>
"ListTunnelFlight_AllFlights" | translate
}}</mat-radio-button>
</mat-radio-group>
<mat-nav-list> <div>
<mat-list-item matListItemLine *ngFor="let stat of stats"> <button mat-raised-button color="accent" (click)="onLoadTable()">
<label>{{ stat.id }} : {{ stat.values }}</label> {{ "ListTunnelFlight_LoadTable" | translate }}
</mat-list-item> </button>
</mat-nav-list>
<div class="chart-container"> @if (dataSourceTable?.data.length) {
<!-- https://www.freecodecamp.org/news/how-to-make-bar-and-line-charts-using-chartjs-in-angular/ --> <table mat-table [dataSource]="dataSourceTable">
<canvas <ng-container matColumnDef="id">
baseChart <th mat-header-cell *matHeaderCellDef>ID</th>
[data]="barChartData" <td mat-cell *matCellDef="let element">
[options]="barChartOptions" <span
[legend]="barChartLegend" class="smallSpanWithBreakWord"
[type]="barChartType" [innerHTML]="element.id"
> ></span>
</canvas> </td>
</div> </ng-container>
<ng-container matColumnDef="tunnel">
<div> <th mat-header-cell *matHeaderCellDef>Tunnel</th>
<button mat-raised-button color="accent" (click)="onLoadTable()"> <td mat-cell *matCellDef="let element">
{{ "ListTunnelFlight_LoadTable" | translate }} <span
</button> class="smallSpanWithBreakWord"
[innerHTML]="element.tunnel.name"
<table ></span>
mat-table </td>
[dataSource]="dataSourceTable" </ng-container>
*ngIf="dataSourceTable?.data.length" <ng-container matColumnDef="jumpType">
> <th mat-header-cell *matHeaderCellDef>Jump Type</th>
<ng-container matColumnDef="id"> <td mat-cell *matCellDef="let element">
<th mat-header-cell *matHeaderCellDef>ID</th> <span
<td mat-cell *matCellDef="let element"> class="smallSpanWithBreakWord"
<span class="smallSpanWithBreakWord" [innerHTML]="element.id"></span> [innerHTML]="element.jumpType.name"
</td> ></span>
</ng-container> </td>
</ng-container>
<ng-container matColumnDef="tunnel"> <ng-container matColumnDef="nbMinutes">
<th mat-header-cell *matHeaderCellDef>Tunnel</th> <th mat-header-cell *matHeaderCellDef>Minutes</th>
<td mat-cell *matCellDef="let element"> <td mat-cell *matCellDef="let element">
<span <span
class="smallSpanWithBreakWord" class="smallSpanWithBreakWord"
[innerHTML]="element.tunnel.name" [innerHTML]="element.nbMinutes"
></span> ></span>
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="notes">
<ng-container matColumnDef="jumpType"> <th mat-header-cell *matHeaderCellDef>Notes</th>
<th mat-header-cell *matHeaderCellDef>Jump Type</th> <td mat-cell *matCellDef="let element">
<td mat-cell *matCellDef="let element"> <span
<span class="smallSpanWithBreakWord"
class="smallSpanWithBreakWord" [innerHTML]="element.notes"
[innerHTML]="element.jumpType.name" ></span>
></span> </td>
</td> </ng-container>
</ng-container> <ng-container matColumnDef="flightDate">
<th mat-header-cell *matHeaderCellDef>Date</th>
<ng-container matColumnDef="nbMinutes"> <td mat-cell *matCellDef="let element">
<th mat-header-cell *matHeaderCellDef>Minutes</th> <span
<td mat-cell *matCellDef="let element"> class="smallSpanWithBreakWord"
<span [innerHTML]="
class="smallSpanWithBreakWord" element.flightDate | date: 'yyyy-MM-dd'
[innerHTML]="element.nbMinutes" "
></span> ></span>
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="actions">
<ng-container matColumnDef="notes"> <th
<th mat-header-cell *matHeaderCellDef>Notes</th> mat-header-cell
<td mat-cell *matCellDef="let element"> *matHeaderCellDef
<span style="min-width: 80px"
class="smallSpanWithBreakWord" ></th>
[innerHTML]="element.notes" <td
></span> mat-cell
</td> *matCellDef="let element"
</ng-container> style="text-align: left"
>
<ng-container matColumnDef="flightDate"> <mat-icon
<th mat-header-cell *matHeaderCellDef>Date</th> aria-hidden="false"
<td mat-cell *matCellDef="let element"> aria-label="Delete this jump"
<span style="cursor: pointer"
class="smallSpanWithBreakWord" (click)="delete(element)"
[innerHTML]="element.flightDate | date: 'yyyy-MM-dd'" svgIcon="delete"
></span> ></mat-icon>
</td> </td>
</ng-container> </ng-container>
<tr
<ng-container matColumnDef="actions"> mat-header-row
<th mat-header-cell *matHeaderCellDef style="min-width: 80px"></th> *matHeaderRowDef="displayedColumns; sticky: true"
<td mat-cell *matCellDef="let element" style="text-align: left"> ></tr>
<mat-icon <tr
aria-hidden="false" mat-row
aria-label="Delete this jump" *matRowDef="let row; columns: displayedColumns"
style="cursor: pointer" ></tr>
(click)="delete(element)" </table>
svgIcon="delete" }
></mat-icon> </div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</div>
</div> </div>

View File

@@ -22,257 +22,274 @@ 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"],
imports: [ imports: [
TranslateModule, TranslateModule,
BaseChartDirective, BaseChartDirective,
CommonModule, CommonModule,
MatIconModule, MatIconModule,
MatListModule, MatListModule,
MatProgressSpinnerModule, MatProgressSpinnerModule,
RouterLink, RouterLink,
MatRadioModule, MatRadioModule,
MatProgressBarModule, MatProgressBarModule,
FormsModule, FormsModule,
RouterModule, RouterModule,
MatTableModule, MatTableModule,
MatButtonModule, MatButtonModule,
], ],
}) })
export class ListOfTunnelFlightsComponent implements OnInit { export class ListOfTunnelFlightsComponent implements OnInit {
public barChartLegend = true; public barChartLegend = true;
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( constructor(
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
private serviceTunnelFlight: TunnelFlightService, private serviceTunnelFlight: TunnelFlightService,
private translateService: TranslateService, private translateService: TranslateService,
private dateService: DateService 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.chartConfig();
this.selectedPeriod = "currentYear";
this.getDataForGraph();
}
public onPeriodChange() {
this.getDataForGraph();
if (this.dataSourceTable?.data.length > 0) {
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;
}); });
} this.updateTitle();
this.chartConfig();
this.selectedPeriod = "currentYear";
this.getDataForGraph();
} }
const results = Array.from(tmpResults, function (item) { public onPeriodChange() {
return { label: item[0], data: item[1] }; this.getDataForGraph();
}); if (this.dataSourceTable?.data.length > 0) {
this.getDataForTable();
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 onLoadTable() {
} this.getDataForTable();
}
public delete(item: TunnelFlight) { private chartConfig() {
let data: Array<TunnelFlight> = this.dataSourceTable.data; this.barChartType = "bar";
data = data.filter((d) => d.id !== item.id); 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,
},
},
};
}
this.dataSourceTable.data = data; private updateTitle() {
this.serviceTunnelFlight.deleteTunnelFlight(item); 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: 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,50 +1,66 @@
<form <form
focusInvalidInput focusInvalidInput
autocomplete="off" autocomplete="off"
style="padding: 10px" style="padding: 10px"
[formGroup]="loginForm" [formGroup]="loginForm"
(ngSubmit)="onLoginSubmit()" (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 <input
type="text" type="text"
matInput matInput
#username="matInput" #username="matInput"
formControlName="username" formControlName="username"
[ngClass]="{ 'is-invalid': submitted && formCtrls['username'].errors }" [ngClass]="{
/> 'is-invalid': submitted && formCtrls['username'].errors,
<mat-error *ngIf="formCtrls['username'].hasError('required')"> }"
{{ "LoginUser_UsernameRequired" | translate }} />
</mat-error> @if (formCtrls["username"].hasError("required")) {
<mat-error *ngIf="formCtrls['username'].hasError('minlength')"> <mat-error>
{{ 'LoginUser_UsernamePattern | translate }} {{ "LoginUser_UsernameRequired" | translate }}
</mat-error> </mat-error>
</mat-form-field> }
</p> @if (formCtrls["username"].hasError("minlength")) {
<p> <mat-error>
<mat-form-field> {{ 'LoginUser_UsernamePattern | translate }}
<mat-label>{{ "LoginUser_Password" | translate }}</mat-label> </mat-error>
<input }
type="password" </mat-form-field>
matInput </p>
formControlName="password" <p>
[ngClass]="{ 'is-invalid': submitted && formCtrls['password'].errors }" <mat-form-field>
/> <mat-label>{{ "LoginUser_Password" | translate }}</mat-label>
<mat-error *ngIf="formCtrls['password'].hasError('required')"> <input
{{ "LoginUser_PasswordRequired" | translate }} type="password"
</mat-error> matInput
<mat-error *ngIf="formCtrls['password'].hasError('pattern')"> formControlName="password"
{{ "LoginUser_PasswordPattern" | translate }} [ngClass]="{
</mat-error> 'is-invalid': submitted && formCtrls['password'].errors,
</mat-form-field> }"
</p> />
@if (formCtrls["password"].hasError("required")) {
<mat-error>
{{ "LoginUser_PasswordRequired" | translate }}
</mat-error>
}
@if (formCtrls["password"].hasError("pattern")) {
<mat-error>
{{ "LoginUser_PasswordPattern" | translate }}
</mat-error>
}
</mat-form-field>
</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> @if (loading) {
{{ "LoginUser_BtnLogin" | translate }} <span class="spinner-border spinner-border-sm mr-1"></span>
</button> }
{{ "LoginUser_BtnLogin" | translate }}
</button>
<div *ngIf="error" class="alert alert-danger mt-3 mb-0">{{ error }}</div> @if (error) {
<div class="alert alert-danger mt-3 mb-0">{{ error }}</div>
}
</form> </form>

View File

@@ -2,10 +2,10 @@ import { Component, OnInit, ViewChild, AfterViewInit } from "@angular/core";
import { Router, ActivatedRoute } from "@angular/router"; import { Router, ActivatedRoute } from "@angular/router";
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { import {
FormBuilder, FormBuilder,
FormGroup, FormGroup,
ReactiveFormsModule, ReactiveFormsModule,
Validators, Validators,
} from "@angular/forms"; } from "@angular/forms";
import { TranslateModule, TranslatePipe } from "@ngx-translate/core"; import { TranslateModule, TranslatePipe } from "@ngx-translate/core";
import { MatInput, MatInputModule } from "@angular/material/input"; import { MatInput, MatInputModule } from "@angular/material/input";
@@ -17,72 +17,72 @@ 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"],
imports: [ imports: [
CommonModule, CommonModule,
MatFormFieldModule, MatFormFieldModule,
ReactiveFormsModule, ReactiveFormsModule,
MatInputModule, MatInputModule,
TranslateModule, TranslateModule,
TranslatePipe, TranslatePipe,
MatButtonModule, MatButtonModule,
], ],
}) })
export class LoginUserComponent implements OnInit { export class LoginUserComponent implements OnInit {
public loginForm: FormGroup; public loginForm: FormGroup;
public loading = false; public loading = false;
public submitted = false; public submitted = false;
public returnUrl: string; public returnUrl: string;
public error: string = ""; public error: string = "";
constructor( constructor(
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private authenticationService: AuthenticationService private authenticationService: AuthenticationService,
) { ) {
if (this.authenticationService.currentUserValue) { if (this.authenticationService.currentUserValue) {
this.router.navigate(["/"]); this.router.navigate(["/"]);
}
} }
}
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() {
return this.loginForm.controls; return this.loginForm.controls;
} }
public onLoginSubmit() { public onLoginSubmit() {
this.submitted = true; this.submitted = true;
if (this.loginForm.valid) { if (this.loginForm.valid) {
this.loading = true; this.loading = true;
this.authenticationService this.authenticationService
.login( .login(
this.formCtrls["username"].value, this.formCtrls["username"].value,
this.formCtrls["password"].value this.formCtrls["password"].value,
) )
.pipe(first()) .pipe(first())
.subscribe({ .subscribe({
complete: () => this.router.navigate([this.returnUrl]), complete: () => this.router.navigate([this.returnUrl]),
error: (error) => { error: (error) => {
this.error = error.message; this.error = error.message;
this.loading = false; this.loading = false;
}, },
}); });
}
} }
}
} }

View File

@@ -1,10 +1,10 @@
.content { .content {
height: 90vh; height: 90vh;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.mat-card-title { .mat-card-title {
margin: 0 !important; margin: 0 !important;
} }

View File

@@ -1,43 +1,51 @@
<div class="content"> <div class="content">
<mat-card style="max-width: 500px" flex="50"> <mat-card style="max-width: 500px" flex="50">
<mat-card-header <mat-card-header
style=" style="
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background-color: rgba(0, 0, 0, 0.03); background-color: rgba(0, 0, 0, 0.03);
" "
> >
<mat-card-title>{{ "Login_Title" | translate }}</mat-card-title> <mat-card-title>{{ "Login_Title" | translate }}</mat-card-title>
<mat-select <mat-select
(selectionChange)="switchLang($event)" (selectionChange)="switchLang($event)"
[(value)]="selectedLanguageFlag" [(value)]="selectedLanguageFlag"
style="width: 60px; padding-left: 30px" style="width: 60px; padding-left: 30px"
> >
<mat-select-trigger> <mat-select-trigger>
<img <img
src="{{ 'assets/img/' + selectedLanguageFlag + '.svg' }}" src="{{
style="width: 30px" 'assets/img/' + selectedLanguageFlag + '.svg'
/> }}"
</mat-select-trigger> style="width: 30px"
<mat-option value="fr"> />
<img src="assets/img/fr.svg" style="width: 30px" /> </mat-select-trigger>
</mat-option> <mat-option value="fr">
<mat-option value="en"> <img src="assets/img/fr.svg" style="width: 30px" />
<img src="assets/img/en.svg" style="width: 30px" /> </mat-option>
</mat-option> <mat-option value="en">
</mat-select> <img src="assets/img/en.svg" style="width: 30px" />
</mat-card-header> </mat-option>
</mat-select>
</mat-card-header>
<mat-card-content> <mat-card-content>
<mat-tab-group mat-align-tabs="center" animationDuration="0ms"> <mat-tab-group mat-align-tabs="center" animationDuration="0ms">
<mat-tab label="{{ 'Login_Tab_WithUser' | translate }}" tabIndex="-1"> <mat-tab
<app-login-user></app-login-user> label="{{ 'Login_Tab_WithUser' | translate }}"
</mat-tab> tabIndex="-1"
<mat-tab label="{{ 'Login_Tab_CreateUser' | translate }}" tabIndex="-1"> >
<app-create-user></app-create-user> <app-login-user></app-login-user>
</mat-tab> </mat-tab>
</mat-tab-group> <mat-tab
</mat-card-content> label="{{ 'Login_Tab_CreateUser' | translate }}"
</mat-card> tabIndex="-1"
>
<app-create-user></app-create-user>
</mat-tab>
</mat-tab-group>
</mat-card-content>
</mat-card>
</div> </div>

View File

@@ -1,10 +1,10 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { import {
TranslateModule, TranslateModule,
TranslatePipe, TranslatePipe,
TranslateService, TranslateService,
} from "@ngx-translate/core"; } from "@ngx-translate/core";
import { CommonModule } from "@angular/common";
import { MatSelectModule } from "@angular/material/select"; import { MatSelectModule } from "@angular/material/select";
import { MatOptionModule } from "@angular/material/core"; import { MatOptionModule } from "@angular/material/core";
import { MatCardModule } from "@angular/material/card"; import { MatCardModule } from "@angular/material/card";
@@ -14,32 +14,31 @@ import { LoginUserComponent } from "../login-user/login-user.component";
import { CreateUserComponent } from "../create-user/create-user.component"; import { CreateUserComponent } from "../create-user/create-user.component";
@Component({ @Component({
selector: "app-login", selector: "app-login",
templateUrl: "./login.component.html", templateUrl: "./login.component.html",
styleUrls: ["./login.component.css"], styleUrls: ["./login.component.css"],
imports: [ imports: [
TranslateModule, TranslateModule,
CommonModule, MatSelectModule,
MatSelectModule, MatOptionModule,
MatOptionModule, MatCardModule,
MatCardModule, MatTabsModule,
MatTabsModule, LoginUserComponent,
LoginUserComponent, CreateUserComponent,
CreateUserComponent, TranslateModule,
TranslateModule, ],
],
}) })
export class LoginComponent implements OnInit { export class LoginComponent implements OnInit {
public selectedLanguageFlag: string; public selectedLanguageFlag: string;
constructor(private translate: TranslateService) { constructor(private translate: TranslateService) {
this.selectedLanguageFlag = "en"; this.selectedLanguageFlag = "en";
} }
ngOnInit() {} ngOnInit() {}
public switchLang(event: any) { public switchLang(event: any) {
this.translate.use(event.value); this.translate.use(event.value);
this.selectedLanguageFlag = event.value; this.selectedLanguageFlag = event.value;
} }
} }

View File

@@ -1,7 +1,7 @@
.content { .content {
height: 90vh; height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
} }

View File

@@ -1,16 +1,23 @@
<div class="content"> <div class="content">
<form [formGroup]="addForm" (ngSubmit)="onSubmit(addForm.value)"> <form [formGroup]="addForm" (ngSubmit)="onSubmit(addForm.value)">
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Aircraft name</mat-label> <mat-label>Aircraft name</mat-label>
<input matInput type="text" formControlName="aircraftName"> <input matInput type="text" formControlName="aircraftName" />
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<input type="file" #fileUpload id="fileUpload" name="fileUpload" accept="image/*" formControlName="image" <input
(change)="onFileChanged($event)" /> type="file"
</p> #fileUpload
id="fileUpload"
name="fileUpload"
accept="image/*"
formControlName="image"
(change)="onFileChanged($event)"
/>
</p>
<button type="submit" mat-raised-button color="accent">Add</button> <button type="submit" mat-raised-button color="accent">Add</button>
</form> </form>
</div> </div>

View File

@@ -1,9 +1,9 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
ReactiveFormsModule, ReactiveFormsModule,
Validators, Validators,
} from "@angular/forms"; } from "@angular/forms";
import { TranslateModule, TranslateService } from "@ngx-translate/core"; import { TranslateModule, TranslateService } from "@ngx-translate/core";
import { MatFormFieldModule } from "@angular/material/form-field"; import { MatFormFieldModule } from "@angular/material/form-field";
@@ -16,101 +16,106 @@ import { AircraftService } from "../../services/aircraft.service";
import { ServiceComm } from "../../services/service-comm.service"; import { ServiceComm } from "../../services/service-comm.service";
@Component({ @Component({
selector: "app-new-aircraft", selector: "app-new-aircraft",
templateUrl: "./new-aircraft.component.html", templateUrl: "./new-aircraft.component.html",
styleUrls: ["./new-aircraft.component.css"], styleUrls: ["./new-aircraft.component.css"],
imports: [ imports: [
TranslateModule, TranslateModule,
MatFormFieldModule, MatFormFieldModule,
ReactiveFormsModule, ReactiveFormsModule,
MatFormFieldModule, MatFormFieldModule,
ReactiveFormsModule, ReactiveFormsModule,
MatInputModule, MatInputModule,
MatButtonModule, MatButtonModule,
], ],
}) })
export class NewAircraftComponent implements OnInit { export class NewAircraftComponent implements OnInit {
public addForm: FormGroup; public addForm: FormGroup;
public imageError: string; public imageError: string;
private selectedFile: string; private selectedFile: string;
constructor( constructor(
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
private serviceApi: AircraftService, private serviceApi: AircraftService,
private translateService: TranslateService, private translateService: TranslateService,
) { ) {
this.addForm = new FormGroup( this.addForm = new FormGroup(
{ {
aircraftName: new FormControl("", Validators.required), aircraftName: new FormControl("", Validators.required),
}, },
{ updateOn: "blur" }, { updateOn: "blur" },
); );
} }
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.updateTitle();
}
onSubmit(formData) {
if (formData.invalid) {
return;
} }
this.serviceApi onSubmit(formData) {
.addAircraft(formData.aircraftName, this.selectedFile) if (formData.invalid) {
.subscribe(() => { return;
this.serviceComm.refreshData(AddAction.Aircraft); }
});
}
public onFileChanged(fileInput: any) { this.serviceApi
const file = fileInput.dataTransfer .addAircraft(formData.aircraftName, this.selectedFile)
? fileInput.dataTransfer.files[0] .subscribe(() => {
: fileInput.target.files[0]; this.serviceComm.refreshData(AddAction.Aircraft);
const allowed_types = ["image/png", "image/jpeg"]; });
const max_size = 20971520;
if (!allowed_types.includes(file.type)) {
this.imageError = "Only Images are allowed ( JPG | PNG )";
} else if (file.size > max_size) {
this.imageError = "Maximum size allowed is " + max_size / 1000 + "Mb";
} else {
const reader = new FileReader();
reader.onload = this.checkAndExtractDataToBase64.bind(this);
reader.readAsDataURL(fileInput.target.files[0]);
} }
}
private checkAndExtractDataToBase64(e: any) { public onFileChanged(fileInput: any) {
const max_height = 15200; const file = fileInput.dataTransfer
const max_width = 25600; ? fileInput.dataTransfer.files[0]
: fileInput.target.files[0];
const allowed_types = ["image/png", "image/jpeg"];
const max_size = 20971520;
const image = new Image(); if (!allowed_types.includes(file.type)) {
image.src = e.target.result; this.imageError = "Only Images are allowed ( JPG | PNG )";
image.onload = (rs) => { } else if (file.size > max_size) {
const img_height = rs.currentTarget["height"]; this.imageError =
const img_width = rs.currentTarget["width"]; "Maximum size allowed is " + max_size / 1000 + "Mb";
} else {
const reader = new FileReader();
reader.onload = this.checkAndExtractDataToBase64.bind(this);
reader.readAsDataURL(fileInput.target.files[0]);
}
}
if (img_height > max_height && img_width > max_width) { private checkAndExtractDataToBase64(e: any) {
this.imageError = const max_height = 15200;
"Maximum dimentions allowed " + max_height + "*" + max_width + "px"; const max_width = 25600;
} else {
const imgBase64Path = e.target.result;
this.selectedFile = imgBase64Path;
this.imageError = "OK";
}
};
}
private updateTitle() { const image = new Image();
this.translateService.get("NewAircraft_Title").subscribe((data) => { image.src = e.target.result;
this.serviceComm.updatedComponentTitle(data); image.onload = (rs) => {
}); const img_height = rs.currentTarget["height"];
} const img_width = rs.currentTarget["width"];
if (img_height > max_height && img_width > max_width) {
this.imageError =
"Maximum dimentions allowed " +
max_height +
"*" +
max_width +
"px";
} else {
const imgBase64Path = e.target.result;
this.selectedFile = imgBase64Path;
this.imageError = "OK";
}
};
}
private updateTitle() {
this.translateService.get("NewAircraft_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
} }

View File

@@ -1,7 +1,7 @@
.content { .content {
height: 90vh; height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
} }

View File

@@ -1,42 +1,42 @@
<div class="content"> <div class="content">
<form [formGroup]="addForm" (ngSubmit)="onSubmit(addForm.value)"> <form [formGroup]="addForm" (ngSubmit)="onSubmit(addForm.value)">
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Dropzone name</mat-label> <mat-label>Dropzone name</mat-label>
<input matInput type="text" formControlName="dzName" /> <input matInput type="text" formControlName="dzName" />
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>GPS</mat-label> <mat-label>GPS</mat-label>
<input matInput type="text" formControlName="gps" /> <input matInput type="text" formControlName="gps" />
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Address of the DZ</mat-label> <mat-label>Address of the DZ</mat-label>
<textarea matInput formControlName="address"></textarea> <textarea matInput formControlName="address"></textarea>
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Web site</mat-label> <mat-label>Web site</mat-label>
<input matInput type="text" formControlName="website" /> <input matInput type="text" formControlName="website" />
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Mail of contact</mat-label> <mat-label>Mail of contact</mat-label>
<input matInput type="text" formControlName="contactMail" /> <input matInput type="text" formControlName="contactMail" />
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-checkbox formControlName="isDz">Is a dropzone</mat-checkbox> <mat-checkbox formControlName="isDz">Is a dropzone</mat-checkbox>
</p> </p>
<p> <p>
<mat-checkbox formControlName="isTunnel">Is a tunnel</mat-checkbox> <mat-checkbox formControlName="isTunnel">Is a tunnel</mat-checkbox>
</p> </p>
<button type="submit" mat-raised-button color="accent">Add</button> <button type="submit" mat-raised-button color="accent">Add</button>
</form> </form>
</div> </div>

View File

@@ -1,9 +1,9 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
ReactiveFormsModule, ReactiveFormsModule,
Validators, Validators,
} from "@angular/forms"; } from "@angular/forms";
import { TranslateModule, TranslateService } from "@ngx-translate/core"; import { TranslateModule, TranslateService } from "@ngx-translate/core";
import { MatFormFieldModule } from "@angular/material/form-field"; import { MatFormFieldModule } from "@angular/material/form-field";
@@ -17,87 +17,87 @@ import { ServiceComm } from "../../services/service-comm.service";
import { DropzoneService } from "../../services/dropzone.service"; import { DropzoneService } from "../../services/dropzone.service";
@Component({ @Component({
selector: "app-new-drop-zone", selector: "app-new-drop-zone",
templateUrl: "./new-drop-zone.component.html", templateUrl: "./new-drop-zone.component.html",
styleUrls: ["./new-drop-zone.component.css"], styleUrls: ["./new-drop-zone.component.css"],
imports: [ imports: [
TranslateModule, TranslateModule,
MatFormFieldModule, MatFormFieldModule,
MatCheckboxModule, MatCheckboxModule,
ReactiveFormsModule, ReactiveFormsModule,
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,
MatButtonModule, MatButtonModule,
], ],
}) })
export class NewDropZoneComponent implements OnInit { export class NewDropZoneComponent implements OnInit {
public addForm: FormGroup; public addForm: FormGroup;
constructor( constructor(
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
private dropzoneService: DropzoneService, private dropzoneService: DropzoneService,
private translateService: TranslateService, private translateService: TranslateService,
) { ) {
this.addForm = new FormGroup( this.addForm = new FormGroup(
{ {
dzName: new FormControl("", Validators.required), dzName: new FormControl("", Validators.required),
gps: new FormControl("x.x,y.y", [ gps: new FormControl("x.x,y.y", [
Validators.required, Validators.required,
Validators.pattern("d+.d+,d+.d+"), Validators.pattern("d+.d+,d+.d+"),
]), ]),
address: new FormControl("", Validators.required), address: new FormControl("", Validators.required),
website: new FormControl("", Validators.required), website: new FormControl("", Validators.required),
contactMail: new FormControl("", [ contactMail: new FormControl("", [
Validators.required, Validators.required,
Validators.email, Validators.email,
]), ]),
isDz: new FormControl(true), isDz: new FormControl(true),
isTunnel: new FormControl(false), isTunnel: new FormControl(false),
}, },
{ updateOn: "blur" }, { updateOn: "blur" },
); );
} }
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.updateTitle();
}
onSubmit(formData) {
const splitGps: Array<string> = formData.gps.split(",");
let dzType: Array<string> = new Array<string>();
if (formData.isDz === true) {
dzType.push("dz");
}
if (formData.isTunnel === true) {
dzType.push("tunnel");
} }
this.dropzoneService onSubmit(formData) {
.addDropZone( const splitGps: Array<string> = formData.gps.split(",");
splitGps[0], let dzType: Array<string> = new Array<string>();
splitGps[1],
formData.dzName,
formData.address,
formData.website,
formData.contactMail,
dzType,
false,
)
.subscribe(() => {
this.serviceComm.refreshData(AddAction.Dropzone);
});
}
private updateTitle() { if (formData.isDz === true) {
this.translateService.get("NewDz_Title").subscribe((data) => { dzType.push("dz");
this.serviceComm.updatedComponentTitle(data); }
}); if (formData.isTunnel === true) {
} dzType.push("tunnel");
}
this.dropzoneService
.addDropZone(
splitGps[0],
splitGps[1],
formData.dzName,
formData.address,
formData.website,
formData.contactMail,
dzType,
false,
)
.subscribe(() => {
this.serviceComm.refreshData(AddAction.Dropzone);
});
}
private updateTitle() {
this.translateService.get("NewDz_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
} }

View File

@@ -1,7 +1,7 @@
.content { .content {
height: 90vh; height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
} }

View File

@@ -1,48 +1,48 @@
<div class="content"> <div class="content">
<form [formGroup]="addForm" (ngSubmit)="onSubmit(addForm.value)"> <form [formGroup]="addForm" (ngSubmit)="onSubmit(addForm.value)">
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Name of gear</mat-label> <mat-label>Name of gear</mat-label>
<input matInput type="text" formControlName="name"> <input matInput type="text" formControlName="name" />
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Manufacturer</mat-label> <mat-label>Manufacturer</mat-label>
<input matInput type="text" formControlName="manufacturer"> <input matInput type="text" formControlName="manufacturer" />
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Min size of canopy</mat-label> <mat-label>Min size of canopy</mat-label>
<input matInput type="text" formControlName="minSize"> <input matInput type="text" formControlName="minSize" />
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Max size of canopy</mat-label> <mat-label>Max size of canopy</mat-label>
<input matInput type="text" formControlName="maxSize"> <input matInput type="text" formControlName="maxSize" />
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>AAD system</mat-label> <mat-label>AAD system</mat-label>
<input matInput type="text" formControlName="aad"> <input matInput type="text" formControlName="aad" />
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Main Canopy</mat-label> <mat-label>Main Canopy</mat-label>
<input matInput type="text" formControlName="mainCanopy"> <input matInput type="text" formControlName="mainCanopy" />
</mat-form-field> </mat-form-field>
</p> </p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Reserve canopy</mat-label> <mat-label>Reserve canopy</mat-label>
<input matInput type="text" formControlName="reserveCanopy"> <input matInput type="text" formControlName="reserveCanopy" />
</mat-form-field> </mat-form-field>
</p> </p>
<button mat-raised-button color="accent" type="submit">Add</button> <button mat-raised-button color="accent" type="submit">Add</button>
</form> </form>
</div> </div>

View File

@@ -1,9 +1,9 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
ReactiveFormsModule, ReactiveFormsModule,
Validators, Validators,
} from "@angular/forms"; } from "@angular/forms";
import { MatFormFieldModule } from "@angular/material/form-field"; import { MatFormFieldModule } from "@angular/material/form-field";
import { TranslateModule, TranslateService } from "@ngx-translate/core"; import { TranslateModule, TranslateService } from "@ngx-translate/core";
@@ -15,85 +15,85 @@ import { GearService } from "../../services/gear.service";
import { AddAction } from "../../models/add-action.enum"; import { AddAction } from "../../models/add-action.enum";
@Component({ @Component({
selector: "app-new-gear", selector: "app-new-gear",
templateUrl: "./new-gear.component.html", templateUrl: "./new-gear.component.html",
styleUrls: ["./new-gear.component.css"], styleUrls: ["./new-gear.component.css"],
imports: [ imports: [
TranslateModule, TranslateModule,
MatFormFieldModule, MatFormFieldModule,
ReactiveFormsModule, ReactiveFormsModule,
ReactiveFormsModule, ReactiveFormsModule,
MatInputModule, MatInputModule,
MatButtonModule, MatButtonModule,
], ],
}) })
export class NewGearComponent implements OnInit { export class NewGearComponent implements OnInit {
public addForm: FormGroup; public addForm: FormGroup;
constructor( constructor(
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
private serviceApi: GearService, private serviceApi: GearService,
private translateService: TranslateService, private translateService: TranslateService,
) { ) {
this.addForm = new FormGroup( this.addForm = new FormGroup(
{ {
name: new FormControl("", Validators.required), name: new FormControl("", Validators.required),
manufacturer: new FormControl("", Validators.required), manufacturer: new FormControl("", Validators.required),
minSize: new FormControl("", [ minSize: new FormControl("", [
Validators.required, Validators.required,
Validators.min(60), Validators.min(60),
Validators.max(320), Validators.max(320),
]), ]),
maxSize: new FormControl("", [ maxSize: new FormControl("", [
Validators.required, Validators.required,
Validators.min(60), Validators.min(60),
Validators.max(320), Validators.max(320),
]), ]),
aad: new FormControl("", Validators.required), aad: new FormControl("", Validators.required),
mainCanopy: new FormControl("", [ mainCanopy: new FormControl("", [
Validators.required, Validators.required,
Validators.min(60), Validators.min(60),
Validators.max(320), Validators.max(320),
]), ]),
reserveCanopy: new FormControl("", [ reserveCanopy: new FormControl("", [
Validators.required, Validators.required,
Validators.min(60), Validators.min(60),
Validators.max(320), Validators.max(320),
]), ]),
}, },
{ updateOn: "blur" }, { updateOn: "blur" },
); );
} }
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.updateTitle(); onSubmit(formData) {
} this.serviceApi
.addGear(
formData.name,
formData.manufacturer,
+formData.minSize,
+formData.maxSize,
formData.aad,
formData.mainCanopy,
formData.reserveCanopy,
)
.subscribe(() => {
this.serviceComm.refreshData(AddAction.Gear);
});
}
onSubmit(formData) { private updateTitle() {
this.serviceApi this.translateService.get("NewGear_Title").subscribe((data) => {
.addGear( this.serviceComm.updatedComponentTitle(data);
formData.name, });
formData.manufacturer, }
+formData.minSize,
+formData.maxSize,
formData.aad,
formData.mainCanopy,
formData.reserveCanopy,
)
.subscribe(() => {
this.serviceComm.refreshData(AddAction.Gear);
});
}
private updateTitle() {
this.translateService.get("NewGear_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
} }

View File

@@ -1,7 +1,7 @@
.content { .content {
height: 90vh; height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
} }

View File

@@ -1,12 +1,12 @@
<div class="content"> <div class="content">
<form [formGroup]="addForm" (ngSubmit)="onSubmit(addForm.value)"> <form [formGroup]="addForm" (ngSubmit)="onSubmit(addForm.value)">
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Jump type name</mat-label> <mat-label>Jump type name</mat-label>
<input matInput type="text" formControlName="jumptypeName"> <input matInput type="text" formControlName="jumptypeName" />
</mat-form-field> </mat-form-field>
</p> </p>
<button type="submit" mat-raised-button color="accent">Add</button> <button type="submit" mat-raised-button color="accent">Add</button>
</form> </form>
</div> </div>

View File

@@ -1,9 +1,9 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
ReactiveFormsModule, ReactiveFormsModule,
Validators, Validators,
} from "@angular/forms"; } from "@angular/forms";
import { TranslateModule, TranslateService } from "@ngx-translate/core"; import { TranslateModule, TranslateService } from "@ngx-translate/core";
import { MatFormFieldModule } from "@angular/material/form-field"; import { MatFormFieldModule } from "@angular/material/form-field";
@@ -15,52 +15,54 @@ import { ServiceComm } from "../../services/service-comm.service";
import { JumpTypeService } from "../../services/jump-type.service"; import { JumpTypeService } from "../../services/jump-type.service";
@Component({ @Component({
selector: "app-new-jump-type", selector: "app-new-jump-type",
templateUrl: "./new-jump-type.component.html", templateUrl: "./new-jump-type.component.html",
styleUrls: ["./new-jump-type.component.css"], styleUrls: ["./new-jump-type.component.css"],
imports: [ imports: [
TranslateModule, TranslateModule,
MatFormFieldModule, MatFormFieldModule,
ReactiveFormsModule, ReactiveFormsModule,
MatInputModule, MatInputModule,
MatButtonModule, MatButtonModule,
], ],
}) })
export class NewJumpTypeComponent implements OnInit { export class NewJumpTypeComponent implements OnInit {
public addForm: FormGroup; public addForm: FormGroup;
constructor( constructor(
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
private jumpTypeService: JumpTypeService, private jumpTypeService: JumpTypeService,
private translateService: TranslateService, private translateService: TranslateService,
) { ) {
this.addForm = new FormGroup( this.addForm = new FormGroup(
{ {
jumptypeName: new FormControl("", Validators.required), jumptypeName: new FormControl("", Validators.required),
}, },
{ updateOn: "blur" }, { updateOn: "blur" },
); );
} }
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.updateTitle(); onSubmit(formData) {
} this.jumpTypeService
.addJumpType(formData.jumptypeName)
.subscribe(() => {
this.serviceComm.refreshData(AddAction.JumpType);
});
}
onSubmit(formData) { private updateTitle() {
this.jumpTypeService.addJumpType(formData.jumptypeName).subscribe(() => { this.translateService.get("NewJumpType_Title").subscribe((data) => {
this.serviceComm.refreshData(AddAction.JumpType); this.serviceComm.updatedComponentTitle(data);
}); });
} }
private updateTitle() {
this.translateService.get("NewJumpType_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
} }

View File

@@ -1,17 +1,17 @@
.formNewJumps { .formNewJumps {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 150px; min-width: 150px;
max-width: 500px; max-width: 500px;
width: 100%; width: 100%;
} }
.content { .content {
min-height: 90vh; min-height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
padding-top: 25px; padding-top: 25px;
} }

View File

@@ -1,270 +1,276 @@
<div class="content"> <div class="content">
<div> <div>
<button <button
mat-raised-button mat-raised-button
color="accent" color="accent"
[routerLink]="['/jumps']" [routerLink]="['/jumps']"
[routerLinkActive]="['active']" [routerLinkActive]="['active']"
skipLocationChange skipLocationChange
>
{{ "NewJump_GoToJump" | translate }}
</button>
<p>
<mat-checkbox [(ngModel)]="resetForm" labelPosition="before">{{
"NewJump_ResetForm" | translate
}}</mat-checkbox>
</p>
</div>
<form
class="formNewJumps"
(ngSubmit)="onFormSubmit()"
*ngIf="notLoadingToDisplay(); else loading"
>
<mat-form-field>
<mat-label>{{ "NewJump_ChooseJumpType" | translate }}</mat-label>
<input
type="text"
matInput
[matAutocomplete]="autoJumpType"
[(ngModel)]="selectedJumpType"
name="selectedJumpType"
/>
<mat-autocomplete
#autoJumpType="matAutocomplete"
[displayWith]="displayNameFn"
>
<mat-option *ngFor="let jumpType of listOfJumpType" [value]="jumpType">
{{ jumpType.name }}
</mat-option>
</mat-autocomplete>
<button
*ngIf="selectedJumpType"
matSuffix
mat-icon-button
aria-label="Clear"
(click)="selectedJumpType = undefined"
>
<mat-icon>close</mat-icon>
</button>
</mat-form-field>
<mat-form-field>
<mat-label>{{ "NewJump_ChooseAircraft" | translate }}</mat-label>
<input
type="text"
matInput
[matAutocomplete]="autoAircraft"
[(ngModel)]="selectedAircraft"
name="selectedAircraft"
/>
<mat-autocomplete
#autoAircraft="matAutocomplete"
[displayWith]="displayNameFn"
>
<mat-option *ngFor="let aircraft of listOfAircraft" [value]="aircraft">
{{ aircraft.name }}
</mat-option>
</mat-autocomplete>
<button
*ngIf="selectedAircraft"
matSuffix
mat-icon-button
aria-label="Clear"
(click)="selectedAircraft = undefined"
>
<mat-icon>close</mat-icon>
</button>
</mat-form-field>
<mat-form-field>
<mat-label>{{ "NewJump_ChooseDz" | translate }}</mat-label>
<input
type="text"
matInput
[matAutocomplete]="autoDropZone"
[(ngModel)]="selectedDz"
(ngModelChange)="onChangeDz($event)"
name="selectedDz"
/>
<mat-autocomplete
#autoDropZone="matAutocomplete"
[displayWith]="displayNameFn"
>
<mat-option
*ngFor="let dropZone of listOfFilteredDropZone"
[value]="dropZone"
> >
{{ dropZone.name }} {{ "NewJump_GoToJump" | translate }}
<mat-icon </button>
aria-hidden="false" <p>
aria-label="Favorite" <mat-checkbox [(ngModel)]="resetForm" labelPosition="before">{{
*ngIf="dropZone.isFavorite === true" "NewJump_ResetForm" | translate
color="primary" }}</mat-checkbox>
> </p>
favorite</mat-icon </div>
>
</mat-option>
</mat-autocomplete>
<button
*ngIf="selectedDz"
matSuffix
mat-icon-button
aria-label="Clear"
(click)="resetDz()"
>
<mat-icon>close</mat-icon>
</button>
</mat-form-field>
<mat-form-field> @if (notLoadingToDisplay()) {
<mat-label>{{ "NewJump_ChooseGear" | translate }}</mat-label> <form class="formNewJumps" (ngSubmit)="onFormSubmit()">
<input <mat-form-field>
type="text" <mat-label>{{
matInput "NewJump_ChooseJumpType" | translate
[matAutocomplete]="autoGear" }}</mat-label>
[(ngModel)]="selectedGear" <input
name="selectedGear" type="text"
/> matInput
<mat-autocomplete [matAutocomplete]="autoJumpType"
#autoGear="matAutocomplete" [(ngModel)]="selectedJumpType"
[displayWith]="displayGearFn" name="selectedJumpType"
> />
<mat-option *ngFor="let gear of listOfGear" [value]="gear"> <mat-autocomplete
{{ gear.name }} ({{ gear.mainCanopy }}) #autoJumpType="matAutocomplete"
</mat-option> [displayWith]="displayNameFn"
</mat-autocomplete> >
<button @for (jumpType of listOfJumpType; track jumpType) {
*ngIf="selectedGear" <mat-option [value]="jumpType">
matSuffix {{ jumpType.name }}
mat-icon-button </mat-option>
aria-label="Clear" }
(click)="selectedGear = undefined" </mat-autocomplete>
> @if (selectedJumpType) {
<mat-icon>close</mat-icon> <button
</button> matSuffix
</mat-form-field> mat-icon-button
aria-label="Clear"
<mat-checkbox [(ngModel)]="withCutaway" name="withCutaway">{{ (click)="selectedJumpType = undefined"
"NewJump_Cutaway" | translate >
}}</mat-checkbox> <mat-icon svgIcon="close"></mat-icon>
<mat-checkbox [(ngModel)]="isSpecial" name="isSpecial">{{ </button>
"NewJump_Special" | translate }
}}</mat-checkbox> </mat-form-field>
<mat-form-field>
<mat-form-field> <mat-label>{{
<input "NewJump_ChooseAircraft" | translate
matInput }}</mat-label>
[matDatepicker]="beginDateDp" <input
[(ngModel)]="beginDate" type="text"
name="beginDate" matInput
disabled [matAutocomplete]="autoAircraft"
(ngModelChange)="onChangeBeginDate($event)" [(ngModel)]="selectedAircraft"
/> name="selectedAircraft"
<mat-datepicker-toggle />
matSuffix <mat-autocomplete
[for]="beginDateDp" #autoAircraft="matAutocomplete"
></mat-datepicker-toggle> [displayWith]="displayNameFn"
<mat-datepicker #beginDateDp disabled="false"></mat-datepicker> >
</mat-form-field> @for (aircraft of listOfAircraft; track aircraft) {
<mat-option [value]="aircraft">
<mat-form-field> {{ aircraft.name }}
<input </mat-option>
matInput }
[matDatepicker]="endDateDp" </mat-autocomplete>
[(ngModel)]="endDate" @if (selectedAircraft) {
name="endDate" <button
disabled matSuffix
/> mat-icon-button
<mat-datepicker-toggle aria-label="Clear"
matSuffix (click)="selectedAircraft = undefined"
[for]="endDateDp" >
></mat-datepicker-toggle> <mat-icon svgIcon="close"></mat-icon>
<mat-datepicker #endDateDp disabled="false"></mat-datepicker> </button>
</mat-form-field> }
</mat-form-field>
<mat-form-field> <mat-form-field>
<input <mat-label>{{ "NewJump_ChooseDz" | translate }}</mat-label>
matInput <input
placeholder="{{ 'NewJump_ExitAlt' | translate }}" type="text"
[(ngModel)]="exitAltitude" matInput
name="exitAltitude" [matAutocomplete]="autoDropZone"
type="number" [(ngModel)]="selectedDz"
/> (ngModelChange)="onChangeDz($event)"
<button name="selectedDz"
*ngIf="exitAltitude" />
matSuffix <mat-autocomplete
mat-icon-button #autoDropZone="matAutocomplete"
aria-label="Clear" [displayWith]="displayNameFn"
(click)="exitAltitude = undefined" >
> @for (dropZone of listOfFilteredDropZone; track dropZone) {
<mat-icon>close</mat-icon> <mat-option [value]="dropZone">
</button> {{ dropZone.name }}
</mat-form-field> @if (dropZone.isFavorite === true) {
<mat-form-field> <mat-icon
<input aria-hidden="false"
matInput aria-label="Favorite"
placeholder="{{ 'NewJump_DeployAlt' | translate }}" color="primary"
[(ngModel)]="deployAltitude" svgIcon="favorite"
name="deployAltitude" >
type="number" </mat-icon>
/> }
<button </mat-option>
*ngIf="deployAltitude" }
matSuffix </mat-autocomplete>
mat-icon-button @if (selectedDz) {
aria-label="Clear" <button
(click)="deployAltitude = undefined" matSuffix
> mat-icon-button
<mat-icon>close</mat-icon> aria-label="Clear"
</button> (click)="resetDz()"
</mat-form-field> >
<mat-icon svgIcon="close"></mat-icon>
<mat-form-field> </button>
<input }
matInput </mat-form-field>
placeholder="{{ 'NewJump_Count' | translate }}" <mat-form-field>
[(ngModel)]="countOfJumps" <mat-label>{{ "NewJump_ChooseGear" | translate }}</mat-label>
name="countOfJumps" <input
type="number" type="text"
/> matInput
<button [matAutocomplete]="autoGear"
*ngIf="countOfJumps" [(ngModel)]="selectedGear"
matSuffix name="selectedGear"
mat-icon-button />
aria-label="Clear" <mat-autocomplete
(click)="countOfJumps = undefined" #autoGear="matAutocomplete"
> [displayWith]="displayGearFn"
<mat-icon>close</mat-icon> >
</button> @for (gear of listOfGear; track gear) {
</mat-form-field> <mat-option [value]="gear">
{{ gear.name }} ({{ gear.mainCanopy }})
<mat-form-field> </mat-option>
<textarea }
matInput </mat-autocomplete>
placeholder="{{ 'NewJump_Comments' | translate }}" @if (selectedGear) {
[(ngModel)]="comments" <button
name="comments" matSuffix
type="text" mat-icon-button
></textarea> aria-label="Clear"
<button (click)="selectedGear = undefined"
*ngIf="comments" >
matSuffix <mat-icon svgIcon="close"></mat-icon>
mat-icon-button </button>
aria-label="Clear" }
(click)="comments = undefined" </mat-form-field>
> <mat-checkbox [(ngModel)]="withCutaway" name="withCutaway">{{
<mat-icon>close</mat-icon> "NewJump_Cutaway" | translate
</button> }}</mat-checkbox>
</mat-form-field> <mat-checkbox [(ngModel)]="isSpecial" name="isSpecial">{{
"NewJump_Special" | translate
<br /> }}</mat-checkbox>
<button mat-raised-button color="accent" *ngIf="isValidatedForm()"> <mat-form-field>
{{ "NewJump_Submit" | translate }} <input
</button> matInput
</form> [matDatepicker]="beginDateDp"
[(ngModel)]="beginDate"
<ng-template #loading> name="beginDate"
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner> disabled
</ng-template> (ngModelChange)="onChangeBeginDate($event)"
/>
<mat-datepicker-toggle
matSuffix
[for]="beginDateDp"
></mat-datepicker-toggle>
<mat-datepicker #beginDateDp disabled="false"></mat-datepicker>
</mat-form-field>
<mat-form-field>
<input
matInput
[matDatepicker]="endDateDp"
[(ngModel)]="endDate"
name="endDate"
disabled
/>
<mat-datepicker-toggle
matSuffix
[for]="endDateDp"
></mat-datepicker-toggle>
<mat-datepicker #endDateDp disabled="false"></mat-datepicker>
</mat-form-field>
<mat-form-field>
<input
matInput
placeholder="{{ 'NewJump_ExitAlt' | translate }}"
[(ngModel)]="exitAltitude"
name="exitAltitude"
type="number"
/>
@if (exitAltitude) {
<button
matSuffix
mat-icon-button
aria-label="Clear"
(click)="exitAltitude = undefined"
>
<mat-icon svgIcon="close"></mat-icon>
</button>
}
</mat-form-field>
<mat-form-field>
<input
matInput
placeholder="{{ 'NewJump_DeployAlt' | translate }}"
[(ngModel)]="deployAltitude"
name="deployAltitude"
type="number"
/>
@if (deployAltitude) {
<button
matSuffix
mat-icon-button
aria-label="Clear"
(click)="deployAltitude = undefined"
>
<mat-icon svgIcon="close"></mat-icon>
</button>
}
</mat-form-field>
<mat-form-field>
<input
matInput
placeholder="{{ 'NewJump_Count' | translate }}"
[(ngModel)]="countOfJumps"
name="countOfJumps"
type="number"
/>
@if (countOfJumps) {
<button
matSuffix
mat-icon-button
aria-label="Clear"
(click)="countOfJumps = undefined"
>
<mat-icon svgIcon="close"></mat-icon>
</button>
}
</mat-form-field>
<mat-form-field>
<textarea
matInput
placeholder="{{ 'NewJump_Comments' | translate }}"
[(ngModel)]="comments"
name="comments"
type="text"
></textarea>
@if (comments) {
<button
matSuffix
mat-icon-button
aria-label="Clear"
(click)="comments = undefined"
>
<mat-icon svgIcon="close"></mat-icon>
</button>
}
</mat-form-field>
<br />
@if (this.isValidatedForm()) {
<button mat-raised-button color="accent">
{{ "NewJump_Submit" | translate }}
</button>
}
</form>
} @else {
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner>
}
</div> </div>

View File

@@ -2,12 +2,12 @@ import { Component, OnInit } from "@angular/core";
import { RouterLink, RouterModule } from "@angular/router"; import { RouterLink, RouterModule } from "@angular/router";
import { formatDate } from "@angular/common"; import { formatDate } from "@angular/common";
import { import {
DateAdapter, DateAdapter,
MAT_DATE_FORMATS, MAT_DATE_FORMATS,
NativeDateAdapter, NativeDateAdapter,
} from "@angular/material/core"; } from "@angular/material/core";
import { TranslateModule, TranslateService } from "@ngx-translate/core"; import { TranslateModule, TranslateService } from "@ngx-translate/core";
import { CommonModule } from "@angular/common";
import { MatIconModule } from "@angular/material/icon"; import { MatIconModule } from "@angular/material/icon";
import { MatOptionModule } from "@angular/material/core"; import { MatOptionModule } from "@angular/material/core";
import { MatFormFieldModule } from "@angular/material/form-field"; import { MatFormFieldModule } from "@angular/material/form-field";
@@ -34,245 +34,246 @@ import { GearService } from "../../services/gear.service";
import { StatsService } from "../../services/stats.service"; import { StatsService } from "../../services/stats.service";
export const PICK_FORMATS = { export const PICK_FORMATS = {
parse: { dateInput: "yy MM dd" }, parse: { dateInput: "yy MM dd" },
display: { display: {
dateInput: "yyyy-MM-dd", dateInput: "yyyy-MM-dd",
monthYearLabel: "yyyy MMM", monthYearLabel: "yyyy MMM",
dateA11yLabel: "yyyy MM dd", dateA11yLabel: "yyyy MM dd",
monthYearA11yLabel: "yyyy MMMM", monthYearA11yLabel: "yyyy MMMM",
}, },
}; };
class PickDateAdapter extends NativeDateAdapter { class PickDateAdapter extends NativeDateAdapter {
override format(date: Date, displayFormat: Object): string { override format(date: Date, displayFormat: Object): string {
return formatDate(date, displayFormat.toString(), "en"); return formatDate(date, displayFormat.toString(), "en");
} }
} }
@Component({ @Component({
selector: "app-new-jump", selector: "app-new-jump",
templateUrl: "./new-jump.component.html", templateUrl: "./new-jump.component.html",
styleUrls: ["./new-jump.component.css"], styleUrls: ["./new-jump.component.css"],
providers: [ providers: [
{ provide: DateAdapter, useClass: PickDateAdapter }, { provide: DateAdapter, useClass: PickDateAdapter },
{ provide: MAT_DATE_FORMATS, useValue: PICK_FORMATS }, { provide: MAT_DATE_FORMATS, useValue: PICK_FORMATS },
], ],
imports: [ imports: [
TranslateModule, TranslateModule,
CommonModule, RouterLink,
RouterLink, FormsModule,
FormsModule, RouterModule,
RouterModule, MatIconModule,
MatIconModule, MatOptionModule,
MatOptionModule, MatFormFieldModule,
MatFormFieldModule, MatCheckboxModule,
MatCheckboxModule, MatAutocompleteModule,
MatAutocompleteModule, MatDatepickerModule,
MatDatepickerModule, MatProgressSpinnerModule,
MatProgressSpinnerModule, MatFormFieldModule,
MatFormFieldModule, ReactiveFormsModule,
ReactiveFormsModule, MatInputModule,
MatInputModule, MatButtonModule,
MatButtonModule, ],
],
}) })
export class NewJumpComponent implements OnInit { export class NewJumpComponent implements OnInit {
public beginDate: Date; public beginDate: Date;
public endDate: Date; public endDate: Date;
public exitAltitude: number; public exitAltitude: number;
public deployAltitude: number; public deployAltitude: number;
public countOfJumps: number; public countOfJumps: number;
public selectedDz: DropZoneResp; public selectedDz: DropZoneResp;
public selectedGear: GearResp; public selectedGear: GearResp;
public selectedAircraft: AircraftResp; public selectedAircraft: AircraftResp;
public selectedJumpType: JumpTypeResp; public selectedJumpType: JumpTypeResp;
public withCutaway: boolean; public withCutaway: boolean;
public isSpecial: boolean; public isSpecial: boolean;
public listOfJumpType: Array<JumpTypeResp>; public listOfJumpType: Array<JumpTypeResp>;
public listOfAircraft: Array<AircraftResp>; public listOfAircraft: Array<AircraftResp>;
public listOfFilteredDropZone: Array<DropZoneResp>; public listOfFilteredDropZone: Array<DropZoneResp>;
public listOfGear: Array<GearResp>; public listOfGear: Array<GearResp>;
public comments: string; public comments: string;
public resetForm: boolean; public resetForm: boolean;
private countDatasLoaded: number; private countDatasLoaded: number;
private pendingAddRequest: boolean; private pendingAddRequest: boolean;
private listOfDropZone: Array<DropZoneResp>; private listOfDropZone: Array<DropZoneResp>;
public maxDate: Date; public maxDate: Date;
constructor( constructor(
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
private serviceJump: JumpService, private serviceJump: JumpService,
private serviceJumpType: JumpTypeService, private serviceJumpType: JumpTypeService,
private serviceAircraft: AircraftService, private serviceAircraft: AircraftService,
private serviceDropzone: DropzoneService, private serviceDropzone: DropzoneService,
private serviceGear: GearService, private serviceGear: GearService,
private dateService: DateService, private dateService: DateService,
private translateService: TranslateService, private translateService: TranslateService,
private statsService: StatsService private statsService: StatsService,
) {} ) {}
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.maxDate = this.dateService.addDays(new Date(), 1); this.maxDate = this.dateService.addDays(new Date(), 1);
this.pendingAddRequest = false; this.pendingAddRequest = false;
this.initForm(); this.initForm();
this.getListOfJumpTypes(); this.getListOfJumpTypes();
} }
onFormSubmit() { onFormSubmit() {
this.pendingAddRequest = true; if (this.isValidatedForm()) {
this.pendingAddRequest = true;
this.serviceJump this.serviceJump
.addListOfJump( .addListOfJump(
this.selectedJumpType.id, this.selectedJumpType.id,
this.selectedAircraft.id, this.selectedAircraft.id,
this.selectedDz.id, this.selectedDz.id,
this.selectedGear.id, this.selectedGear.id,
this.withCutaway === undefined ? false : this.withCutaway, this.withCutaway === undefined ? false : this.withCutaway,
this.beginDate, this.beginDate,
this.endDate, this.endDate,
this.exitAltitude, this.exitAltitude,
this.deployAltitude, this.deployAltitude,
this.countOfJumps, this.countOfJumps,
this.comments, this.comments,
this.isSpecial === undefined ? false : this.isSpecial this.isSpecial === undefined ? false : this.isSpecial,
) )
.subscribe(() => { .subscribe(() => {
this.statsService.resetStats(); this.statsService.resetStats();
this.comments = undefined;
this.withCutaway = false;
this.isSpecial = false;
if (this.resetForm === true) {
this.initForm();
}
this.pendingAddRequest = false;
});
}
}
public isValidatedForm(): boolean {
return (
this.selectedDz !== undefined &&
this.selectedDz.id !== undefined &&
this.selectedGear !== undefined &&
this.selectedGear.id !== undefined &&
this.selectedAircraft !== undefined &&
this.selectedAircraft.id !== undefined &&
this.selectedJumpType !== undefined &&
this.selectedJumpType.id !== undefined &&
this.exitAltitude !== undefined &&
typeof this.exitAltitude === "number" &&
this.deployAltitude !== undefined &&
typeof this.deployAltitude === "number" &&
this.countOfJumps !== undefined &&
typeof this.countOfJumps === "number"
);
}
private getListOfJumpTypes() {
this.serviceJumpType.getListOfJumpTypes().subscribe((data) => {
data.sort((a, b) => a.name.localeCompare(b.name));
this.listOfJumpType = data;
this.countDatasLoaded = 1;
this.getListOfAircrafts();
this.getListOfDropZones();
this.getListOfGears();
});
}
private getListOfAircrafts() {
this.serviceAircraft.getListOfAircrafts(true).subscribe((data) => {
data.sort((a, b) => a.name.localeCompare(b.name));
this.listOfAircraft = data;
this.countDatasLoaded++;
});
}
private getListOfDropZones() {
this.serviceDropzone.getListOfDropZones(true).subscribe((data) => {
data.sort(
(a, b) =>
(b.isFavorite ? 1 : 0) - (a.isFavorite ? 1 : 0) ||
a.name.localeCompare(b.name),
);
this.listOfDropZone = data;
this.listOfFilteredDropZone = data;
this.countDatasLoaded++;
});
}
private getListOfGears() {
this.serviceGear.getListOfGears().subscribe((data) => {
data.sort((a, b) => b.id - a.id);
this.listOfGear = data;
this.countDatasLoaded++;
});
}
private initForm() {
this.endDate = new Date();
this.endDate.setHours(0, 0, 0, 0);
this.beginDate = this.dateService.addDays(this.endDate, -1);
this.exitAltitude = 4000;
this.deployAltitude = 1000;
this.countOfJumps = 1;
this.selectedDz = undefined;
this.selectedGear = undefined;
this.selectedAircraft = undefined;
this.selectedJumpType = undefined;
this.listOfFilteredDropZone = this.listOfDropZone;
this.comments = undefined; this.comments = undefined;
this.withCutaway = false; this.withCutaway = false;
this.isSpecial = false; this.isSpecial = false;
if (this.resetForm === true) {
this.initForm();
}
this.pendingAddRequest = false;
});
}
public isValidatedForm(): boolean {
return (
this.selectedDz !== undefined &&
this.selectedDz.id !== undefined &&
this.selectedGear !== undefined &&
this.selectedGear.id !== undefined &&
this.selectedAircraft !== undefined &&
this.selectedAircraft.id !== undefined &&
this.selectedJumpType !== undefined &&
this.selectedJumpType.id !== undefined &&
this.exitAltitude !== undefined &&
typeof this.exitAltitude === "number" &&
this.deployAltitude !== undefined &&
typeof this.deployAltitude === "number" &&
this.countOfJumps !== undefined &&
typeof this.countOfJumps === "number"
);
}
private getListOfJumpTypes() {
this.serviceJumpType.getListOfJumpTypes().subscribe((data) => {
data.sort((a, b) => a.name.localeCompare(b.name));
this.listOfJumpType = data;
this.countDatasLoaded = 1;
this.getListOfAircrafts();
this.getListOfDropZones();
this.getListOfGears();
});
}
private getListOfAircrafts() {
this.serviceAircraft.getListOfAircrafts(true).subscribe((data) => {
data.sort((a, b) => a.name.localeCompare(b.name));
this.listOfAircraft = data;
this.countDatasLoaded++;
});
}
private getListOfDropZones() {
this.serviceDropzone.getListOfDropZones(true).subscribe((data) => {
data.sort(
(a, b) =>
(b.isFavorite ? 1 : 0) - (a.isFavorite ? 1 : 0) ||
a.name.localeCompare(b.name)
);
this.listOfDropZone = data;
this.listOfFilteredDropZone = data;
this.countDatasLoaded++;
});
}
private getListOfGears() {
this.serviceGear.getListOfGears().subscribe((data) => {
data.sort((a, b) => b.id - a.id);
this.listOfGear = data;
this.countDatasLoaded++;
});
}
private initForm() {
this.endDate = new Date();
this.endDate.setHours(0, 0, 0, 0);
this.beginDate = this.dateService.addDays(this.endDate, -1);
this.exitAltitude = 4000;
this.deployAltitude = 1000;
this.countOfJumps = 1;
this.selectedDz = undefined;
this.selectedGear = undefined;
this.selectedAircraft = undefined;
this.selectedJumpType = undefined;
this.listOfFilteredDropZone = this.listOfDropZone;
this.comments = undefined;
this.withCutaway = false;
this.isSpecial = false;
}
public displayNameFn(data?: any): string | undefined {
return data ? data.name : undefined;
}
public displayGearFn(data?: GearResp): string | undefined {
return data ? `${data.name} (${data.mainCanopy})` : undefined;
}
public onChangeDz(event: any) {
let filterValue: string;
if (event.id === undefined) {
filterValue = event.toLowerCase();
this.listOfFilteredDropZone = this.listOfDropZone;
this.listOfFilteredDropZone = this.listOfFilteredDropZone.filter(
(option) => option.name.toLowerCase().includes(filterValue)
);
} }
}
public onChangeBeginDate(event: any) { public displayNameFn(data?: any): string | undefined {
this.endDate = event; return data ? data.name : undefined;
} }
public displayGearFn(data?: GearResp): string | undefined {
return data ? `${data.name} (${data.mainCanopy})` : undefined;
}
public notLoadingToDisplay(): boolean { public onChangeDz(event: any) {
return !(this.pendingAddRequest || this.countDatasLoaded !== 4); let filterValue: string;
}
public resetDz() { if (event.id === undefined) {
this.selectedDz = undefined; filterValue = event.toLowerCase();
this.onChangeDz("");
}
private updateTitle() { this.listOfFilteredDropZone = this.listOfDropZone;
this.translateService.get("NewJump_Title").subscribe((data) => { this.listOfFilteredDropZone = this.listOfFilteredDropZone.filter(
this.serviceComm.updatedComponentTitle(data); (option) => option.name.toLowerCase().includes(filterValue),
}); );
} }
}
public onChangeBeginDate(event: any) {
this.endDate = event;
}
public notLoadingToDisplay(): boolean {
return !(this.pendingAddRequest || this.countDatasLoaded !== 4);
}
public resetDz() {
this.selectedDz = undefined;
this.onChangeDz("");
}
private updateTitle() {
this.translateService.get("NewJump_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
} }

View File

@@ -1,18 +1,17 @@
.formNewJumps { .formNewJumps {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 150px; min-width: 150px;
max-width: 500px; max-width: 500px;
width: 100%; width: 100%;
} }
.content { .content {
min-height: 90vh; min-height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
padding-top: 25px; padding-top: 25px;
} }

View File

@@ -1,145 +1,153 @@
<div class="content"> <div class="content">
<div> <div>
<button <button
mat-raised-button mat-raised-button
color="accent" color="accent"
[routerLink]="['/tunnelFlights']" [routerLink]="['/tunnelFlights']"
[routerLinkActive]="['active']" [routerLinkActive]="['active']"
skipLocationChange skipLocationChange
>
{{ "NewTunnelFlight_GoToJump" | translate }}
</button>
</div>
<form
class="formNewJumps"
(ngSubmit)="onFormSubmit()"
*ngIf="notLoadingToDisplay(); else loading"
>
<mat-form-field>
<mat-label>{{ "NewTunnelFlight_ChooseJumpType" | translate }}</mat-label>
<input
type="text"
matInput
[matAutocomplete]="autoJumpType"
[(ngModel)]="selectedJumpType"
name="selectedJumpType"
/>
<mat-autocomplete
#autoJumpType="matAutocomplete"
[displayWith]="displayNameFn"
>
<mat-option *ngFor="let jumpType of listOfJumpType" [value]="jumpType">
{{ jumpType.name }}
</mat-option>
</mat-autocomplete>
<button
*ngIf="selectedJumpType"
matSuffix
mat-icon-button
aria-label="Clear"
(click)="selectedJumpType = undefined"
>
<mat-icon>close</mat-icon>
</button>
</mat-form-field>
<mat-form-field>
<mat-label>{{ "NewTunnelFlight_ChooseTunnel" | translate }}</mat-label>
<input
type="text"
matInput
[matAutocomplete]="autoDropZone"
[(ngModel)]="selectedTunnel"
(ngModelChange)="onChangeTunnel($event)"
name="selectedTunnel"
/>
<mat-autocomplete
#autoDropZone="matAutocomplete"
[displayWith]="displayNameFn"
>
<mat-option
*ngFor="let tunnel of listOfFilteredTunnel"
[value]="tunnel"
> >
{{ tunnel.name }} {{ "NewTunnelFlight_GoToJump" | translate }}
</mat-option> </button>
</mat-autocomplete> </div>
<button
*ngIf="selectedTunnel"
matSuffix
mat-icon-button
aria-label="Clear"
(click)="resetTunnel()"
>
<mat-icon>close</mat-icon>
</button>
</mat-form-field>
<mat-form-field> @if (notLoadingToDisplay()) {
<mat-label>{{ "NewTunnelFlight_Date_Lbl" | translate }}</mat-label> <form class="formNewJumps" (ngSubmit)="onFormSubmit()">
<input <mat-form-field>
matInput <mat-label>{{
[max]="maxDate" "NewTunnelFlight_ChooseJumpType" | translate
[matDatepicker]="flightDateDp" }}</mat-label>
[(ngModel)]="flightDate" <input
name="flightDate" type="text"
disabled matInput
/> [matAutocomplete]="autoJumpType"
<mat-datepicker-toggle [(ngModel)]="selectedJumpType"
matSuffix name="selectedJumpType"
[for]="flightDateDp" />
></mat-datepicker-toggle> <mat-autocomplete
<mat-datepicker #flightDateDp disabled="false"></mat-datepicker> #autoJumpType="matAutocomplete"
</mat-form-field> [displayWith]="displayNameFn"
>
<mat-form-field> @for (jumpType of listOfJumpType; track jumpType) {
<mat-label>{{ "NewTunnelFlight_Minutes_Lbl" | translate }}</mat-label> <mat-option [value]="jumpType">
<input {{ jumpType.name }}
matInput </mat-option>
placeholder="{{ 'NewTunnelFlight_Minutes' | translate }}" }
[(ngModel)]="minutesOfFlight" </mat-autocomplete>
name="minutesOfFlight" @if (selectedJumpType) {
type="number" <button
/> matSuffix
<button mat-icon-button
*ngIf="minutesOfFlight" aria-label="Clear"
matSuffix (click)="selectedJumpType = undefined"
mat-icon-button >
aria-label="Clear" <mat-icon svgIcon="close"></mat-icon>
(click)="minutesOfFlight = undefined" </button>
> }
<mat-icon>close</mat-icon> </mat-form-field>
</button> <mat-form-field>
</mat-form-field> <mat-label>{{
"NewTunnelFlight_ChooseTunnel" | translate
<mat-form-field> }}</mat-label>
<mat-label>{{ "NewTunnelFlight_Comments_Lbl" | translate }}</mat-label> <input
<textarea type="text"
matInput matInput
placeholder="{{ 'NewTunnelFlight_Comments' | translate }}" [matAutocomplete]="autoDropZone"
[(ngModel)]="comments" [(ngModel)]="selectedTunnel"
name="comments" (ngModelChange)="onChangeTunnel($event)"
type="text" name="selectedTunnel"
></textarea> />
<button <mat-autocomplete
*ngIf="comments" #autoDropZone="matAutocomplete"
matSuffix [displayWith]="displayNameFn"
mat-icon-button >
aria-label="Clear" @for (tunnel of listOfFilteredTunnel; track tunnel) {
(click)="comments = undefined" <mat-option [value]="tunnel">
> {{ tunnel.name }}
<mat-icon>close</mat-icon> </mat-option>
</button> }
</mat-form-field> </mat-autocomplete>
@if (selectedTunnel) {
<br /> <button
<button mat-raised-button color="accent" *ngIf="isValidatedForm()"> matSuffix
{{ "NewTunnelFlight_Submit" | translate }} mat-icon-button
</button> aria-label="Clear"
</form> (click)="resetTunnel()"
>
<ng-template #loading> <mat-icon svgIcon="close"></mat-icon>
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner> </button>
</ng-template> }
</mat-form-field>
<mat-form-field>
<mat-label>{{
"NewTunnelFlight_Date_Lbl" | translate
}}</mat-label>
<input
matInput
[max]="maxDate"
[matDatepicker]="flightDateDp"
[(ngModel)]="flightDate"
name="flightDate"
disabled
/>
<mat-datepicker-toggle
matSuffix
[for]="flightDateDp"
></mat-datepicker-toggle>
<mat-datepicker #flightDateDp disabled="false"></mat-datepicker>
</mat-form-field>
<mat-form-field>
<mat-label>{{
"NewTunnelFlight_Minutes_Lbl" | translate
}}</mat-label>
<input
matInput
placeholder="{{ 'NewTunnelFlight_Minutes' | translate }}"
[(ngModel)]="minutesOfFlight"
name="minutesOfFlight"
type="number"
/>
@if (minutesOfFlight) {
<button
matSuffix
mat-icon-button
aria-label="Clear"
(click)="minutesOfFlight = undefined"
>
<mat-icon svgIcon="close"></mat-icon>
</button>
}
</mat-form-field>
<mat-form-field>
<mat-label>{{
"NewTunnelFlight_Comments_Lbl" | translate
}}</mat-label>
<textarea
matInput
placeholder="{{ 'NewTunnelFlight_Comments' | translate }}"
[(ngModel)]="comments"
name="comments"
type="text"
></textarea>
@if (comments) {
<button
matSuffix
mat-icon-button
aria-label="Clear"
(click)="comments = undefined"
>
<mat-icon svgIcon="close"></mat-icon>
</button>
}
</mat-form-field>
<br />
@if (this.isValidatedForm()) {
<button mat-raised-button color="accent">
{{ "NewTunnelFlight_Submit" | translate }}
</button>
}
</form>
} @else {
<mat-progress-spinner [mode]="'indeterminate'"></mat-progress-spinner>
}
</div> </div>

View File

@@ -2,12 +2,12 @@ import { Component, OnInit } from "@angular/core";
import { RouterLink, RouterModule } from "@angular/router"; import { RouterLink, RouterModule } from "@angular/router";
import { formatDate } from "@angular/common"; import { formatDate } from "@angular/common";
import { import {
DateAdapter, DateAdapter,
MAT_DATE_FORMATS, MAT_DATE_FORMATS,
NativeDateAdapter, NativeDateAdapter,
} from "@angular/material/core"; } from "@angular/material/core";
import { TranslateModule, TranslateService } from "@ngx-translate/core"; import { TranslateModule, TranslateService } from "@ngx-translate/core";
import { CommonModule } from "@angular/common";
import { MatIconModule } from "@angular/material/icon"; import { MatIconModule } from "@angular/material/icon";
import { MatOptionModule } from "@angular/material/core"; import { MatOptionModule } from "@angular/material/core";
import { MatAutocompleteModule } from "@angular/material/autocomplete"; import { MatAutocompleteModule } from "@angular/material/autocomplete";
@@ -28,170 +28,171 @@ import { JumpTypeService } from "../../services/jump-type.service";
import { DateService } from "../../services/date.service"; import { DateService } from "../../services/date.service";
export const PICK_FORMATS = { export const PICK_FORMATS = {
parse: { dateInput: "yy MM dd" }, parse: { dateInput: "yy MM dd" },
display: { display: {
dateInput: "yyyy-MM-dd", dateInput: "yyyy-MM-dd",
monthYearLabel: "yyyy MMM", monthYearLabel: "yyyy MMM",
dateA11yLabel: "yyyy MM dd", dateA11yLabel: "yyyy MM dd",
monthYearA11yLabel: "yyyy MMMM", monthYearA11yLabel: "yyyy MMMM",
}, },
}; };
class PickDateAdapter extends NativeDateAdapter { class PickDateAdapter extends NativeDateAdapter {
override format(date: Date, displayFormat: Object): string { override format(date: Date, displayFormat: Object): string {
return formatDate(date, displayFormat.toString(), "en"); return formatDate(date, displayFormat.toString(), "en");
} }
} }
@Component({ @Component({
selector: "app-new-tunnel-flight", selector: "app-new-tunnel-flight",
templateUrl: "./new-tunnel-flight.component.html", templateUrl: "./new-tunnel-flight.component.html",
styleUrls: ["./new-tunnel-flight.component.css"], styleUrls: ["./new-tunnel-flight.component.css"],
providers: [ providers: [
{ provide: DateAdapter, useClass: PickDateAdapter }, { provide: DateAdapter, useClass: PickDateAdapter },
{ provide: MAT_DATE_FORMATS, useValue: PICK_FORMATS }, { provide: MAT_DATE_FORMATS, useValue: PICK_FORMATS },
], ],
imports: [ imports: [
TranslateModule, TranslateModule,
CommonModule, RouterModule,
RouterModule, RouterLink,
RouterLink, FormsModule,
FormsModule, MatFormFieldModule,
MatFormFieldModule, ReactiveFormsModule,
ReactiveFormsModule, MatAutocompleteModule,
MatAutocompleteModule, MatInputModule,
MatInputModule, MatButtonModule,
MatButtonModule, MatIconModule,
MatIconModule, MatFormFieldModule,
MatFormFieldModule, MatOptionModule,
MatOptionModule, MatDatepickerModule,
MatDatepickerModule, MatProgressSpinnerModule,
MatProgressSpinnerModule, ],
],
}) })
export class NewTunnelFlightComponent implements OnInit { export class NewTunnelFlightComponent implements OnInit {
public flightDate: Date; public flightDate: Date;
public minutesOfFlight: number; public minutesOfFlight: number;
public selectedTunnel: TunnelResp; public selectedTunnel: TunnelResp;
public selectedJumpType: JumpTypeResp; public selectedJumpType: JumpTypeResp;
public listOfTunnel: Array<TunnelResp>; public listOfTunnel: Array<TunnelResp>;
public listOfFilteredTunnel: Array<TunnelResp>; public listOfFilteredTunnel: Array<TunnelResp>;
public resetForm: boolean; public resetForm: boolean;
public comments: string; public comments: string;
private countDatasLoaded: number; private countDatasLoaded: number;
private pendingAddRequest: boolean; private pendingAddRequest: boolean;
public listOfJumpType: Array<JumpTypeResp>; public listOfJumpType: Array<JumpTypeResp>;
public maxDate: Date; public maxDate: Date;
constructor( constructor(
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
private serviceTunnel: TunnelService, private serviceTunnel: TunnelService,
private serviceTunnelFlight: TunnelFlightService, private serviceTunnelFlight: TunnelFlightService,
private serviceJumpType: JumpTypeService, private serviceJumpType: JumpTypeService,
private translateService: TranslateService, private translateService: TranslateService,
private dateService: DateService 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.maxDate = this.dateService.addDays(new Date(), 1); this.maxDate = this.dateService.addDays(new Date(), 1);
this.countDatasLoaded = 0; this.countDatasLoaded = 0;
this.pendingAddRequest = false;
this.initForm();
this.getListOfTunnels();
this.getListOfJumpTypes();
}
public onFormSubmit() {
this.pendingAddRequest = true;
this.serviceTunnelFlight
.addFlight(
this.selectedTunnel.id,
this.selectedJumpType.id,
this.flightDate,
this.minutesOfFlight,
this.comments
)
.subscribe(() => {
this.comments = undefined;
if (this.resetForm === true) {
this.initForm();
}
this.pendingAddRequest = false; this.pendingAddRequest = false;
}); this.initForm();
} this.getListOfTunnels();
this.getListOfJumpTypes();
public isValidatedForm(): boolean {
return (
this.selectedTunnel !== undefined &&
this.selectedTunnel.id !== undefined &&
this.minutesOfFlight !== undefined &&
typeof this.minutesOfFlight === "number"
);
}
private getListOfTunnels() {
this.serviceTunnel.getListOfTunnels().subscribe((data) => {
data.sort((a, b) => a.name.localeCompare(b.name));
this.listOfTunnel = data;
this.listOfFilteredTunnel = data;
this.countDatasLoaded++;
});
}
private getListOfJumpTypes() {
this.serviceJumpType.getListOfJumpTypesForTunnel().subscribe((data) => {
data.sort((a, b) => a.name.localeCompare(b.name));
this.listOfJumpType = data;
this.countDatasLoaded++;
});
}
public notLoadingToDisplay(): boolean {
return !(this.pendingAddRequest || this.countDatasLoaded !== 2);
}
private updateTitle() {
this.translateService.get("NewTunnelFlight_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
private initForm() {
this.flightDate = new Date();
this.flightDate.setHours(0, 0, 0, 0);
this.minutesOfFlight = 1;
this.selectedTunnel = undefined;
this.comments = undefined;
}
public resetTunnel() {
this.selectedTunnel = undefined;
this.onChangeTunnel("");
}
public onChangeTunnel(event: any) {
let filterValue: string;
if (event.id === undefined) {
filterValue = event.toLowerCase();
this.listOfFilteredTunnel = this.listOfTunnel;
this.listOfFilteredTunnel = this.listOfFilteredTunnel.filter((option) =>
option.name.toLowerCase().includes(filterValue)
);
} }
}
public displayNameFn(data?: any): string | undefined { public onFormSubmit() {
return data ? data.name : undefined; if (this.isValidatedForm()) {
} this.pendingAddRequest = true;
this.serviceTunnelFlight
.addFlight(
this.selectedTunnel.id,
this.selectedJumpType.id,
this.flightDate,
this.minutesOfFlight,
this.comments,
)
.subscribe(() => {
this.comments = undefined;
if (this.resetForm === true) {
this.initForm();
}
this.pendingAddRequest = false;
});
}
}
public isValidatedForm(): boolean {
return (
this.selectedTunnel !== undefined &&
this.selectedTunnel.id !== undefined &&
this.minutesOfFlight !== undefined &&
typeof this.minutesOfFlight === "number"
);
}
private getListOfTunnels() {
this.serviceTunnel.getListOfTunnels().subscribe((data) => {
data.sort((a, b) => a.name.localeCompare(b.name));
this.listOfTunnel = data;
this.listOfFilteredTunnel = data;
this.countDatasLoaded++;
});
}
private getListOfJumpTypes() {
this.serviceJumpType.getListOfJumpTypesForTunnel().subscribe((data) => {
data.sort((a, b) => a.name.localeCompare(b.name));
this.listOfJumpType = data;
this.countDatasLoaded++;
});
}
public notLoadingToDisplay(): boolean {
return !(this.pendingAddRequest || this.countDatasLoaded !== 2);
}
private updateTitle() {
this.translateService.get("NewTunnelFlight_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
private initForm() {
this.flightDate = new Date();
this.flightDate.setHours(0, 0, 0, 0);
this.minutesOfFlight = 1;
this.selectedTunnel = undefined;
this.comments = undefined;
}
public resetTunnel() {
this.selectedTunnel = undefined;
this.onChangeTunnel("");
}
public onChangeTunnel(event: any) {
let filterValue: string;
if (event.id === undefined) {
filterValue = event.toLowerCase();
this.listOfFilteredTunnel = this.listOfTunnel;
this.listOfFilteredTunnel = this.listOfFilteredTunnel.filter(
(option) => option.name.toLowerCase().includes(filterValue),
);
}
}
public displayNameFn(data?: any): string | undefined {
return data ? data.name : undefined;
}
} }

View File

@@ -1,49 +1,49 @@
.mat-row td { .mat-row td {
padding-right: 15px; padding-right: 15px;
} }
.labelTab { .labelTab {
padding: 5px; padding: 5px;
cursor: pointer; cursor: pointer;
text-decoration: underline; text-decoration: underline;
font-weight: bold; font-weight: bold;
background-color: lightgray; background-color: lightgray;
} }
.labelTab.selected { .labelTab.selected {
background-color: gray !important; background-color: gray !important;
} }
.content { .content {
min-height: 90vh; min-height: 90vh;
display: flex; display: flex;
justify-content: left; justify-content: left;
flex-direction: column; flex-direction: column;
align-items: initial; align-items: initial;
padding-top: 25px; padding-top: 25px;
} }
.left160 { .left160 {
clear: both; clear: both;
float: left; float: left;
width: 160px; width: 160px;
} }
.paragraph { .paragraph {
margin-bottom: 10px; margin-bottom: 10px;
} }
.containerFlex { .containerFlex {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
.contentFlex { .contentFlex {
flex: 45%; flex: 45%;
} }
.chart-container { .chart-container {
position: relative; position: relative;
margin: auto; margin: auto;
height: 80vh; height: 80vh;
width: 80vw; width: 80vw;
} }

View File

@@ -1,182 +1,259 @@
<div class="content"> <div class="content">
<div class="paragraph"> <div class="paragraph">
<label class="left160">{{ "Summary_TotalJumps" | translate }}</label> <label class="left160">{{ "Summary_TotalJumps" | translate }}</label>
<span>: {{ totalJumps }}</span> <span>: {{ totalJumps }}</span>
</div> </div>
<div class="paragraph"> <div class="paragraph">
<label class="left160">{{ "Summary_TotalCutaways" | translate }}</label> <label class="left160">{{ "Summary_TotalCutaways" | translate }}</label>
<span>: {{ totalCutaways }}</span> <span>: {{ totalCutaways }}</span>
</div> </div>
<div class="paragraph"> <div class="paragraph">
<label class="left160">{{ "Summary_LastJump" | translate }}</label> <label class="left160">{{ "Summary_LastJump" | translate }}</label>
<span>: {{ lastJump }}</span> <span>: {{ lastJump }}</span>
</div> </div>
<div class="paragraph" style="margin-top: 20px"> <div class="paragraph" style="margin-top: 20px">
<label class="left160">{{ "Summary_Refresh" | translate }}</label> <label class="left160">{{ "Summary_Refresh" | translate }}</label>
<mat-icon <mat-icon
aria-hidden="false" aria-hidden="false"
aria-label="Force the refresh of the stats" aria-label="Force the refresh of the stats"
style="cursor: pointer" style="cursor: pointer"
(click)="refreshStats()" (click)="refreshStats()"
svgIcon="reset" svgIcon="reset"
></mat-icon> ></mat-icon>
</div> </div>
<mat-tab-group <mat-tab-group
mat-align-tabs="left" mat-align-tabs="left"
animationDuration="0ms" animationDuration="0ms"
(selectedIndex)="(0)" (selectedIndex)="(0)"
(selectedTabChange)="onTabChanged($event)" (selectedTabChange)="onTabChanged($event)"
> >
<mat-tab label="{{ 'Summary_LastMonth_Title' | translate }}"> <mat-tab label="{{ 'Summary_LastMonth_Title' | translate }}">
<ng-template matTabContent> <ng-template matTabContent>
<div class="containerFlex"> <div class="containerFlex">
<fieldset class="contentFlex"> <fieldset class="contentFlex">
<legend>{{ "Summary_LastMonth_ByDz" | translate }}</legend> <legend>
<table mat-table [dataSource]="dsJumpForLastMonthByDz"> {{ "Summary_LastMonth_ByDz" | translate }}
<ng-container matColumnDef="label"> </legend>
<td mat-cell *matCellDef="let element">{{ element.label }}</td> <table mat-table [dataSource]="dsJumpForLastMonthByDz">
</ng-container> <ng-container matColumnDef="label">
<ng-container matColumnDef="nb"> <td mat-cell *matCellDef="let element">
<td mat-cell *matCellDef="let element">{{ element.nb }}</td> {{ element.label }}
</ng-container> </td>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> </ng-container>
</table> <ng-container matColumnDef="nb">
</fieldset> <td mat-cell *matCellDef="let element">
<fieldset class="contentFlex"> {{ element.nb }}
<legend>{{ "Summary_LastMonth_ByJumpType" | translate }}</legend> </td>
<table mat-table [dataSource]="dsJumpForLastMonthByJumpType"> </ng-container>
<ng-container matColumnDef="label"> <tr
<td mat-cell *matCellDef="let element">{{ element.label }}</td> mat-row
</ng-container> *matRowDef="let row; columns: displayedColumns"
<ng-container matColumnDef="nb"> ></tr>
<td mat-cell *matCellDef="let element">{{ element.nb }}</td> </table>
</ng-container> </fieldset>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> <fieldset class="contentFlex">
</table> <legend>
</fieldset> {{ "Summary_LastMonth_ByJumpType" | translate }}
</div> </legend>
</ng-template> <table
</mat-tab> mat-table
[dataSource]="dsJumpForLastMonthByJumpType"
>
<ng-container matColumnDef="label">
<td mat-cell *matCellDef="let element">
{{ element.label }}
</td>
</ng-container>
<ng-container matColumnDef="nb">
<td mat-cell *matCellDef="let element">
{{ element.nb }}
</td>
</ng-container>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
></tr>
</table>
</fieldset>
</div>
</ng-template>
</mat-tab>
<mat-tab label="{{ 'Summary_LastYear_Title' | translate }}"> <mat-tab label="{{ 'Summary_LastYear_Title' | translate }}">
<ng-template matTabContent> <ng-template matTabContent>
<div class="containerFlex"> <div class="containerFlex">
<fieldset class="contentFlex"> <fieldset class="contentFlex">
<legend>{{ "Summary_LastYear_ByDz" | translate }}</legend> <legend>
<table mat-table [dataSource]="dsJumpForLastYearByDz"> {{ "Summary_LastYear_ByDz" | translate }}
<ng-container matColumnDef="label"> </legend>
<td mat-cell *matCellDef="let element">{{ element.label }}</td> <table mat-table [dataSource]="dsJumpForLastYearByDz">
</ng-container> <ng-container matColumnDef="label">
<ng-container matColumnDef="nb"> <td mat-cell *matCellDef="let element">
<td mat-cell *matCellDef="let element">{{ element.nb }}</td> {{ element.label }}
</ng-container> </td>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> </ng-container>
</table> <ng-container matColumnDef="nb">
</fieldset> <td mat-cell *matCellDef="let element">
<fieldset class="contentFlex"> {{ element.nb }}
<legend>{{ "Summary_LastYear_ByJumpType" | translate }}</legend> </td>
<table mat-table [dataSource]="dsJumpForLastYearByJumpType"> </ng-container>
<ng-container matColumnDef="label"> <tr
<td mat-cell *matCellDef="let element">{{ element.label }}</td> mat-row
</ng-container> *matRowDef="let row; columns: displayedColumns"
<ng-container matColumnDef="nb"> ></tr>
<td mat-cell *matCellDef="let element">{{ element.nb }}</td> </table>
</ng-container> </fieldset>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> <fieldset class="contentFlex">
</table> <legend>
</fieldset> {{ "Summary_LastYear_ByJumpType" | translate }}
</div> </legend>
</ng-template> <table
</mat-tab> mat-table
[dataSource]="dsJumpForLastYearByJumpType"
>
<ng-container matColumnDef="label">
<td mat-cell *matCellDef="let element">
{{ element.label }}
</td>
</ng-container>
<ng-container matColumnDef="nb">
<td mat-cell *matCellDef="let element">
{{ element.nb }}
</td>
</ng-container>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
></tr>
</table>
</fieldset>
</div>
</ng-template>
</mat-tab>
<mat-tab label="{{ 'Summary_ByDz_Title' | translate }}"> <mat-tab label="{{ 'Summary_ByDz_Title' | translate }}">
<ng-template matTabContent> <ng-template matTabContent>
<table mat-table [dataSource]="dsNbJumpByDz"> <table mat-table [dataSource]="dsNbJumpByDz">
<ng-container matColumnDef="label"> <ng-container matColumnDef="label">
<td mat-cell *matCellDef="let element">{{ element.label }}</td> <td mat-cell *matCellDef="let element">
</ng-container> {{ element.label }}
<ng-container matColumnDef="nb"> </td>
<td mat-cell *matCellDef="let element">{{ element.nb }}</td> </ng-container>
</ng-container> <ng-container matColumnDef="nb">
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> <td mat-cell *matCellDef="let element">
</table> {{ element.nb }}
</ng-template> </td>
</mat-tab> </ng-container>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
></tr>
</table>
</ng-template>
</mat-tab>
<mat-tab label="{{ 'Summary_ByAircraft_Title' | translate }}"> <mat-tab label="{{ 'Summary_ByAircraft_Title' | translate }}">
<ng-template matTabContent> <ng-template matTabContent>
<table mat-table [dataSource]="dsNbJumpByAircraft"> <table mat-table [dataSource]="dsNbJumpByAircraft">
<ng-container matColumnDef="label"> <ng-container matColumnDef="label">
<td mat-cell *matCellDef="let element">{{ element.label }}</td> <td mat-cell *matCellDef="let element">
</ng-container> {{ element.label }}
<ng-container matColumnDef="nb"> </td>
<td mat-cell *matCellDef="let element">{{ element.nb }}</td> </ng-container>
</ng-container> <ng-container matColumnDef="nb">
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> <td mat-cell *matCellDef="let element">
</table> {{ element.nb }}
</ng-template> </td>
</mat-tab> </ng-container>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
></tr>
</table>
</ng-template>
</mat-tab>
<mat-tab label="{{ 'Summary_ByGear_Title' | translate }}"> <mat-tab label="{{ 'Summary_ByGear_Title' | translate }}">
<ng-template matTabContent> <ng-template matTabContent>
<table mat-table [dataSource]="dsNbJumpByGear"> <table mat-table [dataSource]="dsNbJumpByGear">
<ng-container matColumnDef="label"> <ng-container matColumnDef="label">
<td mat-cell *matCellDef="let element">{{ element.label }}</td> <td mat-cell *matCellDef="let element">
</ng-container> {{ element.label }}
<ng-container matColumnDef="nb"> </td>
<td mat-cell *matCellDef="let element">{{ element.nb }}</td> </ng-container>
</ng-container> <ng-container matColumnDef="nb">
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> <td mat-cell *matCellDef="let element">
</table> {{ element.nb }}
</ng-template> </td>
</mat-tab> </ng-container>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
></tr>
</table>
</ng-template>
</mat-tab>
<mat-tab label="{{ 'Summary_ByJumpType_Title' | translate }}"> <mat-tab label="{{ 'Summary_ByJumpType_Title' | translate }}">
<ng-template matTabContent> <ng-template matTabContent>
<table mat-table [dataSource]="dsNbJumpByType"> <table mat-table [dataSource]="dsNbJumpByType">
<ng-container matColumnDef="label"> <ng-container matColumnDef="label">
<td mat-cell *matCellDef="let element">{{ element.label }}</td> <td mat-cell *matCellDef="let element">
</ng-container> {{ element.label }}
<ng-container matColumnDef="nb"> </td>
<td mat-cell *matCellDef="let element">{{ element.nb }}</td> </ng-container>
</ng-container> <ng-container matColumnDef="nb">
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> <td mat-cell *matCellDef="let element">
</table> {{ element.nb }}
</ng-template> </td>
</mat-tab> </ng-container>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
></tr>
</table>
</ng-template>
</mat-tab>
<mat-tab label="{{ 'Summary_ByYear_Title' | translate }}"> <mat-tab label="{{ 'Summary_ByYear_Title' | translate }}">
<ng-template matTabContent> <ng-template matTabContent>
<table mat-table [dataSource]="dsNbJumpByYear"> <table mat-table [dataSource]="dsNbJumpByYear">
<ng-container matColumnDef="label"> <ng-container matColumnDef="label">
<td mat-cell *matCellDef="let element">{{ element.label }}</td> <td mat-cell *matCellDef="let element">
</ng-container> {{ element.label }}
<ng-container matColumnDef="nb"> </td>
<td mat-cell *matCellDef="let element">{{ element.nb }}</td> </ng-container>
</ng-container> <ng-container matColumnDef="nb">
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr> <td mat-cell *matCellDef="let element">
</table> {{ element.nb }}
</ng-template> </td>
</mat-tab> </ng-container>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
></tr>
</table>
</ng-template>
</mat-tab>
<mat-tab label="{{ 'Summary_ByYearByJumpType_Title' | translate }}"> <mat-tab label="{{ 'Summary_ByYearByJumpType_Title' | translate }}">
<ng-template matTabContent> <ng-template matTabContent>
<div class="chart-container"> <div class="chart-container">
<canvas <canvas
baseChart baseChart
[data]="barChartData" [data]="barChartData"
[options]="barChartOptions" [options]="barChartOptions"
[plugins]="barChartPlugins" [plugins]="barChartPlugins"
[legend]="barChartLegend" [legend]="barChartLegend"
[type]="barChartType" [type]="barChartType"
> >
</canvas> </canvas>
</div> </div>
</ng-template> </ng-template>
</mat-tab> </mat-tab>
</mat-tab-group> </mat-tab-group>
</div> </div>

View File

@@ -11,294 +11,297 @@ import { MatIconModule } from "@angular/material/icon";
import { ServiceComm } from "../../services/service-comm.service"; import { ServiceComm } from "../../services/service-comm.service";
import { StatsService } from "../../services/stats.service"; import { StatsService } from "../../services/stats.service";
import { import {
StatsByDzResp, StatsByDzResp,
StatsByAircraftResp, StatsByAircraftResp,
StatsByGearResp, StatsByGearResp,
StatsByJumpTypeResp, StatsByJumpTypeResp,
StatsByYearResp, StatsByYearResp,
} from "../../models/stats"; } from "../../models/stats";
@Component({ @Component({
selector: "app-summary", selector: "app-summary",
templateUrl: "./summary.component.html", templateUrl: "./summary.component.html",
styleUrls: ["./summary.component.css"], styleUrls: ["./summary.component.css"],
imports: [ imports: [
MatTabsModule, MatTabsModule,
TranslateModule, TranslateModule,
BaseChartDirective, BaseChartDirective,
MatIconModule, MatIconModule,
MatTableModule, MatTableModule,
], ],
}) })
export class SummaryComponent implements OnInit { export class SummaryComponent implements OnInit {
public dsNbJumpByDz: MatTableDataSource<StatsByDzResp>; public dsNbJumpByDz: MatTableDataSource<StatsByDzResp>;
public dsNbJumpByAircraft: MatTableDataSource<StatsByAircraftResp>; public dsNbJumpByAircraft: MatTableDataSource<StatsByAircraftResp>;
public dsNbJumpByGear: MatTableDataSource<StatsByGearResp>; public dsNbJumpByGear: MatTableDataSource<StatsByGearResp>;
public dsNbJumpByType: MatTableDataSource<StatsByJumpTypeResp>; public dsNbJumpByType: MatTableDataSource<StatsByJumpTypeResp>;
public dsNbJumpByYear: MatTableDataSource<StatsByYearResp>; public dsNbJumpByYear: MatTableDataSource<StatsByYearResp>;
public dsJumpForLastYearByDz: MatTableDataSource<StatsByDzResp>; public dsJumpForLastYearByDz: MatTableDataSource<StatsByDzResp>;
public dsJumpForLastYearByJumpType: MatTableDataSource<StatsByJumpTypeResp>; public dsJumpForLastYearByJumpType: MatTableDataSource<StatsByJumpTypeResp>;
public dsJumpForLastMonthByDz: MatTableDataSource<StatsByDzResp>; public dsJumpForLastMonthByDz: MatTableDataSource<StatsByDzResp>;
public dsJumpForLastMonthByJumpType: MatTableDataSource<StatsByJumpTypeResp>; public dsJumpForLastMonthByJumpType: MatTableDataSource<StatsByJumpTypeResp>;
public displayedColumns: Array<string> = ["label", "nb"]; public displayedColumns: Array<string> = ["label", "nb"];
public displayedColumnsByYearByJumpType: Array<string> = [ public displayedColumnsByYearByJumpType: Array<string> = [
"label", "label",
"label2", "label2",
"nb", "nb",
]; ];
public barChartLegend = true; public barChartLegend = true;
public barChartPlugins: any = []; public barChartPlugins: any = [];
public barChartData: ChartData<"line">; public barChartData: ChartData<"line">;
public barChartOptions: ChartConfiguration["options"]; public barChartOptions: ChartConfiguration["options"];
public barChartType: ChartType; public barChartType: ChartType;
private jumpTypeToColor: Map<string, string>; private jumpTypeToColor: Map<string, string>;
public totalJumps: number; public totalJumps: number;
public totalCutaways: number; public totalCutaways: number;
public lastJump: string; public lastJump: string;
@ViewChild(MatTabGroup) tabGroup: MatTabGroup; @ViewChild(MatTabGroup) tabGroup: MatTabGroup;
constructor( constructor(
private serviceApi: StatsService, private serviceApi: StatsService,
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
private translateService: TranslateService, private translateService: TranslateService,
) {} ) {}
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.serviceApi.getSimpleSummary().subscribe((data) => { this.serviceApi.getSimpleSummary().subscribe((data) => {
this.totalJumps = data.totalJumps; this.totalJumps = data.totalJumps;
this.totalCutaways = data.totalCutaways; this.totalCutaways = data.totalCutaways;
const datepipe: DatePipe = new DatePipe("en-US"); const datepipe: DatePipe = new DatePipe("en-US");
let formattedDate = datepipe.transform( let formattedDate = datepipe.transform(
data.lastJump.jumpDate, data.lastJump.jumpDate,
"EEEE dd MMMM YYYY", "EEEE dd MMMM yyyy",
); );
this.lastJump = formattedDate + " (" + data.lastJump.dropZone.name + ")"; this.lastJump =
}); formattedDate + " (" + data.lastJump.dropZone.name + ")";
});
this.serviceApi.getStatsOfLastMonth().subscribe((data) => { this.serviceApi.getStatsOfLastMonth().subscribe((data) => {
data.byDz.sort((a, b) => b.nb - a.nb); data.byDz.sort((a, b) => b.nb - a.nb);
this.dsJumpForLastMonthByDz = new MatTableDataSource(data.byDz); this.dsJumpForLastMonthByDz = new MatTableDataSource(data.byDz);
data.byJumpType.sort((a, b) => b.nb - a.nb); data.byJumpType.sort((a, b) => b.nb - a.nb);
this.dsJumpForLastMonthByJumpType = new MatTableDataSource( this.dsJumpForLastMonthByJumpType = new MatTableDataSource(
data.byJumpType, data.byJumpType,
); );
}); });
this.chartConfig(); this.chartConfig();
}
public refreshStats() {
this.serviceApi.deleteAllCache();
this.tabGroup.selectedIndex = 0;
}
public onTabChanged(event: MatTabChangeEvent) {
switch (event.index) {
case 0:
this.statsLastMonth();
break;
case 1:
this.statsLastYear();
break;
case 2:
this.statsByDz();
break;
case 3:
this.statsByAircraft();
break;
case 4:
this.statsByGear();
break;
case 5:
this.statsByJumpType();
break;
case 6:
this.statsByYear();
break;
case 7:
this.statsByYearByJumpType();
break;
} }
}
private updateTitle() { public refreshStats() {
this.translateService.get("Summary_Title").subscribe((data) => { this.serviceApi.deleteAllCache();
this.serviceComm.updatedComponentTitle(data); this.tabGroup.selectedIndex = 0;
}); }
}
private chartConfig() { public onTabChanged(event: MatTabChangeEvent) {
this.barChartType = "line"; switch (event.index) {
this.barChartOptions = { case 0:
responsive: true, this.statsLastMonth();
maintainAspectRatio: false, break;
plugins: { case 1:
legend: { this.statsLastYear();
display: true, break;
}, case 2:
colors: { this.statsByDz();
forceOverride: false, break;
}, case 3:
}, this.statsByAircraft();
interaction: { break;
intersect: false, case 4:
mode: "nearest", this.statsByGear();
axis: "x", break;
}, case 5:
scales: { this.statsByJumpType();
x: { break;
stacked: false, case 6:
}, this.statsByYear();
y: { break;
stacked: false, case 7:
beginAtZero: true, this.statsByYearByJumpType();
}, break;
},
};
this.jumpTypeToColor = new Map<string, string>([
["PAC", "#FFD700"],
["Solo", "#FFA500"],
["RW 3", "#40E0D0"],
["RW 4", "#008080"],
["RW 8", "#7FFFD4"],
["RW X", "#114556"],
["FreeFly", "#FFC0CB"],
["FreeStyle", "#FF91A4"],
["Track/Trace", "#87CEEB"],
["Canopy", "#228B22"],
["Landing accuracy", "#FF6347"],
["Wingsuit 1", "#E6E6FA"],
["Wingsuit 2", "#E0B0FF"],
["Wingsuit 3", "#9400D3"],
]);
}
// #region Private methods to get stats
private statsByYearByJumpType() {
this.serviceApi.getStatsByYearByJumpType().subscribe((data) => {
data.sort((a, b) => a.label.localeCompare(b.label));
let firstYear: number = Number(data[0].label);
const now = new Date();
const currentYear = now.getFullYear();
const nbYears = currentYear - firstYear;
let listOfYears = new Array(nbYears).fill(null).map(() => firstYear++);
// Prepare the list of jump type with am empty array
let tmpResults = new Map<string, number[]>();
const listOfJumpType = [...new Set(data.map((obj) => obj.label2))];
listOfJumpType.forEach((type) => {
tmpResults.set(type, new Array(nbYears).fill(NaN));
});
for (let i = 0; i < listOfYears.length; i++) {
const year = listOfYears[i].toString();
let filteredStats = data.filter((d) => d.label == year);
if (filteredStats.length > 0) {
filteredStats.forEach((fs) => {
tmpResults.get(fs.label2)[i] = fs.nb;
});
} }
} }
const results: { private updateTitle() {
label: string; this.translateService.get("Summary_Title").subscribe((data) => {
data: number[]; this.serviceComm.updatedComponentTitle(data);
backgroundColor: string; });
borderColor: string; }
pointBackgroundColor: string;
fill: boolean; private chartConfig() {
pointRadius: number; this.barChartType = "line";
}[] = []; this.barChartOptions = {
tmpResults.forEach((value, key) => { responsive: true,
const color = this.jumpTypeToColor.get(key); maintainAspectRatio: false,
let tmp = { plugins: {
label: key, legend: {
data: value, display: true,
backgroundColor: color, },
borderColor: color, colors: {
pointBackgroundColor: color, forceOverride: false,
fill: false, },
pointRadius: 6, },
interaction: {
intersect: false,
mode: "nearest",
axis: "x",
},
scales: {
x: {
stacked: false,
},
y: {
stacked: false,
beginAtZero: true,
},
},
}; };
results.push(tmp);
});
this.barChartData = { this.jumpTypeToColor = new Map<string, string>([
labels: listOfYears, ["PAC", "#FFD700"],
datasets: results, ["Solo", "#FFA500"],
}; ["RW 3", "#40E0D0"],
}); ["RW 4", "#008080"],
} ["RW 8", "#7FFFD4"],
["RW X", "#114556"],
["FreeFly", "#FFC0CB"],
["FreeStyle", "#FF91A4"],
["Track/Trace", "#87CEEB"],
["Canopy", "#228B22"],
["Landing accuracy", "#FF6347"],
["Wingsuit 1", "#E6E6FA"],
["Wingsuit 2", "#E0B0FF"],
["Wingsuit 3", "#9400D3"],
]);
}
private statsByYear() { // #region Private methods to get stats
this.serviceApi.getStatsByYear().subscribe((data) => { private statsByYearByJumpType() {
data.sort((a, b) => b.label.localeCompare(a.label)); this.serviceApi.getStatsByYearByJumpType().subscribe((data) => {
this.dsNbJumpByYear = new MatTableDataSource(data); data.sort((a, b) => a.label.localeCompare(b.label));
});
}
private statsByJumpType() { let firstYear: number = Number(data[0].label);
this.serviceApi.getStatsByJumpType().subscribe((data) => { const now = new Date();
data.sort((a, b) => b.nb - a.nb); const currentYear = now.getFullYear();
this.dsNbJumpByType = new MatTableDataSource(data); const nbYears = currentYear - firstYear;
}); let listOfYears = new Array(nbYears)
} .fill(null)
.map(() => firstYear++);
private statsByGear() { // Prepare the list of jump type with am empty array
this.serviceApi.getStatsByGear().subscribe((data) => { let tmpResults = new Map<string, number[]>();
data.sort((a, b) => b.nb - a.nb); const listOfJumpType = [...new Set(data.map((obj) => obj.label2))];
this.dsNbJumpByGear = new MatTableDataSource(data); listOfJumpType.forEach((type) => {
}); tmpResults.set(type, new Array(nbYears).fill(NaN));
} });
private statsByAircraft() { for (let i = 0; i < listOfYears.length; i++) {
this.serviceApi.getStatsByAircraft().subscribe((data) => { const year = listOfYears[i].toString();
data.sort((a, b) => b.nb - a.nb);
this.dsNbJumpByAircraft = new MatTableDataSource(data);
});
}
private statsByDz() { let filteredStats = data.filter((d) => d.label == year);
this.serviceApi.getStatsByDz().subscribe((data) => {
data.sort((a, b) => b.nb - a.nb);
this.dsNbJumpByDz = new MatTableDataSource(data);
});
}
private statsLastYear() { if (filteredStats.length > 0) {
this.serviceApi.getStatsOfLastYear().subscribe((data) => { filteredStats.forEach((fs) => {
data.byDz.sort((a, b) => b.nb - a.nb); tmpResults.get(fs.label2)[i] = fs.nb;
this.dsJumpForLastYearByDz = new MatTableDataSource(data.byDz); });
data.byJumpType.sort((a, b) => b.nb - a.nb); }
this.dsJumpForLastYearByJumpType = new MatTableDataSource( }
data.byJumpType,
);
});
}
private statsLastMonth() { const results: {
this.serviceApi.getStatsOfLastMonth().subscribe((data) => { label: string;
data.byDz.sort((a, b) => b.nb - a.nb); data: number[];
this.dsJumpForLastMonthByDz = new MatTableDataSource(data.byDz); backgroundColor: string;
data.byJumpType.sort((a, b) => b.nb - a.nb); borderColor: string;
this.dsJumpForLastMonthByJumpType = new MatTableDataSource( pointBackgroundColor: string;
data.byJumpType, fill: boolean;
); pointRadius: number;
}); }[] = [];
} tmpResults.forEach((value, key) => {
//#endregion const color = this.jumpTypeToColor.get(key);
let tmp = {
label: key,
data: value,
backgroundColor: color,
borderColor: color,
pointBackgroundColor: color,
fill: false,
pointRadius: 6,
};
results.push(tmp);
});
this.barChartData = {
labels: listOfYears,
datasets: results,
};
});
}
private statsByYear() {
this.serviceApi.getStatsByYear().subscribe((data) => {
data.sort((a, b) => b.label.localeCompare(a.label));
this.dsNbJumpByYear = new MatTableDataSource(data);
});
}
private statsByJumpType() {
this.serviceApi.getStatsByJumpType().subscribe((data) => {
data.sort((a, b) => b.nb - a.nb);
this.dsNbJumpByType = new MatTableDataSource(data);
});
}
private statsByGear() {
this.serviceApi.getStatsByGear().subscribe((data) => {
data.sort((a, b) => b.nb - a.nb);
this.dsNbJumpByGear = new MatTableDataSource(data);
});
}
private statsByAircraft() {
this.serviceApi.getStatsByAircraft().subscribe((data) => {
data.sort((a, b) => b.nb - a.nb);
this.dsNbJumpByAircraft = new MatTableDataSource(data);
});
}
private statsByDz() {
this.serviceApi.getStatsByDz().subscribe((data) => {
data.sort((a, b) => b.nb - a.nb);
this.dsNbJumpByDz = new MatTableDataSource(data);
});
}
private statsLastYear() {
this.serviceApi.getStatsOfLastYear().subscribe((data) => {
data.byDz.sort((a, b) => b.nb - a.nb);
this.dsJumpForLastYearByDz = new MatTableDataSource(data.byDz);
data.byJumpType.sort((a, b) => b.nb - a.nb);
this.dsJumpForLastYearByJumpType = new MatTableDataSource(
data.byJumpType,
);
});
}
private statsLastMonth() {
this.serviceApi.getStatsOfLastMonth().subscribe((data) => {
data.byDz.sort((a, b) => b.nb - a.nb);
this.dsJumpForLastMonthByDz = new MatTableDataSource(data.byDz);
data.byJumpType.sort((a, b) => b.nb - a.nb);
this.dsJumpForLastMonthByJumpType = new MatTableDataSource(
data.byJumpType,
);
});
}
//#endregion
} }

View File

@@ -1,14 +1,14 @@
.content { .content {
min-height: 90vh; min-height: 90vh;
justify-content: left; justify-content: left;
align-items: initial; align-items: initial;
padding-top: 25px; padding-top: 25px;
} }
.containerFlex { .containerFlex {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
.contentFlex { .contentFlex {
flex: 45%; flex: 45%;
} }

View File

@@ -1,53 +1,70 @@
<div class="content containerFlex"> <div class="content containerFlex">
<fieldset class="contentFlex"> <fieldset class="contentFlex">
<legend>Profile</legend> <legend>Profile</legend>
<form [formGroup]="userForm" (ngSubmit)="onSubmit(userForm.value)"> <form [formGroup]="userForm" (ngSubmit)="onSubmit(userForm.value)">
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Login</mat-label> <mat-label>{{ "UserProfile_Login" | translate }}</mat-label>
<input matInput type="text" formControlName="login" readonly /> <input
</mat-form-field> matInput
</p> type="text"
<p> formControlName="login"
<mat-form-field> readonly
<mat-label>Firstname</mat-label> />
<input matInput type="text" formControlName="firstName" /> </mat-form-field>
</mat-form-field> </p>
</p> <p>
<p> <mat-form-field>
<mat-form-field> <mat-label>{{
<mat-label>Lastname</mat-label> "UserProfile_Firstname" | translate
<input matInput type="text" formControlName="lastName" /> }}</mat-label>
</mat-form-field> <input matInput type="text" formControlName="firstName" />
</p> </mat-form-field>
<p> </p>
<mat-form-field> <p>
<mat-label>E-mail</mat-label> <mat-form-field>
<input matInput type="text" formControlName="email" /> <mat-label>{{
</mat-form-field> "UserProfile_Lastname" | translate
</p> }}</mat-label>
<input matInput type="text" formControlName="lastName" />
</mat-form-field>
</p>
<p>
<mat-form-field>
<mat-label>{{ "UserProfile_Mail" | translate }}</mat-label>
<input matInput type="text" formControlName="email" />
</mat-form-field>
</p>
<p> <p>
<mat-form-field> <mat-form-field>
<mat-label>Current password</mat-label> <mat-label>{{
<input matInput type="text" formControlName="currentPassword" /> "UserProfile_CurrentPassword" | translate
</mat-form-field> }}</mat-label>
</p> <input
<p> matInput
<mat-form-field> type="text"
<mat-label>New password</mat-label> formControlName="currentPassword"
<input matInput type="text" formControlName="newPassword" /> />
</mat-form-field> </mat-form-field>
</p> </p>
<p>
<mat-form-field>
<mat-label>{{
"UserProfile_NewPassword" | translate
}}</mat-label>
<input matInput type="text" formControlName="newPassword" />
</mat-form-field>
</p>
<button type="submit" mat-raised-button color="accent"> <button type="submit" mat-raised-button color="accent">
Update my profile {{ "UserProfile_Update" | translate }}
</button> </button>
</form> </form>
</fieldset> </fieldset>
<fieldset class="contentFlex"> <fieldset class="contentFlex">
<legend>Images</legend> <legend>Images</legend>
<app-list-of-images></app-list-of-images> <app-list-of-images></app-list-of-images>
</fieldset> </fieldset>
</div> </div>

View File

@@ -1,15 +1,15 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { import {
FormGroup, FormGroup,
FormControl, FormControl,
Validators, Validators,
ReactiveFormsModule, ReactiveFormsModule,
} from "@angular/forms"; } from "@angular/forms";
import { MatLabel, MatFormFieldModule } from "@angular/material/form-field"; import { MatLabel, MatFormFieldModule } from "@angular/material/form-field";
import { import {
TranslateModule, TranslateModule,
TranslatePipe, TranslatePipe,
TranslateService, TranslateService,
} from "@ngx-translate/core"; } from "@ngx-translate/core";
import { MatInputModule } from "@angular/material/input"; import { MatInputModule } from "@angular/material/input";
import { MatButtonModule } from "@angular/material/button"; import { MatButtonModule } from "@angular/material/button";
@@ -19,137 +19,138 @@ import { ListOfImagesComponent } from "../list-of-images/list-of-images.componen
import { ServiceComm } from "../../services/service-comm.service"; import { ServiceComm } from "../../services/service-comm.service";
@Component({ @Component({
selector: "app-user-profile", selector: "app-user-profile",
templateUrl: "./user-profile.component.html", templateUrl: "./user-profile.component.html",
styleUrls: ["./user-profile.component.css"], styleUrls: ["./user-profile.component.css"],
imports: [ imports: [
ListOfImagesComponent, ListOfImagesComponent,
ReactiveFormsModule, ReactiveFormsModule,
MatLabel, MatLabel,
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,
MatButtonModule, MatButtonModule,
TranslateModule, TranslateModule,
], TranslatePipe,
],
}) })
export class UserProfileComponent implements OnInit { export class UserProfileComponent implements OnInit {
public userForm: FormGroup; public userForm: FormGroup;
constructor( constructor(
private authenticationService: AuthenticationService, private authenticationService: AuthenticationService,
private translateService: TranslateService, private translateService: TranslateService,
private serviceComm: ServiceComm, private serviceComm: ServiceComm,
) {} ) {}
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();
const currentUser = this.authenticationService.currentUserValue; const currentUser = this.authenticationService.currentUserValue;
this.userForm = new FormGroup( this.userForm = new FormGroup(
{ {
login: new FormControl(currentUser.login, Validators.required), login: new FormControl(currentUser.login, Validators.required),
firstName: new FormControl(currentUser.firstName, [ firstName: new FormControl(currentUser.firstName, [
Validators.required, Validators.required,
Validators.minLength(3), Validators.minLength(3),
]), ]),
lastName: new FormControl(currentUser.lastName, [ lastName: new FormControl(currentUser.lastName, [
Validators.required, Validators.required,
Validators.minLength(3), Validators.minLength(3),
]), ]),
email: new FormControl(currentUser.email, [ email: new FormControl(currentUser.email, [
Validators.required, Validators.required,
Validators.email, Validators.email,
]), ]),
currentPassword: new FormControl( currentPassword: new FormControl(
"", "",
Validators.pattern("^[A-Za-z0-9_-]{8,15}$"), Validators.pattern("^[A-Za-z0-9_-]{8,15}$"),
), ),
newPassword: new FormControl( newPassword: new FormControl(
"", "",
Validators.pattern("^[A-Za-z0-9_-]{8,15}$"), Validators.pattern("^[A-Za-z0-9_-]{8,15}$"),
), ),
}, },
{ updateOn: "blur" }, { updateOn: "blur" },
); );
}
onSubmit(formData) {
if (this.userForm.invalid) {
return;
} }
// const updatedUser = new User(); onSubmit(formData) {
// updatedUser.firstName = formData.firstName; if (this.userForm.invalid) {
// updatedUser.lastName = formData.lastName; return;
// updatedUser.email = formData.email; }
// this.authenticationService.update(updatedUser); // const updatedUser = new User();
} // updatedUser.firstName = formData.firstName;
// updatedUser.lastName = formData.lastName;
// updatedUser.email = formData.email;
private calculTailleVoile() { // this.authenticationService.update(updatedUser);
let tailleVoile = new Map<string, Array<number>>(); }
tailleVoile.set("60", [175, 161, 147, 133, 124, 115, 107, 97, 89]);
tailleVoile.set("61", [178, 163, 149, 135, 126, 116, 108, 98, 90]);
tailleVoile.set("62", [180, 166, 151, 137, 127, 118, 109, 99, 91]);
tailleVoile.set("63", [183, 168, 153, 138, 129, 119, 111, 100, 91]);
tailleVoile.set("64", [185, 170, 155, 140, 130, 121, 112, 101, 92]);
tailleVoile.set("65", [188, 173, 157, 142, 132, 122, 113, 102, 93]);
tailleVoile.set("66", [190, 175, 159, 144, 134, 123, 114, 103, 94]);
tailleVoile.set("67", [193, 177, 161, 146, 135, 125, 116, 104, 94]);
tailleVoile.set("68", [195, 179, 164, 147, 137, 126, 117, 105, 95]);
tailleVoile.set("69", [198, 182, 166, 149, 138, 128, 118, 106, 96]);
tailleVoile.set("70", [200, 184, 168, 151, 140, 129, 119, 107, 96]);
tailleVoile.set("71", [203, 186, 170, 153, 142, 130, 120, 107, 97]);
tailleVoile.set("72", [205, 189, 172, 155, 143, 132, 121, 108, 98]);
tailleVoile.set("73", [208, 191, 174, 156, 145, 133, 123, 109, 99]);
tailleVoile.set("74", [210, 193, 176, 158, 146, 134, 124, 110, 99]);
tailleVoile.set("75", [213, 196, 178, 160, 148, 136, 125, 111, 100]);
tailleVoile.set("76", [215, 198, 180, 162, 150, 137, 126, 112, 101]);
tailleVoile.set("77", [218, 200, 182, 163, 151, 139, 127, 113, 101]);
tailleVoile.set("78", [220, 202, 184, 165, 153, 140, 128, 114, 102]);
tailleVoile.set("79", [223, 205, 186, 167, 154, 141, 129, 115, 103]);
tailleVoile.set("80", [225, 207, 188, 169, 156, 143, 131, 115, 103]);
tailleVoile.set("81", [228, 209, 190, 170, 157, 144, 132, 116, 104]);
tailleVoile.set("82", [230, 212, 192, 172, 159, 145, 133, 117, 104]);
tailleVoile.set("83", [233, 214, 194, 174, 160, 146, 134, 118, 105]);
tailleVoile.set("84", [235, 216, 196, 176, 162, 148, 135, 119, 106]);
tailleVoile.set("85", [238, 219, 198, 177, 163, 149, 136, 120, 106]);
tailleVoile.set("86", [240, 221, 201, 179, 165, 150, 137, 120, 107]);
tailleVoile.set("87", [243, 223, 203, 181, 166, 152, 138, 121, 108]);
tailleVoile.set("88", [245, 225, 205, 183, 168, 153, 139, 122, 108]);
tailleVoile.set("89", [248, 228, 207, 184, 170, 154, 140, 123, 109]);
tailleVoile.set("90", [250, 230, 209, 186, 171, 156, 141, 124, 109]);
tailleVoile.set("91", [253, 232, 211, 188, 173, 157, 143, 124, 110]);
tailleVoile.set("92", [255, 235, 213, 190, 174, 158, 144, 125, 110]);
tailleVoile.set("93", [258, 237, 215, 191, 176, 159, 145, 126, 111]);
tailleVoile.set("94", [260, 239, 217, 193, 177, 161, 146, 127, 112]);
tailleVoile.set("95", [263, 242, 219, 195, 179, 162, 147, 128, 112]);
tailleVoile.set("96", [265, 244, 221, 197, 180, 163, 148, 128, 113]);
tailleVoile.set("97", [268, 246, 223, 198, 182, 164, 149, 129, 113]);
tailleVoile.set("98", [270, 248, 225, 200, 183, 166, 150, 130, 114]);
tailleVoile.set("99", [273, 251, 227, 202, 185, 167, 151, 131, 114]);
tailleVoile.set("100", [275, 253, 229, 203, 186, 168, 152, 131, 115]);
tailleVoile.set("101", [278, 255, 231, 205, 188, 169, 153, 132, 115]);
tailleVoile.set("102", [280, 258, 233, 207, 189, 171, 154, 133, 116]);
tailleVoile.set("103", [283, 260, 235, 209, 190, 172, 155, 134, 116]);
tailleVoile.set("104", [285, 262, 237, 210, 192, 173, 156, 134, 117]);
tailleVoile.set("105", [288, 265, 239, 212, 193, 174, 157, 135, 118]);
tailleVoile.set("106", [290, 267, 241, 214, 195, 175, 158, 136, 118]);
tailleVoile.set("107", [293, 269, 243, 215, 196, 177, 159, 136, 119]);
tailleVoile.set("108", [295, 271, 245, 217, 198, 178, 160, 137, 119]);
tailleVoile.set("109", [298, 274, 247, 219, 199, 179, 161, 138, 120]);
tailleVoile.set("110", [300, 276, 249, 220, 201, 180, 162, 138, 120]);
}
private updateTitle() { private calculTailleVoile() {
this.translateService.get("UserProfile_Title").subscribe((data) => { let tailleVoile = new Map<string, Array<number>>();
this.serviceComm.updatedComponentTitle(data); tailleVoile.set("60", [175, 161, 147, 133, 124, 115, 107, 97, 89]);
}); tailleVoile.set("61", [178, 163, 149, 135, 126, 116, 108, 98, 90]);
} tailleVoile.set("62", [180, 166, 151, 137, 127, 118, 109, 99, 91]);
tailleVoile.set("63", [183, 168, 153, 138, 129, 119, 111, 100, 91]);
tailleVoile.set("64", [185, 170, 155, 140, 130, 121, 112, 101, 92]);
tailleVoile.set("65", [188, 173, 157, 142, 132, 122, 113, 102, 93]);
tailleVoile.set("66", [190, 175, 159, 144, 134, 123, 114, 103, 94]);
tailleVoile.set("67", [193, 177, 161, 146, 135, 125, 116, 104, 94]);
tailleVoile.set("68", [195, 179, 164, 147, 137, 126, 117, 105, 95]);
tailleVoile.set("69", [198, 182, 166, 149, 138, 128, 118, 106, 96]);
tailleVoile.set("70", [200, 184, 168, 151, 140, 129, 119, 107, 96]);
tailleVoile.set("71", [203, 186, 170, 153, 142, 130, 120, 107, 97]);
tailleVoile.set("72", [205, 189, 172, 155, 143, 132, 121, 108, 98]);
tailleVoile.set("73", [208, 191, 174, 156, 145, 133, 123, 109, 99]);
tailleVoile.set("74", [210, 193, 176, 158, 146, 134, 124, 110, 99]);
tailleVoile.set("75", [213, 196, 178, 160, 148, 136, 125, 111, 100]);
tailleVoile.set("76", [215, 198, 180, 162, 150, 137, 126, 112, 101]);
tailleVoile.set("77", [218, 200, 182, 163, 151, 139, 127, 113, 101]);
tailleVoile.set("78", [220, 202, 184, 165, 153, 140, 128, 114, 102]);
tailleVoile.set("79", [223, 205, 186, 167, 154, 141, 129, 115, 103]);
tailleVoile.set("80", [225, 207, 188, 169, 156, 143, 131, 115, 103]);
tailleVoile.set("81", [228, 209, 190, 170, 157, 144, 132, 116, 104]);
tailleVoile.set("82", [230, 212, 192, 172, 159, 145, 133, 117, 104]);
tailleVoile.set("83", [233, 214, 194, 174, 160, 146, 134, 118, 105]);
tailleVoile.set("84", [235, 216, 196, 176, 162, 148, 135, 119, 106]);
tailleVoile.set("85", [238, 219, 198, 177, 163, 149, 136, 120, 106]);
tailleVoile.set("86", [240, 221, 201, 179, 165, 150, 137, 120, 107]);
tailleVoile.set("87", [243, 223, 203, 181, 166, 152, 138, 121, 108]);
tailleVoile.set("88", [245, 225, 205, 183, 168, 153, 139, 122, 108]);
tailleVoile.set("89", [248, 228, 207, 184, 170, 154, 140, 123, 109]);
tailleVoile.set("90", [250, 230, 209, 186, 171, 156, 141, 124, 109]);
tailleVoile.set("91", [253, 232, 211, 188, 173, 157, 143, 124, 110]);
tailleVoile.set("92", [255, 235, 213, 190, 174, 158, 144, 125, 110]);
tailleVoile.set("93", [258, 237, 215, 191, 176, 159, 145, 126, 111]);
tailleVoile.set("94", [260, 239, 217, 193, 177, 161, 146, 127, 112]);
tailleVoile.set("95", [263, 242, 219, 195, 179, 162, 147, 128, 112]);
tailleVoile.set("96", [265, 244, 221, 197, 180, 163, 148, 128, 113]);
tailleVoile.set("97", [268, 246, 223, 198, 182, 164, 149, 129, 113]);
tailleVoile.set("98", [270, 248, 225, 200, 183, 166, 150, 130, 114]);
tailleVoile.set("99", [273, 251, 227, 202, 185, 167, 151, 131, 114]);
tailleVoile.set("100", [275, 253, 229, 203, 186, 168, 152, 131, 115]);
tailleVoile.set("101", [278, 255, 231, 205, 188, 169, 153, 132, 115]);
tailleVoile.set("102", [280, 258, 233, 207, 189, 171, 154, 133, 116]);
tailleVoile.set("103", [283, 260, 235, 209, 190, 172, 155, 134, 116]);
tailleVoile.set("104", [285, 262, 237, 210, 192, 173, 156, 134, 117]);
tailleVoile.set("105", [288, 265, 239, 212, 193, 174, 157, 135, 118]);
tailleVoile.set("106", [290, 267, 241, 214, 195, 175, 158, 136, 118]);
tailleVoile.set("107", [293, 269, 243, 215, 196, 177, 159, 136, 119]);
tailleVoile.set("108", [295, 271, 245, 217, 198, 178, 160, 137, 119]);
tailleVoile.set("109", [298, 274, 247, 219, 199, 179, 161, 138, 120]);
tailleVoile.set("110", [300, 276, 249, 220, 201, 180, 162, 138, 120]);
}
private updateTitle() {
this.translateService.get("UserProfile_Title").subscribe((data) => {
this.serviceComm.updatedComponentTitle(data);
});
}
} }

View File

@@ -1,139 +1,147 @@
{ {
"Login_Title": "Login to the Skydive log", "Login_Title": "Login to the Skydive log",
"Login_Tab_CreateUser": "Create and login a user", "Login_Tab_CreateUser": "Create and login a user",
"Login_Tab_WithUser": "Login with a user", "Login_Tab_WithUser": "Login with a user",
"LoginUser_BtnLogin": "Login", "LoginUser_BtnLogin": "Login",
"LoginUser_PasswordPattern": "The pattern of the password ([A-Za-z0-9_-] {{ '{' }}8,15{{ '}' }})", "LoginUser_PasswordPattern": "The pattern of the password ([A-Za-z0-9_-] {{ '{' }}8,15{{ '}' }})",
"LoginUser_PasswordRequired": "Password is required", "LoginUser_PasswordRequired": "Password is required",
"LoginUser_Password": "Password", "LoginUser_Password": "Password",
"LoginUser_Username": "Username", "LoginUser_Username": "Username",
"LoginUser_UsernamePattern": "Username must have min 3 characters", "LoginUser_UsernamePattern": "Username must have min 3 characters",
"LoginUser_UsernameRequired": "Username is required", "LoginUser_UsernameRequired": "Username is required",
"LoginCreateUser_Firstname": "Firstname", "CreateUser_Firstname": "Firstname",
"LoginCreateUser_FirstnameRequired": "Firstname is required", "CreateUser_FirstnameRequired": "Firstname is required",
"LoginCreateUser_FirstnamePattern": "Firstname must have min 3 characters", "CreateUser_FirstnamePattern": "Firstname must have min 3 characters",
"LoginCreateUser_Lastname": "Lastname", "CreateUser_Lastname": "Lastname",
"LoginCreateUser_LastnameRequired": "Lastname is required", "CreateUser_LastnameRequired": "Lastname is required",
"LoginCreateUser_LastnamePattern": "Lastname must have min 3 characters", "CreateUser_LastnamePattern": "Lastname must have min 3 characters",
"LoginCreateUser_Email": "E-mail", "CreateUser_Email": "E-mail",
"LoginCreateUser_EmailRequired": "E-mail is required", "CreateUser_EmailRequired": "E-mail is required",
"LoginCreateUser_EmailPattern": "It's not a e-mail", "CreateUser_EmailPattern": "It's not a e-mail",
"LoginCreateUser_Username": "Username", "CreateUser_Username": "Username",
"LoginCreateUser_UsernameRequired": "Username is required", "CreateUser_UsernameRequired": "Username is required",
"LoginCreateUser_UsernamePattern": "Username must have min 3 characters", "CreateUser_UsernamePattern": "Username must have min 3 characters",
"LoginCreateUser_Password": "Password", "CreateUser_Password": "Password",
"LoginCreateUser_PasswordRequired": "Password is required", "CreateUser_PasswordRequired": "Password is required",
"LoginCreateUser_PasswordPattern": "The pattern of the password ([A-Za-z0-9_-|/]{{ '{' }}8,15{{ '}' }})", "CreateUser_PasswordPattern": "The pattern of the password ([A-Za-z0-9_-|/]{{ '{' }}8,15{{ '}' }})",
"LoginCreateUser_BtnLogin": "Create user and login", "CreateUser_BtnLogin": "Create user and login",
"Default_Title": "Home", "Default_Title": "Home",
"ListDz_Title": "List of DZs", "ListDz_Title": "List of DZs",
"Summary_Title": "Summary", "Summary_Title": "Summary",
"NewJump_Title": "New jumps", "NewJump_Title": "New jumps",
"ListJumps_Title": "List of jumps", "ListJumps_Title": "List of jumps",
"ListJumpTypes_Title": "List of jump types", "ListJumpTypes_Title": "List of jump types",
"ListGears_Title": "List of gears", "ListGears_Title": "List of gears",
"ListAircrafts_Title": "List of aircrafts", "ListAircrafts_Title": "List of aircrafts",
"NewTunnelFlight_Title": "New tunnel flights", "NewTunnelFlight_Title": "New tunnel flights",
"ListTunnelFlight_Title": "List of hours of tunnel", "ListTunnelFlight_Title": "List of hours of tunnel",
"UserProfile_Title": "User profile", "UserProfile_Title": "User profile",
"NewJumpType_Title": "New jmp type", "NewJumpType_Title": "New jmp type",
"NewGear_Title": "New gear", "NewGear_Title": "New gear",
"NewDz_Title": "New drop zone", "NewDz_Title": "New drop zone",
"NewAircraft_Title": "New aircraft", "NewAircraft_Title": "New aircraft",
"App_Footer": "Web software to log your skydive jumps - v", "App_Footer": "Web software to log your skydive jumps - v",
"App_Nav_Summary": "Summary", "App_Nav_Summary": "Summary",
"App_Nav_Jumps": "List of jumps", "App_Nav_Jumps": "List of jumps",
"App_Nav_NewJump": "Add a new jump", "App_Nav_NewJump": "Add a new jump",
"App_Nav_Dzs": "List of DZs", "App_Nav_Dzs": "List of DZs",
"App_Nav_Aircrafts": "List of aircrafts", "App_Nav_Aircrafts": "List of aircrafts",
"App_Nav_JumpTypes": "List of jump types", "App_Nav_JumpTypes": "List of jump types",
"App_Nav_Gears": "List of gears", "App_Nav_Gears": "List of gears",
"App_Nav_Logout": "Logout", "App_Nav_Logout": "Logout",
"App_Nav_NewTunnelFlight": "Add tunnel time", "App_Nav_NewTunnelFlight": "Add tunnel time",
"App_Nav_TunnelFlights": "The tunnel flights", "App_Nav_TunnelFlights": "The tunnel flights",
"ListAircrafts_Add": "Add a aircraft", "ListAircrafts_Add": "Add a aircraft",
"ListAircrafts_Header_Id": "ID", "ListAircrafts_Header_Id": "ID",
"ListAircrafts_Header_Name": "Name", "ListAircrafts_Header_Name": "Name",
"ListAircrafts_Header_Image": "Image", "ListAircrafts_Header_Image": "Image",
"ListGears_Add": "Add a gear", "ListGears_Add": "Add a gear",
"ListGears_Header_Id": "ID", "ListGears_Header_Id": "ID",
"ListGears_Header_Name": "Name", "ListGears_Header_Name": "Name",
"ListGears_Header_Manufacturer": "Manufacturer", "ListGears_Header_Manufacturer": "Manufacturer",
"ListGears_Header_CanopySize": "Canopy size", "ListGears_Header_CanopySize": "Canopy size",
"ListGears_Header_Aad": "AAD system", "ListGears_Header_Aad": "AAD system",
"ListGears_Header_Main": "Main canopy", "ListGears_Header_Main": "Main canopy",
"ListGears_Header_Reserve": "Reserve canopy", "ListGears_Header_Reserve": "Reserve canopy",
"ListJumpType_Add": "Add a jump type", "ListJumpType_Add": "Add a jump type",
"ListJumpType_Header_Id": "ID", "ListJumpType_Header_Id": "ID",
"ListJumpType_Header_Name": "Name", "ListJumpType_Header_Name": "Name",
"ListJump_Add": "Add jumps", "ListJump_Add": "Add jumps",
"ListJump_Header_Num": "Num", "ListJump_Header_Num": "Num",
"ListJump_Header_Date": "Date", "ListJump_Header_Date": "Date",
"ListJump_Header_JumpType": "Jump Type", "ListJump_Header_JumpType": "Jump Type",
"ListJump_Header_Aircraft": "Aircraft", "ListJump_Header_Aircraft": "Aircraft",
"ListJump_Header_Dz": "Drop Zone", "ListJump_Header_Dz": "Drop Zone",
"ListJump_Header_Gear": "Gear", "ListJump_Header_Gear": "Gear",
"ListDz_Add": "Add a drop zone", "ListDz_Add": "Add a drop zone",
"ListDz_Header_ID": "ID", "ListDz_Header_ID": "ID",
"ListDz_Header_Name": "Name", "ListDz_Header_Name": "Name",
"ListDz_Header_Address": "Address", "ListDz_Header_Address": "Address",
"ListDz_Header_Type": "Type", "ListDz_Header_Type": "Type",
"ListDz_Filter": "Filter", "ListDz_Filter": "Filter",
"ListDz_Filter_PlaceHolder": "Filter on the name or address of center", "ListDz_Filter_PlaceHolder": "Filter on the name or address of center",
"Summary_TotalJumps": "Total jumps", "Summary_TotalJumps": "Total jumps",
"Summary_TotalCutaways": "Total cutaways", "Summary_TotalCutaways": "Total cutaways",
"Summary_LastJump": "Last jump", "Summary_LastJump": "Last jump",
"Summary_Refresh": "Refresh", "Summary_Refresh": "Refresh",
"Summary_LastMonth_Title": "Jumps in the last month", "Summary_LastMonth_Title": "Jumps in the last month",
"Summary_LastMonth_ByDz": "By DZ", "Summary_LastMonth_ByDz": "By DZ",
"Summary_LastMonth_ByJumpType": "By jump type", "Summary_LastMonth_ByJumpType": "By jump type",
"Summary_LastYear_Title": "Jumps in the last year", "Summary_LastYear_Title": "Jumps in the last year",
"Summary_LastYear_ByDz": "By DZ", "Summary_LastYear_ByDz": "By DZ",
"Summary_LastYear_ByJumpType": "By jump type", "Summary_LastYear_ByJumpType": "By jump type",
"Summary_ByDz_Title": "By DZ", "Summary_ByDz_Title": "By DZ",
"Summary_ByAircraft_Title": "By aircraft", "Summary_ByAircraft_Title": "By aircraft",
"Summary_ByGear_Title": "By gear", "Summary_ByGear_Title": "By gear",
"Summary_ByJumpType_Title": "By jump type", "Summary_ByJumpType_Title": "By jump type",
"Summary_ByYear_Title": "By year", "Summary_ByYear_Title": "By year",
"Summary_ByYearByJumpType_Title": "By year and by type", "Summary_ByYearByJumpType_Title": "By year and by type",
"NewJump_GoToJump": "View the jumps", "NewJump_GoToJump": "View the jumps",
"NewJump_ResetForm": "Reset form after adding", "NewJump_ResetForm": "Reset form after adding",
"NewJump_ChooseJumpType": "Choose the jump type", "NewJump_ChooseJumpType": "Choose the jump type",
"NewJump_ChooseAircraft": "Choose the aircraft", "NewJump_ChooseAircraft": "Choose the aircraft",
"NewJump_ChooseDz": "Choose the DZ", "NewJump_ChooseDz": "Choose the DZ",
"NewJump_ChooseGear": "Choose the used gear", "NewJump_ChooseGear": "Choose the used gear",
"NewJump_Cutaway": "With a cutaway ?", "NewJump_Cutaway": "With a cutaway ?",
"NewJump_Special": "Is a special jump ?", "NewJump_Special": "Is a special jump ?",
"NewJump_ExitAlt": "Exit altitude", "NewJump_ExitAlt": "Exit altitude",
"NewJump_DeployAlt": "Deploy altitude", "NewJump_DeployAlt": "Deploy altitude",
"NewJump_Count": "Count of jumps", "NewJump_Count": "Count of jumps",
"NewJump_Comments": "Comments", "NewJump_Comments": "Comments",
"NewJump_Submit": "Submit", "NewJump_Submit": "Submit",
"NewTunnelFlight_ChooseTunnel": "Choose the tunnel", "NewTunnelFlight_ChooseTunnel": "Choose the tunnel",
"NewTunnelFlight_Minutes": "Minutes of the flight", "NewTunnelFlight_Minutes": "Minutes of the flight",
"NewTunnelFlight_Comments": "Comments", "NewTunnelFlight_Comments": "Comments",
"NewTunnelFlight_Submit": "Submit", "NewTunnelFlight_Submit": "Submit",
"NewTunnelFlight_Comments_Lbl": "Comments", "NewTunnelFlight_Comments_Lbl": "Comments",
"NewTunnelFlight_Minutes_Lbl": "Time of flight (minutes)", "NewTunnelFlight_Minutes_Lbl": "Time of flight (minutes)",
"NewTunnelFlight_Date_Lbl": "Date of flight", "NewTunnelFlight_Date_Lbl": "Date of flight",
"NewTunnelFlight_GoToJump": "View the tunnel flights", "NewTunnelFlight_GoToJump": "View the tunnel flights",
"NewTunnelFlight_ChooseJumpType": "Choose the jump type", "NewTunnelFlight_ChooseJumpType": "Choose the jump type",
"ListTunnelFlight_CurrentYear": "On the current year", "ListTunnelFlight_CurrentYear": "On the current year",
"ListTunnelFlight_12Months": "On 12 last months", "ListTunnelFlight_12Months": "On 12 last months",
"ListTunnelFlight_Add": "Add tunnel flights", "ListTunnelFlight_Add": "Add tunnel flights",
"ListTunnelFlight_LoadTable": "Load the tunnel flights", "ListTunnelFlight_LoadTable": "Load the tunnel flights",
"ListTunnelFlight_AllFlights": "All" "ListTunnelFlight_AllFlights": "All",
"UserProfile_Update": "Update my profile",
"UserProfile_NewPassword": "New password",
"UserProfile_CurrentPassword": "Current password",
"UserProfile_Mail": "E-mail",
"UserProfile_Lastname": "Lastname",
"UserProfile_Firstname": "Firstname",
"UserProfile_Login": "Login"
} }

View File

@@ -1,139 +1,147 @@
{ {
"Login_Title": "Connexion à Skydive log", "Login_Title": "Connexion à Skydive log",
"Login_Tab_CreateUser": "Créer et se connecter", "Login_Tab_CreateUser": "Créer et se connecter",
"Login_Tab_WithUser": "Se connecter", "Login_Tab_WithUser": "Se connecter",
"LoginUser_BtnLogin": "Connecter", "LoginUser_BtnLogin": "Connecter",
"LoginUser_PasswordPattern": "Le mot de passe doit contenir lettres minuscule/majuscule et entre 8 et 15 caractères.", "LoginUser_PasswordPattern": "Le mot de passe doit contenir lettres minuscule/majuscule et entre 8 et 15 caractères.",
"LoginUser_PasswordRequired": "Le mot de passe est obligatoire", "LoginUser_PasswordRequired": "Le mot de passe est obligatoire",
"LoginUser_Password": "Mot de passe", "LoginUser_Password": "Mot de passe",
"LoginUser_Username": "Identifiant", "LoginUser_Username": "Identifiant",
"LoginUser_UsernamePattern": "L'identifiant doit être minimum de 3 caractères", "LoginUser_UsernamePattern": "L'identifiant doit être minimum de 3 caractères",
"LoginUser_UsernameRequired": "L'identifiant est obligatoire", "LoginUser_UsernameRequired": "L'identifiant est obligatoire",
"LoginCreateUser_Firstname": "Prénom", "CreateUser_Firstname": "Prénom",
"LoginCreateUser_FirstnameRequired": "Le prénom est obligatoire", "CreateUser_FirstnameRequired": "Le prénom est obligatoire",
"LoginCreateUser_FirstnamePattern": "Le prénom doit être minimum de 3 caractères", "CreateUser_FirstnamePattern": "Le prénom doit être minimum de 3 caractères",
"LoginCreateUser_Lastname": "Nom", "CreateUser_Lastname": "Nom",
"LoginCreateUser_LastnameRequired": "Le nom est obligatoire", "CreateUser_LastnameRequired": "Le nom est obligatoire",
"LoginCreateUser_LastnamePattern": "Le nom doit être minimum de 3 caractères", "CreateUser_LastnamePattern": "Le nom doit être minimum de 3 caractères",
"LoginCreateUser_Email": "E-mail", "CreateUser_Email": "E-mail",
"LoginCreateUser_EmailRequired": "E-mail est obligatoire", "CreateUser_EmailRequired": "E-mail est obligatoire",
"LoginCreateUser_EmailPattern": "Ceci n'est pas un adresse mail", "CreateUser_EmailPattern": "Ceci n'est pas un adresse mail",
"LoginCreateUser_Username": "Identifiant", "CreateUser_Username": "Identifiant",
"LoginCreateUser_UsernameRequired": "L'identifiant est obligatoire", "CreateUser_UsernameRequired": "L'identifiant est obligatoire",
"LoginCreateUser_UsernamePattern": "L'identifiant doit être minimum de 3 caractères", "CreateUser_UsernamePattern": "L'identifiant doit être minimum de 3 caractères",
"LoginCreateUser_Password": "Mot de passe", "CreateUser_Password": "Mot de passe",
"LoginCreateUser_PasswordRequired": "Le mot de passe est obligatoire", "CreateUser_PasswordRequired": "Le mot de passe est obligatoire",
"LoginCreateUser_PasswordPattern": "Le mot de passe doit contenir lettres minuscule/majuscule et entre 8 et 15 caractères.", "CreateUser_PasswordPattern": "Le mot de passe doit contenir lettres minuscule/majuscule et entre 8 et 15 caractères.",
"LoginCreateUser_BtnLogin": "Créer et se connecter", "CreateUser_BtnLogin": "Créer et se connecter",
"Default_Title": "Accueil", "Default_Title": "Accueil",
"ListDz_Title": "Liste des centres de parachutisme", "ListDz_Title": "Liste des centres de parachutisme",
"Summary_Title": "Récapitulatif", "Summary_Title": "Récapitulatif",
"NewJump_Title": "Nouveaux sauts", "NewJump_Title": "Nouveaux sauts",
"ListJumps_Title": "Liste des sauts", "ListJumps_Title": "Liste des sauts",
"ListJumpTypes_Title": "Liste des types de saut", "ListJumpTypes_Title": "Liste des types de saut",
"ListGears_Title": "Liste des pièges", "ListGears_Title": "Liste des pièges",
"ListAircrafts_Title": "Liste des avions", "ListAircrafts_Title": "Liste des avions",
"NewTunnelFlight_Title": "Nouveaux créneaux de soufflerie", "NewTunnelFlight_Title": "Nouveaux créneaux de soufflerie",
"ListTunnelFlight_Title": "Heures de tunnel", "ListTunnelFlight_Title": "Heures de tunnel",
"UserProfile_Title": "Profile utilisateur", "UserProfile_Title": "Profile utilisateur",
"NewJumpType_Title": "Nouveau type de saut", "NewJumpType_Title": "Nouveau type de saut",
"NewGear_Title": "Nouveau piège", "NewGear_Title": "Nouveau piège",
"NewDz_Title": "Nouveau centre", "NewDz_Title": "Nouveau centre",
"NewAircraft_Title": "Nouvel avion", "NewAircraft_Title": "Nouvel avion",
"App_Footer": "Application pour enregistrer ses sauts de parachutisme - v", "App_Footer": "Application pour enregistrer ses sauts de parachutisme - v",
"App_Nav_Summary": "Récapitulatif", "App_Nav_Summary": "Récapitulatif",
"App_Nav_Jumps": "Les sauts", "App_Nav_Jumps": "Les sauts",
"App_Nav_NewJump": "Ajouter un saut", "App_Nav_NewJump": "Ajouter un saut",
"App_Nav_Dzs": "Centre de parachutisme", "App_Nav_Dzs": "Centre de parachutisme",
"App_Nav_Aircrafts": "Avions", "App_Nav_Aircrafts": "Avions",
"App_Nav_JumpTypes": "Type de saut", "App_Nav_JumpTypes": "Type de saut",
"App_Nav_Gears": "Pièges", "App_Nav_Gears": "Pièges",
"App_Nav_Logout": "Se déconnecter", "App_Nav_Logout": "Se déconnecter",
"App_Nav_NewTunnelFlight": "Ajouter du temps en tunnel", "App_Nav_NewTunnelFlight": "Ajouter du temps en tunnel",
"App_Nav_TunnelFlights": "Les vols en soufflerie", "App_Nav_TunnelFlights": "Les vols en soufflerie",
"ListAircrafts_Add": "Ajouter un avion", "ListAircrafts_Add": "Ajouter un avion",
"ListAircrafts_Header_Id": "ID", "ListAircrafts_Header_Id": "ID",
"ListAircrafts_Header_Name": "Nom", "ListAircrafts_Header_Name": "Nom",
"ListAircrafts_Header_Image": "Image", "ListAircrafts_Header_Image": "Image",
"ListGears_Add": "Ajouter un piège", "ListGears_Add": "Ajouter un piège",
"ListGears_Header_Id": "ID", "ListGears_Header_Id": "ID",
"ListGears_Header_Name": "Nom", "ListGears_Header_Name": "Nom",
"ListGears_Header_Manufacturer": "Fabriquant", "ListGears_Header_Manufacturer": "Fabriquant",
"ListGears_Header_CanopySize": "Taille de voile", "ListGears_Header_CanopySize": "Taille de voile",
"ListGears_Header_Aad": "Système de sécurité", "ListGears_Header_Aad": "Système de sécurité",
"ListGears_Header_Main": "Principale", "ListGears_Header_Main": "Principale",
"ListGears_Header_Reserve": "Réserve", "ListGears_Header_Reserve": "Réserve",
"ListJumpType_Add": "Ajouter un type de saut", "ListJumpType_Add": "Ajouter un type de saut",
"ListJumpType_Header_Id": "ID", "ListJumpType_Header_Id": "ID",
"ListJumpType_Header_Name": "Nom", "ListJumpType_Header_Name": "Nom",
"ListJump_Add": "Ajouter des sauts", "ListJump_Add": "Ajouter des sauts",
"ListJump_Header_Num": "Numéro", "ListJump_Header_Num": "Numéro",
"ListJump_Header_Date": "Date", "ListJump_Header_Date": "Date",
"ListJump_Header_JumpType": "Type de saut", "ListJump_Header_JumpType": "Type de saut",
"ListJump_Header_Aircraft": "Avion", "ListJump_Header_Aircraft": "Avion",
"ListJump_Header_Dz": "Centre", "ListJump_Header_Dz": "Centre",
"ListJump_Header_Gear": "Piège", "ListJump_Header_Gear": "Piège",
"ListDz_Add": "Ajouter un centre de parachutisme", "ListDz_Add": "Ajouter un centre de parachutisme",
"ListDz_Header_ID": "ID", "ListDz_Header_ID": "ID",
"ListDz_Header_Name": "Nom", "ListDz_Header_Name": "Nom",
"ListDz_Header_Address": "Adresse", "ListDz_Header_Address": "Adresse",
"ListDz_Header_Type": "Type", "ListDz_Header_Type": "Type",
"ListDz_Filter": "Filtrer", "ListDz_Filter": "Filtrer",
"ListDz_Filter_PlaceHolder": "Filtrer sur le nom ou l'adresse du centre", "ListDz_Filter_PlaceHolder": "Filtrer sur le nom ou l'adresse du centre",
"Summary_TotalJumps": "Nombre de sauts", "Summary_TotalJumps": "Nombre de sauts",
"Summary_TotalCutaways": "Nombre de libération", "Summary_TotalCutaways": "Nombre de libération",
"Summary_LastJump": "Le dernier saut", "Summary_LastJump": "Le dernier saut",
"Summary_Refresh": "Refresh", "Summary_Refresh": "Refresh",
"Summary_LastMonth_Title": "Les sauts du dernier mois", "Summary_LastMonth_Title": "Les sauts du dernier mois",
"Summary_LastMonth_ByDz": "Par centre", "Summary_LastMonth_ByDz": "Par centre",
"Summary_LastMonth_ByJumpType": "Par type de saut", "Summary_LastMonth_ByJumpType": "Par type de saut",
"Summary_LastYear_Title": "Les sauts de la dernière année", "Summary_LastYear_Title": "Les sauts de la dernière année",
"Summary_LastYear_ByDz": "Par centre", "Summary_LastYear_ByDz": "Par centre",
"Summary_LastYear_ByJumpType": "Par type de saut", "Summary_LastYear_ByJumpType": "Par type de saut",
"Summary_ByDz_Title": "Par centre", "Summary_ByDz_Title": "Par centre",
"Summary_ByAircraft_Title": "Par avion", "Summary_ByAircraft_Title": "Par avion",
"Summary_ByGear_Title": "Par piège", "Summary_ByGear_Title": "Par piège",
"Summary_ByJumpType_Title": "Par type de saut", "Summary_ByJumpType_Title": "Par type de saut",
"Summary_ByYear_Title": "Par an", "Summary_ByYear_Title": "Par an",
"Summary_ByYearByJumpType_Title": "Par an et par type", "Summary_ByYearByJumpType_Title": "Par an et par type",
"NewJump_GoToJump": "Voir les sauts", "NewJump_GoToJump": "Voir les sauts",
"NewJump_ResetForm": "Reset du formulaire après l'ajout", "NewJump_ResetForm": "Reset du formulaire après l'ajout",
"NewJump_ChooseJumpType": "Choisir le type de saut", "NewJump_ChooseJumpType": "Choisir le type de saut",
"NewJump_ChooseAircraft": "Choisir l'avion largueur", "NewJump_ChooseAircraft": "Choisir l'avion largueur",
"NewJump_ChooseDz": "Choisir le centre", "NewJump_ChooseDz": "Choisir le centre",
"NewJump_ChooseGear": "Choisir le piège", "NewJump_ChooseGear": "Choisir le piège",
"NewJump_Cutaway": "Libération ?", "NewJump_Cutaway": "Libération ?",
"NewJump_Special": "Saut spécial ?", "NewJump_Special": "Saut spécial ?",
"NewJump_ExitAlt": "Altitude de sortie", "NewJump_ExitAlt": "Altitude de sortie",
"NewJump_DeployAlt": "Altitude d'ouverture", "NewJump_DeployAlt": "Altitude d'ouverture",
"NewJump_Count": "Nombre de sauts", "NewJump_Count": "Nombre de sauts",
"NewJump_Comments": "Commentaires", "NewJump_Comments": "Commentaires",
"NewJump_Submit": "Ajouter", "NewJump_Submit": "Ajouter",
"NewTunnelFlight_ChooseTunnel": "Choisir le tunnel", "NewTunnelFlight_ChooseTunnel": "Choisir le tunnel",
"NewTunnelFlight_Minutes": "Temps de vol(minutes)", "NewTunnelFlight_Minutes": "Temps de vol(minutes)",
"NewTunnelFlight_Comments": "Commentaires", "NewTunnelFlight_Comments": "Commentaires",
"NewTunnelFlight_Submit": "Ajouter", "NewTunnelFlight_Submit": "Ajouter",
"NewTunnelFlight_Comments_Lbl": "Commentaires", "NewTunnelFlight_Comments_Lbl": "Commentaires",
"NewTunnelFlight_Minutes_Lbl": "Temps de vol(minutes)", "NewTunnelFlight_Minutes_Lbl": "Temps de vol(minutes)",
"NewTunnelFlight_Date_Lbl": "Date des vols", "NewTunnelFlight_Date_Lbl": "Date des vols",
"NewTunnelFlight_GoToJump": "Voir les temps de vol en soufflerie", "NewTunnelFlight_GoToJump": "Voir les temps de vol en soufflerie",
"NewTunnelFlight_ChooseJumpType": "Choisir le type de saut", "NewTunnelFlight_ChooseJumpType": "Choisir le type de saut",
"ListTunnelFlight_CurrentYear": "Dans l'année en cours", "ListTunnelFlight_CurrentYear": "Dans l'année en cours",
"ListTunnelFlight_12Months": "Sur 12 derniers mois", "ListTunnelFlight_12Months": "Sur 12 derniers mois",
"ListTunnelFlight_Add": "Ajouter du temps en soufflerie", "ListTunnelFlight_Add": "Ajouter du temps en soufflerie",
"ListTunnelFlight_LoadTable": "Charger les vols en tunnel", "ListTunnelFlight_LoadTable": "Charger les vols en tunnel",
"ListTunnelFlight_AllFlights": "Tous les vols" "ListTunnelFlight_AllFlights": "Tous les vols",
"UserProfile_Update": "Mise à jour",
"UserProfile_NewPassword": "Nouveau mot de passe",
"UserProfile_CurrentPassword": "Actuel mot de passe",
"UserProfile_Mail": "E-mail",
"UserProfile_Lastname": "Nom",
"UserProfile_Firstname": "Prénom",
"UserProfile_Login": "Identifiant"
} }

12
doc.txt
View File

@@ -1,7 +1,11 @@
To build an image "toto" with the version "0.1": podman build . -t skydivelogs:1.5.0 To build an image "skydivelogs" with the version "1.5.5":
podman build . -t skydivelogs:1.5.5
To run ab image to container with volume : To run ab image to container with volume :
podman run -v C:\toto\config:/app/Front/config -v C:\toto\db:/app/API/Data -d -p 5080:80/tcp --name Test -it skydivelogs:1.5.0 podman run -v ./Front/skydivelogs-app/src/config:/usr/share/nginx/html/config:z -v ./Back/skydiveLogs-api/Data:/app/Data:z -d -p 5080:80/tcp --name Skydivelogs-1.5.5 -it skydivelogs:1.5.5
podman save --output skydivelogs-1.5.0.tar skydivelogs:1.5.0 To save the image before to updload to the server :
podman save --output skydivelogs-1.5.5.tar skydivelogs:1.5.5
scp -P 5022 skydivelogs-1.5.0.tar administrator@51.75.68.58:~ Updload to the server :
scp -P 5022 skydivelogs-1.5.5.tar administrator@51.75.68.58:~

View File

@@ -1,6 +0,0 @@
#!/bin/sh
service nginx start
cd /app/API/
dotnet skydiveLogs-api.dll