---|qcm| Mise à jour V17 et QCM
This commit is contained in:
@@ -30,6 +30,7 @@ yarn-error.log
|
||||
|
||||
# Miscellaneous
|
||||
/.angular/cache
|
||||
.nx/
|
||||
.sass-cache/
|
||||
/connect.lock
|
||||
/coverage
|
||||
@@ -45,6 +46,10 @@ testem.log
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# SonarQube
|
||||
.scannerwork/
|
||||
sonar.properties
|
||||
|
||||
**/*.copy.ts
|
||||
**/*.copy.scss
|
||||
**/*.copy.html
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# HeadupApp
|
||||
# HeadupAngular
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.1.3.
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.2.2.
|
||||
|
||||
## Development server
|
||||
|
||||
|
||||
+168
-151
@@ -1,155 +1,172 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"cli": {
|
||||
"packageManager": "npm",
|
||||
"analytics": false
|
||||
},
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"headup_app": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "huapp",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/headup_app",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest"
|
||||
],
|
||||
"styles": [
|
||||
"node_modules/chartist/dist/index.scss",
|
||||
"src/styles/styles.scss"
|
||||
],
|
||||
"scripts": [],
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"buildOptimizer": false,
|
||||
"sourceMap": true,
|
||||
"optimization": false,
|
||||
"namedChunks": true
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
]
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true,
|
||||
"extractLicenses": false,
|
||||
"vendorChunk": true,
|
||||
"buildOptimizer": false,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.development.ts"
|
||||
}
|
||||
],
|
||||
"budgets": [
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb"
|
||||
}
|
||||
]
|
||||
},
|
||||
"local": {
|
||||
"optimization": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true,
|
||||
"extractLicenses": false,
|
||||
"vendorChunk": true,
|
||||
"buildOptimizer": false,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.local.ts"
|
||||
}
|
||||
],
|
||||
"budgets": [
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "headup_app:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "headup_app:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"buildTarget": "headup_app:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"cli": {
|
||||
"packageManager": "npm",
|
||||
"analytics": false,
|
||||
"schematicCollections": [
|
||||
"@angular-eslint/schematics"
|
||||
]
|
||||
},
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"headup_angular": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
},
|
||||
"i18n": {
|
||||
"sourceLocale": "fr"
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser-esbuild",
|
||||
"options": {
|
||||
"outputPath": "dist/headup_angular",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest",
|
||||
{
|
||||
"glob": "_redirects",
|
||||
"input": "src",
|
||||
"output": "/"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"node_modules/chartist/dist/index.scss",
|
||||
"src/styles/styles.scss"
|
||||
],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": ["src/styles"]
|
||||
},
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildOptimizer": true,
|
||||
"extractLicenses": true,
|
||||
"namedChunks": false,
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
]
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"extractLicenses": false,
|
||||
"namedChunks": true,
|
||||
"optimization": false,
|
||||
"sourceMap": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb"
|
||||
}
|
||||
],
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.development.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"local": {
|
||||
"buildOptimizer": false,
|
||||
"extractLicenses": false,
|
||||
"namedChunks": true,
|
||||
"optimization": false,
|
||||
"sourceMap": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb"
|
||||
}
|
||||
],
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.local.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "headup_angular:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "headup_angular:build:development"
|
||||
},
|
||||
"local": {
|
||||
"buildTarget": "headup_angular:build:local"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"buildTarget": "headup_angular:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-eslint/builder:lint",
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.html"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+3481
-1378
File diff suppressed because it is too large
Load Diff
+34
-26
@@ -1,57 +1,65 @@
|
||||
{
|
||||
"name": "headup-app",
|
||||
"version": "1.0.0",
|
||||
"author": "Solide Apps <contact@rampeur.fr>",
|
||||
"author": "Solide Apps <admin@unespace.com>",
|
||||
"contributors": [
|
||||
"Julien Gautier <rampeur@gmail.com>"
|
||||
"Julien Gautier <julien.gautier@unespace.com>"
|
||||
],
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve --configuration development",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"local": "ng serve --configuration local",
|
||||
"dev": "APP_ENV=local ng serve --configuration development",
|
||||
"dev": "ng serve --configuration local",
|
||||
"local": "APP_ENV=local ng serve --port 4400 --configuration local",
|
||||
"lint": "ng lint",
|
||||
"test": "ng test"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.20.2 || ^18.19.1 || ^20.11.1"
|
||||
"node": "^18.19.1 || ^20.11.1"
|
||||
},
|
||||
"license": "UNLICENSED",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.2.3",
|
||||
"@angular/cdk": "^17.2.1",
|
||||
"@angular/common": "^17.2.3",
|
||||
"@angular/compiler": "^17.2.3",
|
||||
"@angular/core": "^17.2.3",
|
||||
"@angular/forms": "^17.2.3",
|
||||
"@angular/material": "^17.2.1",
|
||||
"@angular/platform-browser": "^17.2.3",
|
||||
"@angular/platform-browser-dynamic": "^17.2.3",
|
||||
"@angular/router": "^17.2.3",
|
||||
"@angular/animations": "^17.3.5",
|
||||
"@angular/cdk": "^17.3.5",
|
||||
"@angular/common": "^17.3.5",
|
||||
"@angular/compiler": "^17.3.5",
|
||||
"@angular/core": "^17.3.5",
|
||||
"@angular/forms": "^17.3.5",
|
||||
"@angular/localize": "^17.3.5",
|
||||
"@angular/material": "^17.3.6",
|
||||
"@angular/platform-browser": "^17.3.5",
|
||||
"@angular/platform-browser-dynamic": "^17.3.5",
|
||||
"@angular/router": "^17.3.5",
|
||||
"bootstrap": "^5.3.3",
|
||||
"chart.js": "^4.4.1",
|
||||
"chart.js": "^4.4.2",
|
||||
"chartist": "^1.3.0",
|
||||
"ng-chartist": "^8.2.0",
|
||||
"ng2-charts": "^5.0.4",
|
||||
"ngx-skeleton-loader": "^8.1.0",
|
||||
"ng2-charts": "^6.0.0",
|
||||
"ngx-skeleton-loader": "^9.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "^0.14.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.2.1",
|
||||
"@angular/cli": "~17.2.1",
|
||||
"@angular/compiler-cli": "^17.2.3",
|
||||
"@types/jasmine": "~4.3.0",
|
||||
"jasmine-core": "~4.6.0",
|
||||
"@angular-devkit/build-angular": "^17.3.5",
|
||||
"@angular-eslint/builder": "17.3.0",
|
||||
"@angular-eslint/eslint-plugin": "17.3.0",
|
||||
"@angular-eslint/eslint-plugin-template": "17.3.0",
|
||||
"@angular-eslint/schematics": "17.3.0",
|
||||
"@angular-eslint/template-parser": "17.3.0",
|
||||
"@angular/cli": "^17.3.5",
|
||||
"@angular/compiler-cli": "^17.3.5",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "7.2.0",
|
||||
"@typescript-eslint/parser": "7.2.0",
|
||||
"eslint": "^8.57.0",
|
||||
"jasmine-core": "~5.1.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"typescript": "~5.3.3"
|
||||
"typescript": "~5.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/* /index.html 200
|
||||
@@ -1,48 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'calculator',
|
||||
loadChildren: () => import('./components/calculator/calculator.module').then(m => m.CalculatorModule)
|
||||
},
|
||||
{
|
||||
path: 'dashboard',
|
||||
loadChildren: () => import('./components/dashboard/dashboard.module').then(m => m.DashboardModule)
|
||||
},
|
||||
{
|
||||
path: 'jump',
|
||||
loadChildren: () => import('./components/jump/jump.module').then(m => m.JumpModule)
|
||||
},
|
||||
{
|
||||
path: 'logbook',
|
||||
loadChildren: () => import('./components/logbook/logbook.module').then(m => m.LogbookModule)
|
||||
},
|
||||
{
|
||||
path: 'profile',
|
||||
loadChildren: () => import('./components/profile/profile.module').then(m => m.ProfileModule)
|
||||
},
|
||||
{
|
||||
path: 'qcm',
|
||||
loadChildren: () => import('./components/qcm/qcm.module').then(m => m.QCMModule)
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
loadChildren: () => import('./components/settings/settings.module').then(m => m.SettingsModule)
|
||||
}
|
||||
];
|
||||
|
||||
/* @NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule]
|
||||
}) */
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes, {
|
||||
// preload all modules; optionally we could
|
||||
// implement a custom preloading strategy for just some
|
||||
// of the modules (PRs welcome 😉)
|
||||
preloadingStrategy: PreloadAllModules
|
||||
})],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
@@ -1,12 +1 @@
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * Main wrapper * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<huapp-full-layout></huapp-full-layout>
|
||||
<!--
|
||||
<div class="p-5">
|
||||
<router-outlet><huapp-spinner></huapp-spinner></router-outlet>
|
||||
</div>
|
||||
<huapp-layout-header></huapp-layout-header>
|
||||
<router-outlet><huapp-spinner></huapp-spinner></router-outlet>
|
||||
<huapp-layout-footer></huapp-layout-footer>
|
||||
-->
|
||||
<app-full-layout></app-full-layout>
|
||||
@@ -1,29 +1,29 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule],
|
||||
declarations: [AppComponent]
|
||||
}));
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'Head Up'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('Head Up');
|
||||
});
|
||||
it(`should have the 'headup_angular' title`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('headup_angular');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('.content span')?.textContent).toContain('Head Up app is running!');
|
||||
});
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, headup_angular');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,38 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MAT_DATE_LOCALE, MAT_DATE_FORMATS, provideNativeDateAdapter } from '@angular/material/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { FullComponent } from 'src/app/components/shared/layout';
|
||||
import { UserService } from "src/app/core/services";
|
||||
import { SpinnerComponent } from 'src/app/components';
|
||||
|
||||
export const MY_FORMATS = {
|
||||
parse: {
|
||||
dateInput: 'DD/MM/YYYY',
|
||||
},
|
||||
display: {
|
||||
dateInput: 'DD/MM/YYYY',
|
||||
monthYearLabel: 'MMM YYYY',
|
||||
dateA11yLabel: 'LL',
|
||||
monthYearA11yLabel: 'MMMM-YYYY',
|
||||
},
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-root',
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [
|
||||
FullComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: MAT_DATE_LOCALE, useValue: 'fr-FR' },
|
||||
{ provide: MAT_DATE_FORMATS, useValue: MY_FORMATS },
|
||||
provideNativeDateAdapter(MY_FORMATS)
|
||||
],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
styleUrl: './app.component.scss'
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'Head Up';
|
||||
export class AppComponent implements OnInit {
|
||||
public title = 'Head Up';
|
||||
|
||||
constructor(private userService: UserService, private titleService: Title) { }
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { APP_INITIALIZER, ApplicationConfig, LOCALE_ID } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
//import { provideRouter, withComponentInputBinding, withDebugTracing } from '@angular/router';
|
||||
import { provideRouter, withViewTransitions } from '@angular/router';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import localeFr from '@angular/common/locales/fr';
|
||||
import { provideCharts, withDefaultRegisterables } from 'ng2-charts';
|
||||
import { EMPTY } from "rxjs";
|
||||
|
||||
registerLocaleData(localeFr);
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import { apiInterceptor, tokenInterceptor, errorInterceptor } from "src/app/core/interceptors";
|
||||
import { JwtService, UserService } from "src/app/core/services";
|
||||
|
||||
export function initAuth(jwtService: JwtService, userService: UserService) {
|
||||
return () => (jwtService.getToken() ? userService.getCurrentUser() : EMPTY);
|
||||
}
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
//provideRouter(routes, withComponentInputBinding(), withDebugTracing()),
|
||||
provideRouter(routes, withViewTransitions()),
|
||||
provideHttpClient(
|
||||
withInterceptors([apiInterceptor, tokenInterceptor, errorInterceptor])
|
||||
),
|
||||
provideAnimations(),
|
||||
provideCharts(withDefaultRegisterables()),
|
||||
Title,
|
||||
{ provide: LOCALE_ID, useValue: 'fr-FR' },
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: initAuth,
|
||||
deps: [JwtService, UserService],
|
||||
multi: true,
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
import { LOCALE_ID, NgModule } from '@angular/core';
|
||||
import { BrowserModule, Title } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import localeFr from '@angular/common/locales/fr';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { CoreModule } from './core/core.module';
|
||||
import { AuthModule } from './components/auth/auth.module';
|
||||
import { DashboardModule } from './components/dashboard/dashboard.module';
|
||||
import { SpinnerComponent } from './components/shared';
|
||||
import { FullComponent } from 'src/app/components/shared/layout';
|
||||
//import { HeaderComponent, FooterComponent } from 'src/app/components/shared/layout';
|
||||
|
||||
registerLocaleData(localeFr);
|
||||
|
||||
@NgModule({
|
||||
declarations: [ AppComponent ],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
CoreModule,
|
||||
AuthModule,
|
||||
AppRoutingModule,
|
||||
BrowserAnimationsModule,
|
||||
SpinnerComponent,
|
||||
FullComponent,
|
||||
DashboardModule
|
||||
//HeaderComponent, FooterComponent
|
||||
],
|
||||
providers: [ Title, { provide: LOCALE_ID, useValue: 'fr-FR' } ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
import { AUTH_ROUTES, NOAUTH_ROUTES } from 'src/app/routes';
|
||||
|
||||
export const routes: Routes = [ ...AUTH_ROUTES, ...NOAUTH_ROUTES];
|
||||
@@ -0,0 +1,51 @@
|
||||
<div @flyInOut>
|
||||
<div class="content">
|
||||
<div class="d-flex">
|
||||
<div class="flex-fill">
|
||||
<h1 class="mb-1">{{title}}</h1>
|
||||
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
|
||||
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="flex-spacer"></span>
|
||||
<div class="align-self-center me-2">
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuAeronef" aria-label="Menu Aeronef">
|
||||
<mat-icon fontIcon="more_vert"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuAeronef="matMenu">
|
||||
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
|
||||
<button mat-menu-item>
|
||||
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ subtitle }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-4">
|
||||
@if (displayCharts) {
|
||||
<div class="row">
|
||||
<div class="col-xl-4 col-lg-5 col-md-6 col-sm-12">
|
||||
<app-pie-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [colors]="seriesColor"></app-pie-chart>
|
||||
</div>
|
||||
<div class="col-xl-8 col-lg-7 col-md-6 col-sm-12">
|
||||
<!--<app-bar-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [colors]="seriesColor" label="Nombre total de sauts" [legend]="false"></app-bar-chart>-->
|
||||
<app-bars-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor" [legend]="false"></app-bars-chart>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="d-flex">
|
||||
<div class="flex-fill py-2">
|
||||
<mat-divider class="mt-3 mb-0"></mat-divider>
|
||||
<app-history-table [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [rows]="seriesRow" [colors]="seriesColorClass"></app-history-table>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AeronefComponent } from './aeronef.component';
|
||||
|
||||
describe('AeronefComponent', () => {
|
||||
let component: AeronefComponent;
|
||||
let fixture: ComponentFixture<AeronefComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AeronefComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AeronefComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatGridListModule } from '@angular/material/grid-list';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { MenuItems } from 'src/app/components/shared';
|
||||
import { HistoryTableComponent } from 'src/app/components/shared/helpers-jump';
|
||||
import { BarsChartComponent, PieChartComponent } from 'src/app/components/shared/helpers-chart';
|
||||
import { Jump, AeronefsPageData } from 'src/app/core/models';
|
||||
import { UtilitiesService } from 'src/app/core/services';
|
||||
import { AeronefByImat, AeronefByYear } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-aeronef',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe,
|
||||
MatBadgeModule, MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatExpansionModule, MatGridListModule, MatIconModule, MatMenuModule,
|
||||
BarsChartComponent, HistoryTableComponent, PieChartComponent
|
||||
],
|
||||
templateUrl: './aeronef.component.html',
|
||||
styleUrl: './aeronef.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateX(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateX(-100%)' }),
|
||||
animate(200)
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateX(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
})
|
||||
export class AeronefComponent implements OnInit, OnDestroy {
|
||||
private _data: Subscription = new Subscription();
|
||||
private _aeronefsByImat!: Array<AeronefByImat>;
|
||||
private _aeronefsByYear!: Array<AeronefByYear>;
|
||||
public lastJump: Jump = {} as Jump;
|
||||
public title = 'Aéronefs';
|
||||
public subtitle: string = 'Nombre total de sauts par aéronef';
|
||||
public displayCharts = false;
|
||||
public destroyRef = inject(DestroyRef);
|
||||
public seriesHeader: string[] = [];
|
||||
public seriesName: string[] = [];
|
||||
public seriesValue: number[] = [];
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
const data$: Observable<{ aeronefsPageData: AeronefsPageData }> = this.route.data as Observable<{ aeronefsPageData: AeronefsPageData }>;
|
||||
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { aeronefsPageData: AeronefsPageData }) => {
|
||||
const pageData: AeronefsPageData = data.aeronefsPageData;
|
||||
this.lastJump = pageData.lastjump;
|
||||
this._loadAeronefByImat(pageData);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._data.unsubscribe();
|
||||
}
|
||||
|
||||
private _loadAeronefByImat(pageData: AeronefsPageData): void {
|
||||
this._aeronefsByImat = pageData.aeronefsByImat.map((row: AeronefByImat) => {
|
||||
this.seriesName.push(`${row.aeronef} ${row.imat}`);
|
||||
this.seriesValue.push(row.count);
|
||||
return row;
|
||||
});
|
||||
this._loadAeronefByYear(pageData);
|
||||
}
|
||||
|
||||
private _loadAeronefByYear(pageData: AeronefsPageData): void {
|
||||
this.seriesHeader = ['2018', '2019', '2020', '2021', '2022', '2023', '2024'];
|
||||
const values: Array<number> = this.seriesHeader.map(() => 0);
|
||||
this.seriesName.forEach(() => {
|
||||
this.seriesRow.push([...values]);
|
||||
});
|
||||
this._aeronefsByYear = pageData.aeronefsByImatByYear.map((row: AeronefByYear) => {
|
||||
const aeronef: string = row.aeronef;
|
||||
const imat: string = row.imat;
|
||||
const year: string = row.year.toString();
|
||||
this.seriesRow[this.seriesName.indexOf(`${aeronef} ${imat}`)][this.seriesHeader.indexOf(year)] = row.count;
|
||||
return row;
|
||||
});
|
||||
this.displayCharts = true;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { NoAuthGuard } from 'src/app/core/services/no-auth-guard.service';
|
||||
import { AuthComponent } from './auth.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'login',
|
||||
component: AuthComponent,
|
||||
canActivate: [NoAuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'register',
|
||||
component: AuthComponent,
|
||||
canActivate: [NoAuthGuard]
|
||||
},
|
||||
{
|
||||
path: 'authenticate',
|
||||
component: AuthComponent,
|
||||
canActivate: [NoAuthGuard]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AuthRoutingModule { }
|
||||
@@ -1,77 +1,113 @@
|
||||
<div class="container content auth-page">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3 col-xs-12">
|
||||
<h1 class="text-xs-center">{{ title }}</h1>
|
||||
<p class="text-xs-center">
|
||||
<a [routerLink]="['/login']" *ngIf="authType === 'register'">Vous avez déjà un compte ?</a>
|
||||
<a [routerLink]="['/register']" *ngIf="authType === 'login'">Vous n'avez pas encore de compte ?</a>
|
||||
</p>
|
||||
<huapp-list-errors [errors]="errors"></huapp-list-errors>
|
||||
<form [formGroup]="authForm" (ngSubmit)="submitForm()">
|
||||
<fieldset [disabled]="isSubmitting" class="w-100">
|
||||
<legend [hidden]="true">Informations personnelles</legend>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Votre adresse email" formControlName="email" required>
|
||||
<mat-error *ngIf="this.authForm.controls['email'].status === 'INVALID'">{{getErrorMessage('email')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12" *ngIf="authType === 'register'">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Votre nom" formControlName="lastname" required>
|
||||
<mat-error *ngIf="this.authForm.controls['lastname'].status === 'INVALID'">{{getErrorMessage('lastname')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12" *ngIf="authType === 'register'">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Votre prénom" formControlName="firstname" required>
|
||||
<mat-error *ngIf="this.authForm.controls['firstname'].status === 'INVALID'">{{getErrorMessage('firstname')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12" *ngIf="authType === 'register'">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Numéro de téléphone" formControlName="phone" required>
|
||||
<mat-error *ngIf="this.authForm.controls['phone'].status === 'INVALID'">{{getErrorMessage('phone')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12" *ngIf="authType === 'register'">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Pseudo" formControlName="username">
|
||||
<mat-error *ngIf="this.authForm.controls['username'].status === 'INVALID'">{{getErrorMessage('username')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12" *ngIf="authType === 'register'">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Numéro de licence FFP" formControlName="licence">
|
||||
<mat-error *ngIf="this.authForm.controls['licence'].status === 'INVALID'">{{getErrorMessage('licence')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12" *ngIf="authType === 'register'">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Indiquez le poids en Kg" formControlName="poids">
|
||||
<mat-error *ngIf="this.authForm.controls['poids'].status === 'INVALID'">{{getErrorMessage('poids')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset [disabled]="isSubmitting" class="w-100">
|
||||
<legend [hidden]="true">Informations de connexion</legend>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="password" placeholder="Mot de passe" formControlName="password" required>
|
||||
<mat-error *ngIf="this.authForm.controls['password'].status === 'INVALID'">{{getErrorMessage('password')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12" *ngIf="authType === 'register'">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="password" placeholder="Confirmez votre nouveau mot de passe" formControlName="confirmPassword" required>
|
||||
<mat-error *ngIf="this.authForm.controls['confirmPassword'].status === 'INVALID'">{{getErrorMessage('confirmPassword')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button mat-raised-button color="primary" [disabled]="!authForm.valid" type="submit" class="float-sm-end">{{ btnTitle }}</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3 col-xs-12">
|
||||
<h1 class="text-xs-center">{{ title }}</h1>
|
||||
<p class="text-xs-center">
|
||||
@if (authType === 'register') {
|
||||
<a [routerLink]="['/login']">Vous avez déjà un compte ?</a>
|
||||
}
|
||||
@if (authType === 'login') {
|
||||
<a [routerLink]="['/register']">Vous n'avez pas encore de compte ?</a>
|
||||
}
|
||||
</p>
|
||||
<app-list-errors [errors]="errors"></app-list-errors>
|
||||
<form [formGroup]="authForm" (ngSubmit)="submitForm()">
|
||||
<fieldset [disabled]="isSubmitting" class="w-100">
|
||||
<legend [hidden]="true">Informations personnelles</legend>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Votre adresse email" formControlName="email" required>
|
||||
@if (this.authForm.controls['email'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('email')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@if (authType === 'register') {
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Votre nom" formControlName="lastname" required>
|
||||
@if (this.authForm.controls['lastname'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('lastname')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
@if (authType === 'register') {
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Votre prénom" formControlName="firstname" required>
|
||||
@if (this.authForm.controls['firstname'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('firstname')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
@if (authType === 'register') {
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Numéro de téléphone" formControlName="phone" required>
|
||||
@if (this.authForm.controls['phone'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('phone')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
@if (authType === 'register') {
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Pseudo" formControlName="username">
|
||||
@if (this.authForm.controls['username'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('username')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
@if (authType === 'register') {
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Numéro de licence FFP" formControlName="licence">
|
||||
@if (this.authForm.controls['licence'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('licence')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
@if (authType === 'register') {
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="text" placeholder="Indiquez le poids en Kg" formControlName="poids">
|
||||
@if (this.authForm.controls['poids'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('poids')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
</fieldset>
|
||||
<fieldset [disabled]="isSubmitting" class="w-100">
|
||||
<legend [hidden]="true">Informations de connexion</legend>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="password" placeholder="Mot de passe" formControlName="password" required>
|
||||
@if (this.authForm.controls['password'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('password')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@if (authType === 'register') {
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<input matInput type="password" placeholder="Confirmez votre nouveau mot de passe" formControlName="confirmPassword" required>
|
||||
@if (this.authForm.controls['confirmPassword'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('confirmPassword')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
<div class="col-12">
|
||||
<button mat-raised-button color="primary" [disabled]="!authForm.valid" type="submit" class="float-sm-end">{{ btnTitle }}</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,8 @@ describe('AuthComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AuthComponent]
|
||||
});
|
||||
imports: [AuthComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(AuthComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { NgIf } from '@angular/common';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { UntypedFormBuilder, UntypedFormGroup, AbstractControl, Validators, ValidatorFn, ValidationErrors, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
@@ -7,7 +7,7 @@ import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
|
||||
import { Observable, Observer, Subscription } from 'rxjs';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { Errors } from 'src/app/core/models';
|
||||
@@ -17,13 +17,18 @@ import { ListErrorsComponent, ShowAuthedDirective } from 'src/app/components/sha
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgIf, RouterLink, FormsModule, ReactiveFormsModule,
|
||||
MatFormFieldModule, MatInputModule, MatButtonModule,
|
||||
ListErrorsComponent, ShowAuthedDirective
|
||||
RouterLink,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatButtonModule,
|
||||
ListErrorsComponent,
|
||||
ShowAuthedDirective
|
||||
],
|
||||
selector: 'huapp-auth',
|
||||
selector: 'app-auth',
|
||||
templateUrl: './auth.component.html',
|
||||
styleUrls: ['./auth.component.scss']
|
||||
styleUrl: './auth.component.scss'
|
||||
})
|
||||
export class AuthComponent implements OnInit, OnDestroy {
|
||||
private _url: Subscription = new Subscription();
|
||||
@@ -31,9 +36,10 @@ export class AuthComponent implements OnInit, OnDestroy {
|
||||
authType = '';
|
||||
title = '';
|
||||
btnTitle = '';
|
||||
errors!: Errors;
|
||||
errors: Errors = { errors: {} };
|
||||
isSubmitting = false;
|
||||
authForm!: UntypedFormGroup;
|
||||
destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@@ -44,7 +50,7 @@ export class AuthComponent implements OnInit, OnDestroy {
|
||||
) {
|
||||
this._resetErrors();
|
||||
// use FormBuilder to create a form group
|
||||
let controlsConfig = {
|
||||
const controlsConfig = {
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
username: '',
|
||||
firstname: '',
|
||||
@@ -78,10 +84,10 @@ export class AuthComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
// add form control for username if this is the register page
|
||||
if (this.authType === 'register') {
|
||||
this.authForm.controls["firstname"].setValidators([Validators.required]);
|
||||
this.authForm.controls["lastname"].setValidators([Validators.required]);
|
||||
this.authForm.controls["phone"].setValidators([Validators.required]);
|
||||
this.authForm.controls["confirmPassword"].setValidators([Validators.minLength(8), Validators.required]);
|
||||
this.authForm.controls['firstname'].setValidators([Validators.required]);
|
||||
this.authForm.controls['lastname'].setValidators([Validators.required]);
|
||||
this.authForm.controls['phone'].setValidators([Validators.required]);
|
||||
this.authForm.controls['confirmPassword'].setValidators([Validators.minLength(8), Validators.required]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -93,14 +99,16 @@ export class AuthComponent implements OnInit, OnDestroy {
|
||||
|
||||
submitForm() {
|
||||
this.isSubmitting = true;
|
||||
this.errors = { errors: {} };
|
||||
|
||||
if (this.authForm.valid) {
|
||||
this._resetErrors();
|
||||
const credentials = this.authForm.value;
|
||||
const user$ = this.userService.attemptAuth(this.authType, credentials).pipe(take(1));
|
||||
const user$ = this.userService.attemptAuth(this.authType, credentials).pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._user = user$.subscribe({
|
||||
next: () => this.router.navigateByUrl('/'),
|
||||
next: () => void this.router.navigateByUrl('/'),
|
||||
error: (err) => {
|
||||
console.log(err);
|
||||
this.errors = err;
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { NoAuthGuard } from 'src/app/core/services/no-auth-guard.service';
|
||||
import { AuthComponent } from './auth.component';
|
||||
import { AuthRoutingModule } from './auth-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
AuthRoutingModule,
|
||||
AuthComponent
|
||||
],
|
||||
providers: [NoAuthGuard]
|
||||
})
|
||||
export class AuthModule { }
|
||||
@@ -1,18 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { CalculatorComponent } from './calculator.component';
|
||||
import { AuthGuard } from 'src/app/core/services';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CalculatorComponent,
|
||||
canActivate: [AuthGuard]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class CalculatorRoutingModule { }
|
||||
@@ -1,462 +1,259 @@
|
||||
<div class="calculator-page content">
|
||||
<mat-card class="calculator">
|
||||
<mat-card-header class="bg-warn rounded-top py-3 px-4">
|
||||
<mat-card-title>Calcul de taille de voile</mat-card-title>
|
||||
<mat-card-subtitle>DT48 - 08 février 2024</mat-card-subtitle>
|
||||
<div @flyInOut>
|
||||
<div class="calculator-page content">
|
||||
<div class="d-flex">
|
||||
<div class="flex-fill">
|
||||
<h1 class="mb-1">{{title}}</h1>
|
||||
<span class="me-2 fs-6"><span class="fs-5 mx-1">DT48</span> - 08 février 2024</span>
|
||||
</div>
|
||||
<span class="flex-spacer"></span>
|
||||
<form #searchFormData="ngForm" class="inputs-form">
|
||||
<mat-form-field class="bg-crystal me-3" subscriptSizing="dynamic">
|
||||
<mat-label>Nombre de sauts</mat-label>
|
||||
<input matInput [(ngModel)]="inputs.jumps" (change)="refresh()" type="number" min="0" max="2000" step="1" name="jumps">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="bg-crystal me-3" subscriptSizing="dynamic">
|
||||
<mat-label>Poids nu</mat-label>
|
||||
<input matInput [(ngModel)]="inputs.weight" (change)="refresh()" type="number" min="60" max="110" name="weight">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="bg-crystal me-3" subscriptSizing="dynamic">
|
||||
<mat-label>Poids équipement</mat-label>
|
||||
<input matInput [(ngModel)]="inputs.gear" (change)="refresh()" type="number" min="0" name="gear">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="bg-crystal me-3" subscriptSizing="dynamic">
|
||||
<mat-label>Taille actuelle</mat-label>
|
||||
<input matInput [(ngModel)]="inputs.current" (change)="refresh()" type="number" min="0" name="current" id="current" placeholder="Taille actuelle" >
|
||||
</mat-form-field>
|
||||
</form>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuCalculator" aria-label="Menu Calculator">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuCalculator="matMenu">
|
||||
<ng-container *ngFor="let menuitem of menuItems.getMenuCalculator()">
|
||||
<button mat-menu-item [routerLink]="['/', menuitem.state]" *ngIf="menuitem.type === 'link'">
|
||||
<div class="align-self-center me-2">
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuCalculator" aria-label="Menu Calculator">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuCalculator="matMenu">
|
||||
@for (menuitem of menuItems.getMenuCalculator(); track menuitem) {
|
||||
@if (menuitem.type === 'link') {
|
||||
<button mat-menu-item [routerLink]="['/', menuitem.state]">
|
||||
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
</mat-menu>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="my-4">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-8 col-sm-12">
|
||||
<div *ngIf="displayCharts" class="linechart position-relative w-100 mt-3">
|
||||
<canvas baseChart class="mx-auto" width="900" height="350"
|
||||
[data]="chartConfig.lineChartData"
|
||||
[options]="chartConfig.lineChartOptions"
|
||||
[legend]="chartConfig.lineChartLegend"
|
||||
[type]="'line'">
|
||||
</canvas>
|
||||
}
|
||||
}
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<mat-card class="calculator">
|
||||
<mat-card-header class="bg-warn rounded-top pt-1 pb-2 px-2">
|
||||
<form #searchFormData="ngForm" class="inputs-form">
|
||||
<mat-form-field class="bg-crystal me-3" subscriptSizing="dynamic" color="accent">
|
||||
<mat-label>Nombre de sauts</mat-label>
|
||||
<input matInput [(ngModel)]="inputs.jumps" (change)="refresh()" type="number" min="0" max="2000"
|
||||
step="1" name="jumps">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="bg-crystal me-3" subscriptSizing="dynamic" color="accent">
|
||||
<mat-label>Poids nu</mat-label>
|
||||
<input matInput [(ngModel)]="inputs.weight" (change)="refresh()" type="number" min="30"
|
||||
max="110" name="weight">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="bg-crystal me-3" subscriptSizing="dynamic" color="accent">
|
||||
<mat-label>Poids équipement</mat-label>
|
||||
<input matInput [(ngModel)]="inputs.gear" (change)="refresh()" type="number" min="0"
|
||||
name="gear">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="bg-crystal me-3" subscriptSizing="dynamic" color="accent">
|
||||
<mat-label>Taille actuelle</mat-label>
|
||||
<input matInput [(ngModel)]="inputs.current" (change)="refresh()" type="number" min="0"
|
||||
name="current" id="current" placeholder="Taille actuelle">
|
||||
</mat-form-field>
|
||||
</form>
|
||||
<span class="flex-spacer"></span>
|
||||
<div class="flex-fill"></div>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="my-4">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-8 col-sm-12">
|
||||
<table class="table table-striped table-dark table-hover mat-elevation-z2">
|
||||
<thead>
|
||||
<tr class="warn">
|
||||
<th class="first"></th>
|
||||
<th class="text-nowrap">Taille actuelle</th>
|
||||
<th class="text-nowrap">Taille min</th>
|
||||
<th class="text-nowrap">Taille min -11%</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="warn">
|
||||
<th class="first">Taille de voile en ft<sup>2</sup></th>
|
||||
<td class="font-monospace text-{{ info.state.color }}"> ●
|
||||
{{ info.sizesFeet.current }} </td>
|
||||
<td class="font-monospace text-success"> ● {{ info.sizesFeet.min }} </td>
|
||||
<td class="font-monospace text-warning"> ● {{ info.sizesFeet.min11 }} </td>
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Charge alaire</th>
|
||||
<td class="font-monospace">{{ info.charges.current | number : '1.2-3' }}</td>
|
||||
<td class="font-monospace">{{ info.charges.min | number : '1.2-3' }}</td>
|
||||
<td class="font-monospace">{{ info.charges.min11 | number : '1.2-3' }}</td>
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Taille de voile en m<sup>2</sup></th>
|
||||
<td class="font-monospace">{{ info.sizesMeter.current | number : '1.2-3' }}</td>
|
||||
<td class="font-monospace">{{ info.sizesMeter.min | number : '1.2-3' }}</td>
|
||||
<td class="font-monospace">{{ info.sizesMeter.min11 | number : '1.2-3' }}</td>
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Coefficient kilo/livres</th>
|
||||
<td colspan="3" class="font-monospace text-start">{{ coeffKgLbs }}</td>
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Coefficient pied/mètre</th>
|
||||
<td colspan="3" class="font-monospace text-start">{{ coeffFtM }}</td>
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Formule charge alaire</th>
|
||||
<td colspan="3" class="font-monospace text-start">
|
||||
( ( {{ inputs.weight }} + {{ inputs.gear }} ) * {{ coeffKgLbs }} ) /
|
||||
{{ inputs.current }} = {{ info.charges.current | number : '1.5' }}<br />
|
||||
( ( poids nu en kg + poids equipement en kg ) * coefficient kg/lbs ) / taille de
|
||||
voile en ft<sup>2</sup>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-8 col-sm-12">
|
||||
@if (displayCharts) {
|
||||
<div class="linechart position-relative w-100 mt-3">
|
||||
<canvas baseChart class="mx-auto" width="800" height="280"
|
||||
[data]="chartConfig.lineChartData" [options]="chartConfig.lineChartOptions"
|
||||
[legend]="chartConfig.lineChartLegend" [type]="'line'">
|
||||
</canvas>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-8 col-sm-12">
|
||||
<table class="table table-striped table-dark table-hover mat-elevation-z2">
|
||||
<thead>
|
||||
<tr class="warn">
|
||||
<th class="first"></th>
|
||||
<th class="text-nowrap">Taille actuelle</th>
|
||||
<th class="text-nowrap">Taille min</th>
|
||||
<th class="text-nowrap">Taille min -11%</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="warn">
|
||||
<th class="first">Taille de voile en ft<sup>2</sup></th>
|
||||
<td class="font-monospace text-{{ info.state.color }}"> ● {{ info.sizesFeet.current }} </td>
|
||||
<td class="font-monospace text-success"> ● {{ info.sizesFeet.min }} </td>
|
||||
<td class="font-monospace text-warning"> ● {{ info.sizesFeet.min11 }} </td>
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Charge alaire</th>
|
||||
<td class="font-monospace">{{ info.charges.current | number : '1.2-3' }}</td>
|
||||
<td class="font-monospace">{{ info.charges.min | number : '1.2-3' }}</td>
|
||||
<td class="font-monospace">{{ info.charges.min11 | number : '1.2-3' }}</td>
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Taille de voile en m<sup>2</sup></th>
|
||||
<td class="font-monospace">{{ info.sizesMeter.current | number : '1.2-3' }}</td>
|
||||
<td class="font-monospace">{{ info.sizesMeter.min | number : '1.2-3' }}</td>
|
||||
<td class="font-monospace">{{ info.sizesMeter.min11 | number : '1.2-3' }}</td>
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Coefficient kilo/livres</th>
|
||||
<td colspan="3" class="font-monospace text-start">{{ coeffKgLbs }}</td>
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Coefficient pied/mètre</th>
|
||||
<td colspan="3" class="font-monospace text-start">{{ coeffFtM }}</td>
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Formule charge alaire</th>
|
||||
<td colspan="3" class="font-monospace text-start">
|
||||
( ( {{ inputs.weight }} + {{ inputs.gear }} ) * {{ coeffKgLbs }} ) / {{ inputs.current }} = {{ info.charges.current | number : '1.5' }}<br />
|
||||
( ( poids nu en kg + poids equipement en kg ) * coefficient kg/lbs ) / taille de voile en ft<sup>2</sup>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<h4 class="py-2 mb-0">Tailles de voile pour {{ inputs.weight }} kg</h4>
|
||||
<div>
|
||||
Prochain changement de valeurs dans {{ ((this.currentRange.end + 1) - inputs.jumps) }}
|
||||
{{ ((this.currentRange.end + 1) - inputs.jumps) > 1 ? 'sauts' : 'saut'}}<br />
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<h4 class="border-bottom border-secondary py-2 mb-2">Tailles de voile pour {{ inputs.weight }} kg</h4>
|
||||
<table class="table table-striped table-dark table-hover mat-elevation-z2 mb-1">
|
||||
<thead>
|
||||
<tr class="warn">
|
||||
<th class="first"></th>
|
||||
<th *ngFor="let header of sizesHeader; let index = index;" class="pl-3 pr-3 {{ tableHeader[(index+1)].active }}">{{ header.name }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="warn">
|
||||
<th class="first">Taille de voile en ft<sup>2</sup></th>
|
||||
<td *ngFor="let value of sizesValuesFeet; let index = index;" class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td>
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Abbattement 11%</th>
|
||||
<td *ngFor="let value of sizesMinValuesFeet; let index = index;" class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td>
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Charge alaire</th>
|
||||
<td *ngFor="let charge of sizesCharges; let index = index;" class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ charge | number : '1.2-3' }}</td>
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Taille de voile en m<sup>2</sup></th>
|
||||
<td *ngFor="let value of sizesValuesMeter; let index = index;" class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value | number : '1.2-2' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h4 class="border-bottom border-secondary py-2 mb-2">Tableau des tailles de voile par poids</h4>
|
||||
<table mat-table [dataSource]="canopy_size_table" matSort class="table table-striped table-dark table-hover mat-elevation-z2">
|
||||
<!-- weight column -->
|
||||
<ng-container matColumnDef="weight">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header class="first {{tableHeader[0].active}}"> {{tableHeader[0].name}} </th>
|
||||
<th mat-cell *matCellDef="let element;" class="text-end ps-3 pe-4 fs-6" role="button" (click)="setCurrentWeight(element.weight)">
|
||||
<span class="text-warn d-block">{{element.weight}}</span>
|
||||
</th>
|
||||
</ng-container>
|
||||
<!-- range_1 column -->
|
||||
<ng-container matColumnDef="range_1">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[1].active}}"> {{tableHeader[1].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;" class="pl-3 text-nowrap fs-6 {{element.ranges[0].active}}">{{element.ranges[0].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_2 column -->
|
||||
<ng-container matColumnDef="range_2">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[2].active}}"> {{tableHeader[2].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;" class="pl-3 text-nowrap fs-6 {{element.ranges[1].active}}">{{element.ranges[1].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_3 column -->
|
||||
<ng-container matColumnDef="range_3">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[3].active}}"> {{tableHeader[3].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;" class="pl-3 text-nowrap fs-6 {{element.ranges[2].active}}">{{element.ranges[2].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_4 column -->
|
||||
<ng-container matColumnDef="range_4">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[4].active}}"> {{tableHeader[4].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;" class="pl-3 text-nowrap fs-6 {{element.ranges[3].active}}">{{element.ranges[3].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_5 column -->
|
||||
<ng-container matColumnDef="range_5">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[5].active}}"> {{tableHeader[5].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;" class="pl-3 text-nowrap fs-6 {{element.ranges[4].active}}">{{element.ranges[4].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_6 column -->
|
||||
<ng-container matColumnDef="range_6">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[6].active}}"> {{tableHeader[6].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;" class="pl-3 text-nowrap fs-6 {{element.ranges[5].active}}">{{element.ranges[5].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_7 column -->
|
||||
<ng-container matColumnDef="range_7">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[7].active}}"> {{tableHeader[7].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;" class="pl-3 text-nowrap fs-6 {{element.ranges[6].active}}">{{element.ranges[6].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_8 column -->
|
||||
<ng-container matColumnDef="range_8">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[8].active}}"> {{tableHeader[8].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;" class="pl-3 text-nowrap fs-6 {{element.ranges[7].active}}">{{element.ranges[7].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_9 column -->
|
||||
<ng-container matColumnDef="range_9">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[9].active}}"> {{tableHeader[9].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;" class="pl-3 text-nowrap fs-6 {{element.ranges[8].active}}">{{element.ranges[8].value}}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: false" class="warn"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" class="text-middle font-monospace warn {{row.active}}" id="weight_{{row.weight}}"></tr>
|
||||
</table>
|
||||
<div class="clearfix">
|
||||
<mat-paginator [pageSize]="50" [pageSizeOptions]="[5, 10, 15, 25, 50]" showFirstLastButtons class="pull-right mb-1 mt-2"></mat-paginator>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<!--
|
||||
<div class="content">
|
||||
<h1>{{title}}</h1>
|
||||
<div class="col-xs-12">
|
||||
<div class="demo-buttons my-2">
|
||||
<button mat-button color="primary" class="me-2">Texte</button>
|
||||
<button mat-stroked-button color="primary" class="me-2">Texte Stroked</button>
|
||||
<button mat-flat-button color="primary" class="me-2">Texte Flat</button>
|
||||
<button mat-raised-button color="primary" class="me-2">Texte Raised</button>
|
||||
<button mat-icon-button color="primary" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
<button mat-fab color="primary" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
<button mat-mini-fab color="primary" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-buttons my-2">
|
||||
<button mat-button color="accent" class="me-2">Texte</button>
|
||||
<button mat-stroked-button color="accent" class="me-2">Texte Stroked</button>
|
||||
<button mat-flat-button color="accent" class="me-2">Texte Flat</button>
|
||||
<button mat-raised-button color="accent" class="me-2">Texte Raised</button>
|
||||
<button mat-icon-button color="accent" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
<button mat-fab color="accent" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
<button mat-mini-fab color="accent" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-buttons my-2">
|
||||
<button mat-button color="warn" class="me-2">Texte</button>
|
||||
<button mat-stroked-button color="warn" class="me-2">Texte Stroked</button>
|
||||
<button mat-flat-button color="warn" class="me-2">Texte Flat</button>
|
||||
<button mat-raised-button color="warn" class="me-2">Texte Raised</button>
|
||||
<button mat-icon-button color="warn" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
<button mat-fab color="warn" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
<button mat-mini-fab color="warn" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-buttons my-2">
|
||||
<button mat-button color="danger" class="me-2">Texte</button>
|
||||
<button mat-stroked-button color="danger" class="me-2">Texte Stroked</button>
|
||||
<button mat-flat-button color="danger" class="me-2">Texte Flat</button>
|
||||
<button mat-raised-button color="danger" class="me-2">Texte Raised</button>
|
||||
<button mat-icon-button color="danger" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
<button mat-fab color="danger" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
<button mat-mini-fab color="danger" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-buttons my-2">
|
||||
<button mat-button color="info" class="me-2">Texte</button>
|
||||
<button mat-stroked-button color="info" class="me-2">Texte Stroked</button>
|
||||
<button mat-flat-button color="info" class="me-2">Texte Flat</button>
|
||||
<button mat-raised-button color="info" class="me-2">Texte Raised</button>
|
||||
<button mat-icon-button color="info" class="me-2">
|
||||
<mat-icon>info</mat-icon>
|
||||
</button>
|
||||
<button mat-fab color="info" class="me-2">
|
||||
<mat-icon>info</mat-icon>
|
||||
</button>
|
||||
<button mat-mini-fab color="info" class="me-2">
|
||||
<mat-icon>info</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-buttons my-2">
|
||||
<button mat-button color="success" class="me-2">Texte</button>
|
||||
<button mat-stroked-button color="success" class="me-2">Texte Stroked</button>
|
||||
<button mat-flat-button color="success" class="me-2">Texte Flat</button>
|
||||
<button mat-raised-button color="success" class="me-2">Texte Raised</button>
|
||||
<button mat-icon-button color="success" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
<button mat-fab color="success" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
<button mat-mini-fab color="success" class="me-2">
|
||||
<mat-icon>check_circle</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
{{ ((((inputs.jumps - this.currentRange.start) + 1) / ((this.currentRange.end - this.currentRange.start) + 1)) * 100) | number : '1.2-2' }}%
|
||||
de la tranche actuelle ({{this.currentRange.start}} à {{this.currentRange.end}})
|
||||
</div>
|
||||
<mat-progress-bar mode="determinate"
|
||||
[value]="((((inputs.jumps - this.currentRange.start) + 1) / ((this.currentRange.end - this.currentRange.start) + 1)) * 100)"
|
||||
color="warn"></mat-progress-bar>
|
||||
<table class="table table-striped table-dark table-hover mat-elevation-z2 mb-1">
|
||||
<thead>
|
||||
<tr class="warn">
|
||||
<th class="first"></th>
|
||||
@for (header of sizesHeader; track header; let index = $index) {
|
||||
<th class="pl-3 pr-3 {{ tableHeader[(index+1)].active }}">{{ header.name }}</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="warn">
|
||||
<th class="first">Taille de voile en ft<sup>2</sup></th>
|
||||
@for (value of sizesValuesFeet; track value; let index = $index) {
|
||||
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td>
|
||||
}
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Abbattement 11%</th>
|
||||
@for (value of sizesMinValuesFeet; track value; let index = $index) {
|
||||
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td>
|
||||
}
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Charge alaire</th>
|
||||
@for (charge of sizesCharges; track charge; let index = $index) {
|
||||
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">
|
||||
{{ charge | number : '1.2-3' }}</td>
|
||||
}
|
||||
</tr>
|
||||
<tr class="warn">
|
||||
<th class="first">Taille de voile en m<sup>2</sup></th>
|
||||
@for (value of sizesValuesMeter; track value; let index = $index) {
|
||||
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">
|
||||
{{ value | number : '1.2-2' }}</td>
|
||||
}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<h4 class="py-2 mb-0">Tableau des tailles de voile par poids</h4>
|
||||
<div class="text-end">
|
||||
{{ ((inputs.jumps / 1600) * 100) | number : '1.2-2' }}% de la plage complète (0 à 1600)
|
||||
</div>
|
||||
<mat-progress-bar mode="determinate" [value]="((inputs.jumps / 1600) * 100)" color="warn">
|
||||
</mat-progress-bar>
|
||||
<table mat-table [dataSource]="canopy_size_table" matSort
|
||||
class="table table-striped table-dark table-hover mat-elevation-z2">
|
||||
<!-- weight column -->
|
||||
<ng-container matColumnDef="weight">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header class="first {{tableHeader[0].active}}">
|
||||
{{tableHeader[0].name}} </th>
|
||||
<th mat-cell *matCellDef="let element;" class="text-end ps-3 pe-4 fs-6" role="button"
|
||||
(click)="setCurrentWeight(element.weight)">
|
||||
<span class="text-warn d-block">{{element.weight}}</span>
|
||||
</th>
|
||||
</ng-container>
|
||||
<!-- range_1 column -->
|
||||
<ng-container matColumnDef="range_1">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[1].active}}">
|
||||
{{tableHeader[1].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;"
|
||||
class="pl-3 text-nowrap fs-6 {{element.ranges[0].active}}">{{element.ranges[0].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_2 column -->
|
||||
<ng-container matColumnDef="range_2">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[2].active}}">
|
||||
{{tableHeader[2].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;"
|
||||
class="pl-3 text-nowrap fs-6 {{element.ranges[1].active}}">{{element.ranges[1].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_3 column -->
|
||||
<ng-container matColumnDef="range_3">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[3].active}}">
|
||||
{{tableHeader[3].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;"
|
||||
class="pl-3 text-nowrap fs-6 {{element.ranges[2].active}}">{{element.ranges[2].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_4 column -->
|
||||
<ng-container matColumnDef="range_4">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[4].active}}">
|
||||
{{tableHeader[4].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;"
|
||||
class="pl-3 text-nowrap fs-6 {{element.ranges[3].active}}">{{element.ranges[3].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_5 column -->
|
||||
<ng-container matColumnDef="range_5">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[5].active}}">
|
||||
{{tableHeader[5].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;"
|
||||
class="pl-3 text-nowrap fs-6 {{element.ranges[4].active}}">{{element.ranges[4].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_6 column -->
|
||||
<ng-container matColumnDef="range_6">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[6].active}}">
|
||||
{{tableHeader[6].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;"
|
||||
class="pl-3 text-nowrap fs-6 {{element.ranges[5].active}}">{{element.ranges[5].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_7 column -->
|
||||
<ng-container matColumnDef="range_7">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[7].active}}">
|
||||
{{tableHeader[7].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;"
|
||||
class="pl-3 text-nowrap fs-6 {{element.ranges[6].active}}">{{element.ranges[6].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_8 column -->
|
||||
<ng-container matColumnDef="range_8">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[8].active}}">
|
||||
{{tableHeader[8].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;"
|
||||
class="pl-3 text-nowrap fs-6 {{element.ranges[7].active}}">{{element.ranges[7].value}}</td>
|
||||
</ng-container>
|
||||
<!-- range_9 column -->
|
||||
<ng-container matColumnDef="range_9">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef class="{{tableHeader[9].active}}">
|
||||
{{tableHeader[9].name}} </th>
|
||||
<td mat-cell *matCellDef="let element;"
|
||||
class="pl-3 text-nowrap fs-6 {{element.ranges[8].active}}">{{element.ranges[8].value}}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: false" class="warn"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"
|
||||
class="text-middle font-monospace warn {{row.active}}" id="weight_{{row.weight}}"></tr>
|
||||
</table>
|
||||
<div class="clearfix">
|
||||
<mat-paginator [pageSize]="50" [pageSizeOptions]="[5, 10, 15, 25, 50]" showFirstLastButtons
|
||||
class="pull-right mb-1 mt-2"></mat-paginator>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<section>
|
||||
<div class="demo-label">Basic</div>
|
||||
<div class="demo-button-row">
|
||||
<button mat-button>Basic</button>
|
||||
<button mat-button color="primary">Primary</button>
|
||||
<button mat-button color="accent">Accent</button>
|
||||
<button mat-button color="warn">Warn</button>
|
||||
<button mat-button color="danger">Danger</button>
|
||||
<button mat-button color="info">Info</button>
|
||||
<button mat-button color="success">Success</button>
|
||||
<button mat-button disabled>Disabled</button>
|
||||
<a mat-button href="https://www.google.com/" target="_blank">Link</a>
|
||||
</div>
|
||||
</section>
|
||||
<mat-divider></mat-divider>
|
||||
<section>
|
||||
<div class="demo-label">Raised</div>
|
||||
<div class="demo-button-row">
|
||||
<button mat-raised-button>Basic</button>
|
||||
<button mat-raised-button color="primary">Primary</button>
|
||||
<button mat-raised-button color="accent">Accent</button>
|
||||
<button mat-raised-button color="warn">Warn</button>
|
||||
<button mat-raised-button color="danger">Danger</button>
|
||||
<button mat-raised-button color="info">Info</button>
|
||||
<button mat-raised-button color="success">Success</button>
|
||||
<button mat-raised-button disabled>Disabled</button>
|
||||
<a mat-raised-button href="https://www.google.com/" target="_blank">Link</a>
|
||||
</div>
|
||||
</section>
|
||||
<mat-divider></mat-divider>
|
||||
<section>
|
||||
<div class="demo-label">Stroked</div>
|
||||
<div class="demo-button-row">
|
||||
<button mat-stroked-button>Basic</button>
|
||||
<button mat-stroked-button color="primary">Primary</button>
|
||||
<button mat-stroked-button color="accent">Accent</button>
|
||||
<button mat-stroked-button color="warn">Warn</button>
|
||||
<button mat-stroked-button color="danger">Danger</button>
|
||||
<button mat-stroked-button color="info">Info</button>
|
||||
<button mat-stroked-button color="success">Success</button>
|
||||
<button mat-stroked-button disabled>Disabled</button>
|
||||
<a mat-stroked-button href="https://www.google.com/" target="_blank">Link</a>
|
||||
</div>
|
||||
</section>
|
||||
<mat-divider></mat-divider>
|
||||
<section>
|
||||
<div class="demo-label">Flat</div>
|
||||
<div class="demo-button-row">
|
||||
<button mat-flat-button>Basic</button>
|
||||
<button mat-flat-button color="primary">Primary</button>
|
||||
<button mat-flat-button color="accent">Accent</button>
|
||||
<button mat-flat-button color="warn">Warn</button>
|
||||
<button mat-flat-button color="danger">Danger</button>
|
||||
<button mat-flat-button color="info">Info</button>
|
||||
<button mat-flat-button color="success">Success</button>
|
||||
<button mat-flat-button disabled>Disabled</button>
|
||||
<a mat-flat-button href="https://www.google.com/" target="_blank">Link</a>
|
||||
</div>
|
||||
</section>
|
||||
<mat-divider></mat-divider>
|
||||
<section>
|
||||
<div class="demo-label">Icon</div>
|
||||
<div class="demo-button-row">
|
||||
<div class="demo-flex-container">
|
||||
<button mat-icon-button aria-label="Example icon button with a vertical three dot icon">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="primary" aria-label="Example icon button with a home icon">
|
||||
<mat-icon>home</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="accent" aria-label="Example icon button with a menu icon">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" aria-label="Example icon button with a heart icon">
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="danger" aria-label="Example icon button with a heart icon">
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="info" aria-label="Example icon button with a heart icon">
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="success" aria-label="Example icon button with a heart icon">
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button disabled aria-label="Example icon button with a open in new tab icon">
|
||||
<mat-icon>open_in_new</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<mat-divider></mat-divider>
|
||||
<section>
|
||||
<div class="demo-label">FAB</div>
|
||||
<div class="demo-button-row">
|
||||
<div class="demo-flex-container">
|
||||
<div class="demo-button-container">
|
||||
<button mat-fab color="primary" aria-label="Example icon button with a delete icon">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-button-container">
|
||||
<button mat-fab color="accent" aria-label="Example icon button with a bookmark icon">
|
||||
<mat-icon>bookmark</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-button-container">
|
||||
<button mat-fab color="warn" aria-label="Example icon button with a home icon">
|
||||
<mat-icon>home</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-button-container">
|
||||
<button mat-fab color="danger" aria-label="Example icon button with a heart icon">
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-button-container">
|
||||
<button mat-fab color="info" aria-label="Example icon button with a heart icon">
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-button-container">
|
||||
<button mat-fab color="success" aria-label="Example icon button with a heart icon">
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-button-container">
|
||||
<button mat-fab disabled aria-label="Example icon button with a heart icon">
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<mat-divider></mat-divider>
|
||||
<section>
|
||||
<div class="demo-label">Mini FAB</div>
|
||||
<div class="demo-button-row">
|
||||
<div class="demo-flex-container">
|
||||
<div class="demo-button-container">
|
||||
<button mat-mini-fab color="primary" aria-label="Example icon button with a menu icon">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-button-container">
|
||||
<button mat-mini-fab color="accent" aria-label="Example icon button with a plus one icon">
|
||||
<mat-icon>plus_one</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-button-container">
|
||||
<button mat-mini-fab color="warn" aria-label="Example icon button with a filter list icon">
|
||||
<mat-icon>filter_list</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-button-container">
|
||||
<button mat-mini-fab color="danger" aria-label="Example icon button with a heart icon">
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-button-container">
|
||||
<button mat-mini-fab color="info" aria-label="Example icon button with a heart icon">
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-button-container">
|
||||
<button mat-mini-fab color="success" aria-label="Example icon button with a heart icon">
|
||||
<mat-icon>favorite</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="demo-button-container">
|
||||
<button mat-mini-fab disabled aria-label="Example icon button with a home icon">
|
||||
<mat-icon>home</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
-->
|
||||
@@ -8,8 +8,8 @@ describe('CalculatorComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [CalculatorComponent]
|
||||
});
|
||||
imports: [CalculatorComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(CalculatorComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Component, Input, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { RouterLink, RouterModule } from '@angular/router';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
@@ -10,27 +11,41 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatSort, MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||
import { NgChartsModule } from 'ng2-charts';
|
||||
import { BaseChartDirective } from 'ng2-charts';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { MenuItems } from 'src/app/components/shared';
|
||||
import { CalculatorInfo, CalculatorResult, CalculatorState, InputParams, Jump, LineConfig, Range, TableHeader, User, WeightSize, weightSizes } from 'src/app/core/models';
|
||||
import { CalculatorInfo, CalculatorResult, CalculatorState, InputParams, Jump, LineConfig, WeightSizeRange, TableHeader, User, WeightSize, weightSizes } from 'src/app/core/models';
|
||||
import { CalculatorService, JumpsService, UserService, UtilitiesService } from 'src/app/core/services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-calculator',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule, FormsModule, RouterModule,
|
||||
MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatFormFieldModule, MatInputModule, MatIconModule, MatMenuModule,
|
||||
MatPaginatorModule, MatSortModule, MatTableModule,
|
||||
NgChartsModule
|
||||
MatPaginatorModule, MatProgressBarModule, MatSortModule, MatTableModule,
|
||||
BaseChartDirective
|
||||
],
|
||||
selector: 'huapp-calculator',
|
||||
styleUrls: ['./calculator.component.scss'],
|
||||
templateUrl: './calculator.component.html'
|
||||
templateUrl: './calculator.component.html',
|
||||
styleUrl: './calculator.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateY(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateY(-100%)' }),
|
||||
animate(200)
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateY(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
})
|
||||
export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
//private _lastjump!: Subscription; // = new Subscription();
|
||||
@@ -39,6 +54,7 @@ export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
public title = 'Calcul de taille de voile';
|
||||
public subtitle = 'DT48 - 08 février 2024';
|
||||
public lastjump: Jump = {} as Jump;
|
||||
public currentRange: WeightSizeRange = {} as WeightSizeRange;
|
||||
public currentUser: User = {} as User;
|
||||
public displayedColumns: Array<string> = ['weight'];
|
||||
public tableHeader: Array<TableHeader> = [<TableHeader>{name: 'Poids nu en kg', active: ''}];
|
||||
@@ -81,8 +97,8 @@ export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
const currentUser$: Observable<User> = this._userService.currentUser;
|
||||
const lastjump$: Observable<Jump> = this._jumpsService.getLastJump();
|
||||
|
||||
weightSizes[0].ranges.forEach((range: Range) => {
|
||||
let header: TableHeader = {name: range.label, active: range.active};
|
||||
weightSizes[0].ranges.forEach((range: WeightSizeRange) => {
|
||||
const header: TableHeader = {name: range.label, active: range.active};
|
||||
this.tableHeader.push(header)
|
||||
this.sizesHeader.push(header);
|
||||
this.displayedColumns.push(range.name);
|
||||
@@ -139,6 +155,10 @@ export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private _refreshActive(): void {
|
||||
let weight: number = this.inputs.weight;
|
||||
if (this.inputs.weight < 60) {
|
||||
weight = 60;
|
||||
}
|
||||
weightSizes.forEach(element => {
|
||||
element.active = '';
|
||||
element.ranges.forEach(range => {
|
||||
@@ -148,29 +168,34 @@ export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
this.tableHeader.forEach(element => {
|
||||
element.active = '';
|
||||
});
|
||||
if (this.inputs.weight >= 60 && this.inputs.weight <= 110) {
|
||||
weightSizes[(this.inputs.weight - 60)].active = 'active';
|
||||
if (weight >= 60 && weight <= 110) {
|
||||
weightSizes[(weight - 60)].active = 'active';
|
||||
}
|
||||
if (this.inputs.jumps >= 0) {
|
||||
var num = this._calculatorService.getRangeNum(this.inputs.jumps);
|
||||
const num = this._calculatorService.getRangeNum(this.inputs.jumps);
|
||||
this.tableHeader[num].active = 'active';
|
||||
weightSizes.forEach(element => {
|
||||
element.ranges[(num-1)].active = 'active';
|
||||
});
|
||||
this.currentRange = weightSizes[0].ranges[(num-1)];
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshSizes(): void {
|
||||
let weight: number = this.inputs.weight;
|
||||
if (this.inputs.weight < 60) {
|
||||
weight = 60;
|
||||
}
|
||||
if (this.inputs.jumps > 2000) {
|
||||
this.info.state.error = '2K+ Jumps';
|
||||
}
|
||||
this.sizesValuesFeet = this._calculatorService.getCanopySizes(this.inputs.weight);
|
||||
this.sizesMinValuesFeet = this._calculatorService.getCanopySizes(this.inputs.weight, true);
|
||||
this.sizesValuesFeet = this._calculatorService.getCanopySizes(weight);
|
||||
this.sizesMinValuesFeet = this._calculatorService.getCanopySizes(weight, true);
|
||||
this.sizesValuesMeter = this.sizesValuesFeet.map((value: number, index: number) => {
|
||||
this.sizesCharges[index] = this._calculatorService.getCharge(value, this.inputs.weight, this.inputs.gear);
|
||||
return this._calculatorService.convertFeet2Meters(value);
|
||||
});
|
||||
this.info.sizesFeet = this._calculatorService.canopySizeCalc(this.inputs.weight, this.inputs.jumps);
|
||||
this.info.sizesFeet = this._calculatorService.canopySizeCalc(weight, this.inputs.jumps);
|
||||
this.info.sizesFeet.current = this.inputs.current;
|
||||
this.info.sizesMeter = {
|
||||
current: this._calculatorService.convertFeet2Meters(this.inputs.current),
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AuthResolver } from 'src/app/core/services';
|
||||
import { CalculatorComponent } from './calculator.component';
|
||||
import { CalculatorRoutingModule } from './calculator-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CalculatorRoutingModule,
|
||||
CalculatorComponent
|
||||
],
|
||||
exports: [],
|
||||
providers: [AuthResolver]
|
||||
})
|
||||
export class CalculatorModule { }
|
||||
@@ -0,0 +1,58 @@
|
||||
<div @flyInOut>
|
||||
<div class="content">
|
||||
<div class="d-flex">
|
||||
<div class="flex-fill">
|
||||
<h1 class="mb-1">{{title}}</h1>
|
||||
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
|
||||
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="flex-spacer"></span>
|
||||
<div class="align-self-center me-2">
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuCanopyModel" aria-label="Menu Canopy Model">
|
||||
<mat-icon fontIcon="more_vert"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuCanopyModel="matMenu">
|
||||
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
|
||||
<button mat-menu-item>
|
||||
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ subtitle }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-4">
|
||||
@if (displayCharts) {
|
||||
<div class="row">
|
||||
<div class="col-xl-3 col-lg-6 col-md-6 col-sm-12">
|
||||
<app-pie-chart [headers]="seriesModelHeader" [names]="seriesModelName" [values]="seriesModelValue" [colors]="seriesColor"></app-pie-chart>
|
||||
</div>
|
||||
<div class="col-xl-3 col-lg-6 col-md-6 col-sm-12">
|
||||
<app-pie-chart [headers]="seriesSizeHeader" [names]="seriesSizeName" [values]="seriesSizeValue" [colors]="seriesColor"></app-pie-chart>
|
||||
</div>
|
||||
<div class="col-xl-6 col-lg-12 col-md-12">
|
||||
<!--<app-pie-chart [headers]="seriesSizeHeader" [names]="seriesSizeName" [values]="seriesSizeValue" [colors]="seriesColor"></app-pie-chart>-->
|
||||
<app-bars-chart [headers]="seriesSizeHeader" [names]="seriesSizeName" [values]="seriesSizeRow" [colors]="seriesColor" [legend]="false"></app-bars-chart>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-xl-6 col-lg-12">
|
||||
<mat-divider class="mt-3 mb-0"></mat-divider>
|
||||
<app-history-table [headers]="seriesModelHeader" [names]="seriesModelName" [values]="seriesModelValue" [rows]="seriesModelRow" [colors]="seriesColorClass"></app-history-table>
|
||||
</div>
|
||||
<div class="col-xl-6 col-lg-12">
|
||||
<mat-divider class="mt-3 mb-0"></mat-divider>
|
||||
<app-history-table [headers]="seriesSizeHeader" [names]="seriesSizeName" [values]="seriesSizeValue" [rows]="seriesSizeRow" [colors]="seriesColorClass"></app-history-table>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CanopyComponent } from './canopy.component';
|
||||
|
||||
describe('CanopyComponent', () => {
|
||||
let component: CanopyComponent;
|
||||
let fixture: ComponentFixture<CanopyComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CanopyComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CanopyComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,142 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatGridListModule } from '@angular/material/grid-list';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { MenuItems } from 'src/app/components/shared';
|
||||
import { HistoryTableComponent } from 'src/app/components/shared/helpers-jump';
|
||||
import { BarsChartComponent, PieChartComponent } from 'src/app/components/shared/helpers-chart';
|
||||
import { Jump, CanopiesPageData } from 'src/app/core/models';
|
||||
import { UtilitiesService } from 'src/app/core/services';
|
||||
import { CanopyBySize, CanopyByYear, CanopyModelBySize, CanopyModelByYear } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-canopy',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe,
|
||||
MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule,
|
||||
MatGridListModule, MatIconModule, MatMenuModule,
|
||||
HistoryTableComponent, BarsChartComponent, PieChartComponent
|
||||
],
|
||||
templateUrl: './canopy.component.html',
|
||||
styleUrl: './canopy.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateX(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateX(-100%)' }),
|
||||
animate(200)
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateX(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
})
|
||||
export class CanopyComponent implements OnInit, OnDestroy {
|
||||
private _data: Subscription = new Subscription();
|
||||
private _canopiesModelBySize!: Array<CanopyModelBySize>;
|
||||
private _canopiesModelByYear!: Array<CanopyModelByYear>;
|
||||
private _canopiesSizeBySize!: Array<CanopyBySize>;
|
||||
private _canopiesSizeByYear!: Array<CanopyByYear>;
|
||||
public lastJump: Jump = {} as Jump;
|
||||
public title = 'Voiles';
|
||||
public subtitle: string = 'Nombre total de sauts par modèle et taille';
|
||||
public displayCharts = false;
|
||||
public destroyRef = inject(DestroyRef);
|
||||
public seriesModelHeader: string[] = [];
|
||||
public seriesModelName: string[] = [];
|
||||
public seriesModelValue: number[] = [];
|
||||
public seriesModelRow: Array<Array<number>> = [];
|
||||
public seriesSizeHeader: string[] = [];
|
||||
public seriesSizeName: string[] = [];
|
||||
public seriesSizeValue: number[] = [];
|
||||
public seriesSizeRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
const data$: Observable<{ canopiesPageData: CanopiesPageData }> = this.route.data as Observable<{ canopiesPageData: CanopiesPageData }>;
|
||||
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { canopiesPageData: CanopiesPageData }) => {
|
||||
const pageData: CanopiesPageData = data.canopiesPageData;
|
||||
this.lastJump = pageData.lastjump;
|
||||
this._loadCanopyBySize(pageData);
|
||||
this._loadCanopyModelBySize(pageData);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._data.unsubscribe();
|
||||
}
|
||||
|
||||
private _loadCanopyBySize(pageData: CanopiesPageData): void {
|
||||
this._canopiesSizeBySize = pageData.canopiesBySize.map((row: CanopyBySize) => {
|
||||
this.seriesSizeName.push(row.taille.toString());
|
||||
this.seriesSizeValue.push(row.count);
|
||||
return row;
|
||||
});
|
||||
this._loadCanopyByYear(pageData);
|
||||
}
|
||||
|
||||
private _loadCanopyByYear(pageData: CanopiesPageData): void {
|
||||
this.seriesSizeHeader = ['2018', '2019', '2020', '2021', '2022', '2023', '2024'];
|
||||
const values: Array<number> = this.seriesSizeHeader.map(() => 0);
|
||||
this.seriesSizeName.forEach(() => {
|
||||
this.seriesSizeRow.push([...values]);
|
||||
});
|
||||
this._canopiesSizeByYear = pageData.canopiesBySizeByYear.map((row: CanopyByYear) => {
|
||||
const taille: string = row.taille.toString();
|
||||
const year: string = row.year.toString();
|
||||
this.seriesSizeRow[this.seriesSizeName.indexOf(taille)][this.seriesSizeHeader.indexOf(year)] = row.count;
|
||||
return row;
|
||||
});
|
||||
}
|
||||
|
||||
private _loadCanopyModelBySize(pageData: CanopiesPageData): void {
|
||||
this._canopiesModelBySize = pageData.canopiesBySizeByModel.map((row: CanopyModelBySize) => {
|
||||
this.seriesModelName.push(`${row.taille.toString()} - ${row.voile}`);
|
||||
this.seriesModelValue.push(row.count);
|
||||
return row;
|
||||
});
|
||||
this._loadCanopyModelByYear(pageData);
|
||||
}
|
||||
|
||||
private _loadCanopyModelByYear(pageData: CanopiesPageData): void {
|
||||
this.seriesModelHeader = ['2018', '2019', '2020', '2021', '2022', '2023', '2024'];
|
||||
const values: Array<number> = this.seriesModelHeader.map(() => 0);
|
||||
this.seriesModelName.forEach(() => {
|
||||
this.seriesModelRow.push([...values]);
|
||||
});
|
||||
this._canopiesModelByYear = pageData.canopiesBySizeByModelByYear.map((row: CanopyModelByYear) => {
|
||||
const voile: string = row.voile;
|
||||
const taille: string = row.taille.toString();
|
||||
const year: string = row.year.toString();
|
||||
this.seriesModelRow[this.seriesModelName.indexOf(`${taille} - ${voile}`)][this.seriesModelHeader.indexOf(year)] = row.count;
|
||||
return row;
|
||||
});
|
||||
this.displayCharts = true;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
import { AuthResolver } from 'src/app/core/services';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: DashboardComponent,
|
||||
resolve: {
|
||||
isAuthenticated: AuthResolver
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class DashboardRoutingModule { }
|
||||
@@ -1,32 +1,37 @@
|
||||
<div class="content">
|
||||
<h1>{{title}}</h1>
|
||||
<hr />
|
||||
<h1 class="mb-1">{{title}}</h1>
|
||||
<span [hidden]="!lastJump.numero" class="me-2 fst-italic0 fs-6">
|
||||
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
|
||||
</span>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<ng-container *appShowAuthed="true">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-sm-12">
|
||||
<huapp-jumps-by-month></huapp-jumps-by-month>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<huapp-drop-zones></huapp-drop-zones>
|
||||
<div class="col-xs-12">
|
||||
<app-jumps-by-month></app-jumps-by-month>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-sm-2 mt-lg-3">
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12">
|
||||
<huapp-canopy-sizes></huapp-canopy-sizes>
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<app-drop-zones></app-drop-zones>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12">
|
||||
<huapp-canopy-models></huapp-canopy-models>
|
||||
<br />
|
||||
<huapp-aeronefs></huapp-aeronefs>
|
||||
<div class="col-md-8 col-sm-12">
|
||||
<app-drop-zones-bar></app-drop-zones-bar>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-4 col-sm-6 col-xs-12">
|
||||
<huapp-aeronefs-bar></huapp-aeronefs-bar>
|
||||
<br />
|
||||
<huapp-drop-zones-bar></huapp-drop-zones-bar>
|
||||
</div>
|
||||
<div class="row mt-sm-2 mt-lg-3">
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<app-aeronefs></app-aeronefs>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12">
|
||||
<div class="col-md-8 col-sm-12">
|
||||
<app-aeronefs-bar></app-aeronefs-bar>
|
||||
</div>
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 col-xs-12">
|
||||
</div>
|
||||
<div class="row mt-sm-2 mt-lg-3">
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<app-canopy-models></app-canopy-models>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<app-canopy-sizes></app-canopy-sizes>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -8,8 +8,8 @@ describe('DashboardComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DashboardComponent]
|
||||
});
|
||||
imports: [DashboardComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(DashboardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { ActivatedRoute, Data, Router } from '@angular/router';
|
||||
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { User, Errors, Aeronef, AeronefByImat, Canopy, CanopyModelBySize, DropZone, DropZoneByYear } from 'src/app/core/models';
|
||||
import { Aeronef, AeronefByImat, Canopy, CanopyModelBySize, DropZone, DropZoneByYear, Errors, Jump, User } from 'src/app/core/models';
|
||||
//import { AeronefsService, CanopiesService, DropZonesService, UserService } from 'src/app/core/services';
|
||||
import { UserService } from 'src/app/core/services';
|
||||
import { JumpsService, UserService } from 'src/app/core/services';
|
||||
import { ShowAuthedDirective } from 'src/app/components/shared/show-authed.directive';
|
||||
import {
|
||||
AeronefsComponent, AeronefsBarComponent,
|
||||
@@ -19,19 +21,21 @@ import {
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
ShowAuthedDirective,
|
||||
DatePipe, ShowAuthedDirective, MatDividerModule,
|
||||
AeronefsComponent, AeronefsBarComponent,
|
||||
CanopyModelsComponent, CanopySizesComponent,
|
||||
DropZonesComponent, DropZonesBarComponent,
|
||||
JumpsByMonthComponent
|
||||
],
|
||||
selector: 'huapp-dashboard',
|
||||
selector: 'app-dashboard',
|
||||
templateUrl: './dashboard.component.html',
|
||||
styleUrls: ['./dashboard.component.scss']
|
||||
styleUrl: './dashboard.component.scss'
|
||||
})
|
||||
export class DashboardComponent implements OnInit, OnDestroy {
|
||||
private _currentUser: Subscription = new Subscription();
|
||||
private _data: Subscription = new Subscription();
|
||||
private _lastjump: Subscription = new Subscription();
|
||||
private _lastjump$: Observable<Jump> = new Observable();
|
||||
/*
|
||||
private _aeronefs: Subscription = new Subscription();
|
||||
private _canopies: Subscription = new Subscription();
|
||||
@@ -50,12 +54,14 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
public dropzoneAggregate!: Array<DropZoneByYear>;
|
||||
public dropzones!: Array<DropZone>;
|
||||
public dropzonesCount = 0;
|
||||
public lastJump: Jump = {} as Jump;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private titleService: Title,
|
||||
private userService: UserService
|
||||
private _userService: UserService,
|
||||
private _jumpsService: JumpsService
|
||||
/*
|
||||
private aeronefsService: AeronefsService,
|
||||
private canopiesService: CanopiesService,
|
||||
@@ -74,10 +80,15 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
this.router.navigateByUrl('/login');
|
||||
return;
|
||||
}
|
||||
const currentUser$: Observable<User> = this.userService.currentUser;
|
||||
const currentUser$: Observable<User> = this._userService.currentUser;
|
||||
this._currentUser = currentUser$.subscribe((userData: User) => {
|
||||
this.canModify = userData.role === 'Admin';
|
||||
});
|
||||
|
||||
this._lastjump$ = this._jumpsService.getLastJump();
|
||||
this._lastjump = this._lastjump$.subscribe((jump) => {
|
||||
this.lastJump = jump;
|
||||
});
|
||||
});
|
||||
/*
|
||||
this._loadAeronefs();
|
||||
@@ -90,6 +101,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
ngOnDestroy() {
|
||||
this._currentUser.unsubscribe();
|
||||
this._data.unsubscribe();
|
||||
this._lastjump.unsubscribe();
|
||||
/*
|
||||
this._aeronefs.unsubscribe();
|
||||
this._canopies.unsubscribe();
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AuthResolver } from 'src/app/core/services';
|
||||
import { DashboardComponent } from './dashboard.component';
|
||||
import { DashboardRoutingModule } from './dashboard-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
DashboardRoutingModule,
|
||||
DashboardComponent
|
||||
],
|
||||
exports: [],
|
||||
providers: [AuthResolver]
|
||||
})
|
||||
export class DashboardModule { }
|
||||
@@ -0,0 +1,57 @@
|
||||
<div @flyInOut>
|
||||
<div class="content">
|
||||
<div class="d-flex">
|
||||
<div class="flex-fill">
|
||||
<h1 class="mb-1">{{title}}</h1>
|
||||
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
|
||||
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="flex-spacer"></span>
|
||||
<div class="align-self-center me-2">
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuDropzone" aria-label="Menu Dropzone">
|
||||
<mat-icon fontIcon="more_vert"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuDropzone="matMenu">
|
||||
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
|
||||
<button mat-menu-item>
|
||||
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ subtitle }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-4">
|
||||
@if (displayCharts) {
|
||||
<div class="row">
|
||||
<div class="col-xl-4 col-lg-5 col-md-6 col-sm-12">
|
||||
<app-pie-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [colors]="seriesColor"></app-pie-chart>
|
||||
</div>
|
||||
<div class="col-xl-8 col-lg-7 col-md-6 col-sm-12">
|
||||
<app-bars-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor" [legend]="false"></app-bars-chart>
|
||||
<!--
|
||||
@if (displayCharts) {
|
||||
<div class="barchart position-relative w-100 my-1">
|
||||
<x-chartist [configuration]="chartBarConfig"></x-chartist>
|
||||
</div>
|
||||
}
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="d-flex">
|
||||
<div class="flex-fill py-2">
|
||||
<mat-divider class="mt-3 mb-0"></mat-divider>
|
||||
<app-history-table [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [rows]="seriesRow" [colors]="seriesColorClass"></app-history-table>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DropzoneComponent } from './dropzone.component';
|
||||
|
||||
describe('DropzoneComponent', () => {
|
||||
let component: DropzoneComponent;
|
||||
let fixture: ComponentFixture<DropzoneComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DropzoneComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DropzoneComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,163 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatGridListModule } from '@angular/material/grid-list';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
//import { ChartistModule, Configuration } from 'ng-chartist';
|
||||
//import { AxisOptions, Label } from 'chartist';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { MenuItems } from 'src/app/components/shared';
|
||||
import { BarsChartComponent, PieChartComponent } from 'src/app/components/shared/helpers-chart';
|
||||
import { HistoryTableComponent } from 'src/app/components/shared/helpers-jump';
|
||||
import { DropZoneByOaci, DropZoneByYear, DropZonesPageData, Jump } from 'src/app/core/models';
|
||||
import { UtilitiesService } from 'src/app/core/services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dropzone',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe,
|
||||
MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule,
|
||||
MatGridListModule, MatIconModule, MatMenuModule,
|
||||
//ChartistModule,
|
||||
HistoryTableComponent, BarsChartComponent, PieChartComponent
|
||||
],
|
||||
templateUrl: './dropzone.component.html',
|
||||
styleUrl: './dropzone.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateX(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateX(-100%)' }),
|
||||
animate(200)
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateX(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
})
|
||||
export class DropzoneComponent implements OnInit, OnDestroy {
|
||||
private _data: Subscription = new Subscription();
|
||||
private _lastjump: Subscription = new Subscription();
|
||||
private _dropzonesByOaci!: Array<DropZoneByOaci>;
|
||||
private _dropzonesByYear!: Array<DropZoneByYear>;
|
||||
public lastJump: Jump = {} as Jump;
|
||||
public title = 'Dropzones';
|
||||
public subtitle: string = 'Nombre total de sauts par dropzone';
|
||||
public displayCharts = false;
|
||||
public destroyRef = inject(DestroyRef);
|
||||
public seriesHeader: string[] = [];
|
||||
public seriesName: string[] = [];
|
||||
public seriesValue: number[] = [];
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
//public chartBarConfig: Configuration = this._utilitiesService.getBarConfig();
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
const data$: Observable<{ dropZonesPageData: DropZonesPageData }> = this.route.data as Observable<{ dropZonesPageData: DropZonesPageData }>;
|
||||
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { dropZonesPageData: DropZonesPageData }) => {
|
||||
const pageData: DropZonesPageData = data.dropZonesPageData;
|
||||
this.lastJump = pageData.lastjump;
|
||||
this._loadDropZoneByOaci(pageData);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._data.unsubscribe();
|
||||
}
|
||||
|
||||
private _loadDropZoneByOaci(pageData: DropZonesPageData): void {
|
||||
this._dropzonesByOaci = pageData.dropZonesByOaci.map((row: DropZoneByOaci) => {
|
||||
this.seriesName.push(`${row.oaci} - ${row.lieu}`);
|
||||
this.seriesValue.push(row.count);
|
||||
return row;
|
||||
});
|
||||
this._loadDropZoneByYear(pageData);
|
||||
}
|
||||
|
||||
private _loadDropZoneByYear(pageData: DropZonesPageData): void {
|
||||
this.seriesHeader = ['2018', '2019', '2020', '2021', '2022', '2023', '2024'];
|
||||
const values: Array<number> = this.seriesHeader.map(() => 0);
|
||||
this.seriesName.forEach(() => {
|
||||
this.seriesRow.push([...values]);
|
||||
});
|
||||
this._dropzonesByYear = pageData.dropZonesByOaciByYear.map((row: DropZoneByYear) => {
|
||||
const lieu: string = row.lieu;
|
||||
const oaci: string = row.oaci;
|
||||
const year: string = row.year.toString();
|
||||
this.seriesRow[this.seriesName.indexOf(`${oaci} - ${lieu}`)][this.seriesHeader.indexOf(year)] = row.count;
|
||||
return row;
|
||||
});
|
||||
/*
|
||||
const axisX: AxisOptions = {
|
||||
labelInterpolationFnc: function(value: Label, index: number): string {
|
||||
return index % 1 === 0 ? `${value.toString()}` : '';
|
||||
}
|
||||
}
|
||||
this.chartBarConfig = {
|
||||
type: 'Bar',
|
||||
data: {
|
||||
'labels': this.seriesHeader,
|
||||
'series': this.seriesRow
|
||||
},
|
||||
options: {
|
||||
seriesBarDistance: 15,
|
||||
horizontalBars: false,
|
||||
high: 180,
|
||||
axisX: {
|
||||
showGrid: false,
|
||||
offset: 20
|
||||
},
|
||||
axisY: {
|
||||
showGrid: true,
|
||||
offset: 40
|
||||
},
|
||||
height: 300
|
||||
},
|
||||
responsiveOptions: [
|
||||
['screen and (min-width: 1024px)', {
|
||||
axisX: axisX
|
||||
}],
|
||||
['screen and (min-width: 641px) and (max-width: 1024px)', {
|
||||
seriesBarDistance: 7,
|
||||
axisY: {
|
||||
offset: 30
|
||||
},
|
||||
axisX: axisX
|
||||
}],
|
||||
['screen and (max-width: 640px)', {
|
||||
seriesBarDistance: 5,
|
||||
axisY: {
|
||||
offset: 20
|
||||
},
|
||||
axisX: axisX
|
||||
}]
|
||||
]
|
||||
};
|
||||
*/
|
||||
this.displayCharts = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<div @flyInOut>
|
||||
<div class="content">
|
||||
<div class="d-flex">
|
||||
<div class="flex-fill">
|
||||
<h1 class="mb-1">{{title}}</h1>
|
||||
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
|
||||
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="flex-spacer"></span>
|
||||
<div class="align-self-center me-2">
|
||||
<p>Home works!</p>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<app-list-errors [errors]="errors"></app-list-errors>
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Card title</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-4">
|
||||
<p>Home works!</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HomeComponent } from './home.component';
|
||||
|
||||
describe('HomeComponent', () => {
|
||||
let component: HomeComponent;
|
||||
let fixture: ComponentFixture<HomeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [HomeComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HomeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, DestroyRef, inject, OnInit } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { ListErrorsComponent, } from 'src/app/components/shared';
|
||||
import { Errors, Jump } from 'src/app/core/models';
|
||||
import { JumpsService } from 'src/app/core/services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe,
|
||||
MatCardModule, MatDividerModule,
|
||||
ListErrorsComponent
|
||||
],
|
||||
templateUrl: './home.component.html',
|
||||
styleUrl: './home.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateY(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateY(-100%)' }),
|
||||
animate(200)
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateY(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
})
|
||||
export class HomeComponent implements OnInit {
|
||||
private _lastjump: Subscription = new Subscription();
|
||||
private _lastjump$: Observable<Jump> = new Observable();
|
||||
public title = 'Home';
|
||||
public errors: Errors = { errors: {} };
|
||||
public lastJump: Jump = {} as Jump;
|
||||
public destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private _jumpsService: JumpsService
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this._lastjump$ = this._jumpsService.getLastJump().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._lastjump = this._lastjump$.subscribe((jump) => {
|
||||
this.lastJump = jump;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
export * from './auth/auth.component';
|
||||
export * from './aeronef/aeronef.component';
|
||||
export * from './calculator/calculator.component';
|
||||
export * from './canopy/canopy.component';
|
||||
export * from './dashboard/dashboard.component';
|
||||
export * from './dropzone/dropzone.component';
|
||||
export * from './home/home.component';
|
||||
export * from './jump/jump.component';
|
||||
export * from './logbook/logbook.component';
|
||||
export * from './profile/profile.component';
|
||||
export * from './qcm/qcm.component';
|
||||
export * from './settings';
|
||||
export * from './shared';
|
||||
@@ -1,349 +0,0 @@
|
||||
<h2 mat-dialog-title class="bg-warn border-bottom border-light-subtle d-flex mb-3 pt-3 pb-2">
|
||||
Ajouter un saut<span class="flex-spacer"></span>
|
||||
<mat-icon fontIcon="close" class="pointer" (click)="closeDialog()"></mat-icon>
|
||||
</h2>
|
||||
<mat-dialog-content>
|
||||
<div class="container">
|
||||
<!--
|
||||
<div class="row">
|
||||
<div class="col-xs-12 border-bottom border-dark pb-2 mb-2">
|
||||
<mat-form-field appearance="outline" class="mt-2 me-2" color="warn">
|
||||
<mat-label>Numéro ou plage de saut à ajouter</mat-label>
|
||||
<input matInput [(ngModel)]="params.numeros" required />
|
||||
<mat-hint>645 / +5 / 650-660</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="mt-2 me-3" color="warn">
|
||||
<mat-label>Choisissez une date</mat-label>
|
||||
<input matInput [matDatepicker]="datepicker" [(ngModel)]="params.date" (dateChange)="onDateChange()" required />
|
||||
<mat-hint>MM/DD/YYYY</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="datepicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #datepicker>
|
||||
<mat-datepicker-actions>
|
||||
<button mat-button matDatepickerCancel>Annuler</button>
|
||||
<button mat-raised-button color="warn" matDatepickerApply>Appliquer</button>
|
||||
</mat-datepicker-actions>
|
||||
</mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-checkbox (change)="setLastJump($event.checked)" [checked]="useLast.all">
|
||||
<mat-icon fontIcon="history" class="align-middle me-2"></mat-icon> <span class="d-inline-block align-middle">Utiliser les valeurs du dernier saut</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
<form class="row" [formGroup]="jumpForm" (ngSubmit)="submitForm()">
|
||||
<div class="col-xs-12 border-bottom border-dark pb-2 mb-2">
|
||||
<mat-form-field appearance="outline" class="mt-2 me-2" color="warn">
|
||||
<mat-label>Numéro ou plage de saut à ajouter</mat-label>
|
||||
<input matInput formControlName="numero" required />
|
||||
<mat-hint>645 / +5 / 650-660</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="mt-2 me-3" color="warn">
|
||||
<mat-label>Choisissez une date</mat-label>
|
||||
<input matInput [matDatepicker]="datepicker" formControlName="date" (dateChange)="onDateChange()" required />
|
||||
<mat-hint>MM/DD/YYYY</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="datepicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #datepicker>
|
||||
<mat-datepicker-actions>
|
||||
<button mat-button matDatepickerCancel>Annuler</button>
|
||||
<button mat-raised-button color="warn" matDatepickerApply>Appliquer</button>
|
||||
</mat-datepicker-actions>
|
||||
</mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-checkbox (change)="setLastJump($event.checked)" [checked]="useLast.all">
|
||||
<mat-icon fontIcon="history" class="align-middle me-2"></mat-icon> <span class="d-inline-block align-middle">Utiliser les valeurs du dernier saut</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Dropzone
|
||||
<mat-checkbox class="float-end" (change)="setLastDropzone($event.checked)" [checked]="useLast.dropzone">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Lieu</mat-label>
|
||||
<input matInput formControlName="lieu">
|
||||
<mat-error *ngIf="this.jumpForm.controls['lieu'].status === 'INVALID'">{{getErrorMessage('lieu')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Oaci</mat-label>
|
||||
<input matInput formControlName="oaci">
|
||||
<mat-error *ngIf="this.jumpForm.controls['oaci'].status === 'INVALID'">{{getErrorMessage('oaci')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Aéronef
|
||||
<mat-checkbox class="float-end" (change)="setLastAeronef($event.checked)" [checked]="useLast.aeronef">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Aéronef</mat-label>
|
||||
<input matInput formControlName="aeronef">
|
||||
<mat-error *ngIf="this.jumpForm.controls['aeronef'].status === 'INVALID'">{{getErrorMessage('aeronef')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Imatriculation</mat-label>
|
||||
<input matInput formControlName="imat">
|
||||
<mat-error *ngIf="this.jumpForm.controls['imat'].status === 'INVALID'">{{getErrorMessage('imat')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Voile
|
||||
<mat-checkbox class="float-end" (change)="setLastVoile($event.checked)" [checked]="useLast.voile">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Modèle</mat-label>
|
||||
<input matInput formControlName="voile">
|
||||
<mat-error *ngIf="this.jumpForm.controls['voile'].status === 'INVALID'">{{getErrorMessage('voile')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Taille en ft<sup>2</sup></mat-label>
|
||||
<input matInput formControlName="taille" type="number" min="0" step="1">
|
||||
<span matTextSuffix>ft<sup>2</sup></span>
|
||||
<mat-error *ngIf="this.jumpForm.controls['taille'].status === 'INVALID'">{{getErrorMessage('taille')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Altitude
|
||||
<mat-checkbox class="float-end" (change)="setLastAltitude($event.checked)" [checked]="useLast.altitude">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Hauteur de largage</mat-label>
|
||||
<input matInput formControlName="hauteur" type="number" min="0" step="1">
|
||||
<mat-error *ngIf="this.jumpForm.controls['hauteur'].status === 'INVALID'">{{getErrorMessage('hauteur')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Hauteur de déploiement</mat-label>
|
||||
<input matInput formControlName="deploiement" type="number" min="0" step="1">
|
||||
<mat-error *ngIf="this.jumpForm.controls['deploiement'].status === 'INVALID'">{{getErrorMessage('deploiement')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Discipline
|
||||
<mat-checkbox class="float-end" (change)="setLastDiscipline($event.checked)" [checked]="useLast.discipline">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Categorie</mat-label>
|
||||
<input matInput formControlName="categorie">
|
||||
<mat-error *ngIf="this.jumpForm.controls['categorie'].status === 'INVALID'">{{getErrorMessage('categorie')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Module</mat-label>
|
||||
<input matInput formControlName="module">
|
||||
<mat-error *ngIf="this.jumpForm.controls['module'].status === 'INVALID'">{{getErrorMessage('module')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Divers
|
||||
<mat-checkbox class="float-end" (change)="setLastDivers($event.checked)" [checked]="useLast.divers">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Accessoires</mat-label>
|
||||
<input matInput formControlName="accessoires">
|
||||
<mat-error *ngIf="this.jumpForm.controls['accessoires'].status === 'INVALID'">{{getErrorMessage('accessoires')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Zone</mat-label>
|
||||
<input matInput formControlName="zone">
|
||||
<mat-error *ngIf="this.jumpForm.controls['zone'].status === 'INVALID'">{{getErrorMessage('zone')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Groupe
|
||||
<mat-checkbox class="float-end" (change)="setLastGroupe($event.checked)" [checked]="useLast.groupe">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Sautants</mat-label>
|
||||
<input matInput placeholder="Ajouter un sautant" [formControl]="tagField" (keyup.enter)="addSautant()">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Nombre de participants</mat-label>
|
||||
<input matInput formControlName="participants" type="number" min="0" step="1">
|
||||
<mat-error *ngIf="this.jumpForm.controls['participants'].status === 'INVALID'">{{getErrorMessage('participants')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<div class="mb-2">
|
||||
<span *ngFor="let sautant of jump.sautants" class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle pointer me-1" fontIcon="close" (click)="removeSautant(sautant)"></mat-icon>
|
||||
{{ sautant }}
|
||||
</span>
|
||||
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon>
|
||||
ju_solide
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Programme
|
||||
<mat-checkbox class="float-end" (change)="setLastProgramme($event.checked)" [checked]="useLast.programme">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2 w-100" color="warn">
|
||||
<mat-label>Programme</mat-label>
|
||||
<textarea matInput formControlName="programme"></textarea>
|
||||
<mat-error *ngIf="this.jumpForm.controls['programme'].status === 'INVALID'">{{getErrorMessage('programme')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">Vidéo</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Dossier</mat-label>
|
||||
<input matInput formControlName="dossier">
|
||||
<mat-error *ngIf="this.jumpForm.controls['dossier'].status === 'INVALID'">{{getErrorMessage('dossier')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Video</mat-label>
|
||||
<input matInput formControlName="video">
|
||||
<mat-error *ngIf="this.jumpForm.controls['video'].status === 'INVALID'">{{getErrorMessage('video')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<span class="d-inline-block align-middle ms-1">{{ filepath }}{{ filename }}</span>
|
||||
</div>
|
||||
</form>
|
||||
<div class="row" [hidden]="true">
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Dropzone
|
||||
<mat-checkbox class="float-end" (change)="setLastDropzone($event.checked)" [(ngModel)]="useLast.dropzone">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Lieu</mat-label>
|
||||
<input matInput [(ngModel)]="jump.lieu">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Oaci</mat-label>
|
||||
<input matInput [(ngModel)]="jump.oaci">
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Aéronef
|
||||
<mat-checkbox class="float-end" (change)="setLastAeronef($event.checked)" [(ngModel)]="useLast.aeronef">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Aéronef</mat-label>
|
||||
<input matInput [(ngModel)]="jump.aeronef">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Imatriculation</mat-label>
|
||||
<input matInput [(ngModel)]="jump.imat">
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Voile
|
||||
<mat-checkbox class="float-end" (change)="setLastVoile($event.checked)" [(ngModel)]="useLast.voile">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Modèle</mat-label>
|
||||
<input matInput [(ngModel)]="jump.voile">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Taille</mat-label>
|
||||
<input matInput [(ngModel)]="jump.taille" type="number" min="0" step="1">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Altitude
|
||||
<mat-checkbox class="float-end" (change)="setLastAltitude($event.checked)" [(ngModel)]="useLast.altitude">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Hauteur</mat-label>
|
||||
<input matInput [(ngModel)]="jump.hauteur" type="number" min="0" step="1">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Hauteur de déploiement</mat-label>
|
||||
<input matInput [(ngModel)]="jump.deploiement" type="number" min="0" step="1">
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Discipline
|
||||
<mat-checkbox class="float-end" (change)="setLastDiscipline($event.checked)" [(ngModel)]="useLast.discipline">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Categorie</mat-label>
|
||||
<input matInput [(ngModel)]="jump.categorie">
|
||||
<mat-hint>FF / Solo / VR / Wingsuit</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Module</mat-label>
|
||||
<input matInput [(ngModel)]="jump.module">
|
||||
<mat-hint>Chutas / Track / Trace</mat-hint>
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Divers
|
||||
<mat-checkbox class="float-end" (change)="setLastDivers($event.checked)" [(ngModel)]="useLast.divers">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Accessoires</mat-label>
|
||||
<input matInput [(ngModel)]="jump.accessoires">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Zone</mat-label>
|
||||
<input matInput [(ngModel)]="jump.zone">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Groupe
|
||||
<mat-checkbox class="float-end" (change)="setLastGroupe($event.checked)" [(ngModel)]="useLast.groupe">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Sautants</mat-label>
|
||||
<input matInput type="text" placehomder="Ajouter un sautant" [formControl]="tagField" (keyup.enter)="addSautant()">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Nombre de participants</mat-label>
|
||||
<input matInput [(ngModel)]="jump.participants" type="number" min="0" step="1" [value]="jump.sautants.length+1">
|
||||
</mat-form-field>
|
||||
<div class="mb-2">
|
||||
<span *ngFor="let sautant of jump.sautants" class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle pointer me-1" fontIcon="close" (click)="removeSautant(sautant)"></mat-icon>
|
||||
{{ sautant }}
|
||||
</span>
|
||||
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon>
|
||||
ju_solide
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Programme
|
||||
<mat-checkbox class="float-end" [(ngModel)]="params.video">
|
||||
<mat-icon fontIcon="videocam" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2 w-100">
|
||||
<mat-label>Programme</mat-label>
|
||||
<textarea matInput [(ngModel)]="jump.programme"></textarea>
|
||||
</mat-form-field>
|
||||
<span class="d-inline-block align-middle ms-1">{{ filepath }}{{ this.jumpForm.controls['date'].value | date: 'yyyy-MM-dd' }}_S00{{ nextJump }}.mp4</span>
|
||||
<span class="d-inline-block align-middle ms-1">{{ jump.dossier }}{{ jump.video }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<span class="flex-spacer"></span>
|
||||
<button mat-button (click)="closeDialog()" class="me-1">Annuler</button>
|
||||
<!--<button mat-button color="warn" [disabled]="!jumpForm.valid" [mat-dialog-close]="{jump: jump, params: params}" cdkFocusInitial>Enregistrer</button>-->
|
||||
<button mat-button color="warn" [disabled]="!jumpForm.valid" (click)="submitForm()" cdkFocusInitial>Enregistrer</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -1,145 +0,0 @@
|
||||
<h2 mat-dialog-title class="bg-primary border-bottom border-light-subtle d-flex mb-3 pt-3 pb-2">
|
||||
Modifier un saut<span class="flex-spacer"></span>
|
||||
<mat-icon fontIcon="close" class="pointer" (click)="closeDialog()"></mat-icon>
|
||||
</h2>
|
||||
<mat-dialog-content>
|
||||
<div class="container">
|
||||
<form class="row" [formGroup]="jumpForm" (ngSubmit)="submitForm()">
|
||||
<div class="col-xs-12">
|
||||
<mat-form-field appearance="outline" class="mt-2 me-2" color="primary">
|
||||
<mat-label>Numéro du saut</mat-label>
|
||||
<input matInput formControlName="numero">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="mt-2 me-3" color="primary">
|
||||
<mat-label>Choisissez une date</mat-label>
|
||||
<input matInput [matDatepicker]="datepicker" formControlName="date" />
|
||||
<mat-hint>MM/DD/YYYY</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="datepicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #datepicker>
|
||||
<mat-datepicker-actions>
|
||||
<button mat-button matDatepickerCancel>Annuler</button>
|
||||
<button mat-raised-button color="primary" matDatepickerApply>Appliquer</button>
|
||||
</mat-datepicker-actions>
|
||||
</mat-datepicker>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Dropzone</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Lieu</mat-label>
|
||||
<input matInput formControlName="lieu">
|
||||
<mat-error *ngIf="this.jumpForm.controls['lieu'].status === 'INVALID'">{{getErrorMessage('lieu')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Oaci</mat-label>
|
||||
<input matInput formControlName="oaci">
|
||||
<mat-error *ngIf="this.jumpForm.controls['oaci'].status === 'INVALID'">{{getErrorMessage('oaci')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Aéronef</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Aéronef</mat-label>
|
||||
<input matInput formControlName="aeronef">
|
||||
<mat-error *ngIf="this.jumpForm.controls['aeronef'].status === 'INVALID'">{{getErrorMessage('aeronef')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Imatriculation</mat-label>
|
||||
<input matInput formControlName="imat">
|
||||
<mat-error *ngIf="this.jumpForm.controls['imat'].status === 'INVALID'">{{getErrorMessage('imat')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Voile</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Modèle</mat-label>
|
||||
<input matInput formControlName="voile">
|
||||
<mat-error *ngIf="this.jumpForm.controls['voile'].status === 'INVALID'">{{getErrorMessage('voile')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Taille en ft<sup>2</sup></mat-label>
|
||||
<input matInput formControlName="taille" type="number" min="0" step="1">
|
||||
<span matTextSuffix>ft<sup>2</sup></span>
|
||||
<mat-error *ngIf="this.jumpForm.controls['taille'].status === 'INVALID'">{{getErrorMessage('taille')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Altitude</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Hauteur de largage</mat-label>
|
||||
<input matInput formControlName="hauteur" type="number" min="0" step="1">
|
||||
<mat-error *ngIf="this.jumpForm.controls['hauteur'].status === 'INVALID'">{{getErrorMessage('hauteur')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Hauteur de déploiement</mat-label>
|
||||
<input matInput formControlName="deploiement" type="number" min="0" step="1">
|
||||
<mat-error *ngIf="this.jumpForm.controls['deploiement'].status === 'INVALID'">{{getErrorMessage('deploiement')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Discipline</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Categorie</mat-label>
|
||||
<input matInput formControlName="categorie">
|
||||
<mat-error *ngIf="this.jumpForm.controls['categorie'].status === 'INVALID'">{{getErrorMessage('categorie')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Module</mat-label>
|
||||
<input matInput formControlName="module">
|
||||
<mat-error *ngIf="this.jumpForm.controls['module'].status === 'INVALID'">{{getErrorMessage('module')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Divers</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Accessoires</mat-label>
|
||||
<input matInput formControlName="accessoires">
|
||||
<mat-error *ngIf="this.jumpForm.controls['accessoires'].status === 'INVALID'">{{getErrorMessage('accessoires')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Zone</mat-label>
|
||||
<input matInput formControlName="zone">
|
||||
<mat-error *ngIf="this.jumpForm.controls['zone'].status === 'INVALID'">{{getErrorMessage('zone')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Groupe</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Sautants</mat-label>
|
||||
<input matInput placeholder="Ajouter un sautant" [formControl]="tagField" (keyup.enter)="addSautant()">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Nombre de participants</mat-label>
|
||||
<input matInput formControlName="participants" type="number" min="0" step="1">
|
||||
<mat-error *ngIf="this.jumpForm.controls['participants'].status === 'INVALID'">{{getErrorMessage('participants')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<div class="mb-2">
|
||||
<span *ngFor="let sautant of jump.sautants" class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle pointer me-1" fontIcon="close" (click)="removeSautant(sautant)"></mat-icon>
|
||||
{{ sautant }}
|
||||
</span>
|
||||
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon>
|
||||
ju_solide
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Programme</h4>
|
||||
<mat-form-field appearance="fill" class="me-2 w-100">
|
||||
<mat-label>Programme</mat-label>
|
||||
<textarea matInput formControlName="programme"></textarea>
|
||||
<mat-error *ngIf="this.jumpForm.controls['programme'].status === 'INVALID'">{{getErrorMessage('programme')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Vidéo</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Dossier</mat-label>
|
||||
<input matInput formControlName="dossier">
|
||||
<mat-error *ngIf="this.jumpForm.controls['dossier'].status === 'INVALID'">{{getErrorMessage('dossier')}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Video</mat-label>
|
||||
<input matInput formControlName="video">
|
||||
<mat-error *ngIf="this.jumpForm.controls['video'].status === 'INVALID'">{{getErrorMessage('video')}}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<span class="flex-spacer"></span>
|
||||
<button mat-button (click)="closeDialog()" class="me-1">Annuler</button>
|
||||
<button mat-button color="primary" [disabled]="!jumpForm.valid" (click)="submitForm()" cdkFocusInitial>Enregistrer</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -1,98 +0,0 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { NgFor, NgIf } from '@angular/common';
|
||||
import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
|
||||
import { Jump, JumpForm } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-edit-dialog',
|
||||
templateUrl: 'jump-edit.dialog.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgFor, NgIf, FormsModule, ReactiveFormsModule,
|
||||
MatButtonModule, MatCheckboxModule, MatDatepickerModule,
|
||||
MatDialogModule, MatFormFieldModule, MatIconModule,
|
||||
MatInputModule, MatSelectModule, MatOptionModule
|
||||
]
|
||||
})
|
||||
export class JumpEditDialogComponent {
|
||||
public tagField = new FormControl<string>('', { nonNullable: true});
|
||||
public jumpForm: FormGroup;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<JumpEditDialogComponent>,
|
||||
private fb: FormBuilder,
|
||||
@Inject(MAT_DIALOG_DATA) public jump: Jump
|
||||
) {
|
||||
let controlsConfig = {
|
||||
slug: [this.jump.slug, Validators.required],
|
||||
date: [this.jump.date, Validators.required],
|
||||
numero: [this.jump.numero, Validators.required],
|
||||
lieu: [this.jump.lieu, Validators.required],
|
||||
oaci: this.jump.oaci,
|
||||
aeronef: [this.jump.aeronef, Validators.required],
|
||||
imat: this.jump.imat,
|
||||
hauteur: [this.jump.hauteur, Validators.required],
|
||||
deploiement: this.jump.deploiement,
|
||||
voile: this.jump.voile,
|
||||
taille: [this.jump.taille, Validators.required],
|
||||
categorie: this.jump.categorie,
|
||||
module: this.jump.module,
|
||||
participants: [this.jump.participants, Validators.required],
|
||||
programme: this.jump.programme,
|
||||
accessoires: this.jump.accessoires,
|
||||
zone: this.jump.zone,
|
||||
dossier: this.jump.dossier,
|
||||
video: this.jump.video
|
||||
};
|
||||
this.jumpForm = this.fb.group(controlsConfig);
|
||||
}
|
||||
|
||||
addSautant(): void {
|
||||
const tag = this.tagField.value;
|
||||
if (tag != null && tag.trim() !== '' && this.jump.sautants.indexOf(tag) < 0) {
|
||||
this.jump.sautants.push(tag);
|
||||
}
|
||||
const participants = (this.jump.sautants.length + 1);
|
||||
this.jumpForm.controls['participants'].setValue(participants);
|
||||
this.tagField.reset('');
|
||||
}
|
||||
|
||||
closeDialog(): void {
|
||||
// close the dialog without result
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
getErrorMessage(name: string): string {
|
||||
if (this.jumpForm.controls[name].errors !== null) {
|
||||
return `Ce champ est requis.`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
removeSautant(index: string): void {
|
||||
this.jump.sautants = this.jump.sautants.filter((sautant) => sautant !== index);
|
||||
const participants = (this.jump.sautants.length + 1);
|
||||
this.jumpForm.controls['participants'].setValue(participants);
|
||||
}
|
||||
|
||||
submitForm(): void {
|
||||
// update the model
|
||||
this.updateJump(this.jumpForm.value);
|
||||
// close the dialog with result
|
||||
this.dialogRef.close(this.jump);
|
||||
}
|
||||
|
||||
updateJump(values: NonNullable<unknown>): void {
|
||||
Object.assign(this.jump, values);
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<h2 mat-dialog-title class="bg-cyan border-bottom border-light-subtle d-flex mb-3 pt-3 pb-2">
|
||||
Saut n°{{jump.numero}}<span class="flex-spacer"></span>
|
||||
<mat-icon fontIcon="close" class="pointer" (click)="closeDialog()"></mat-icon>
|
||||
</h2>
|
||||
<mat-dialog-content>
|
||||
<div class="container">
|
||||
<h5 class="border-bottom text-light-info border-light-subtle pb-2 mb-3">Le {{ jump.date | date: 'dd/MM/yyyy' }} à {{ jump.lieu }} :</h5>
|
||||
<div class="row mb-3">
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<dl class="dl-horizontal">
|
||||
<dt class="text-cyan fw-normal">Lieu:</dt>
|
||||
<dd>{{jump.lieu}}</dd>
|
||||
<dt class="text-cyan fw-normal">Oaci:</dt>
|
||||
<dd>{{jump.oaci}}</dd>
|
||||
<dt class="text-cyan fw-normal">Aeronef:</dt>
|
||||
<dd>{{jump.aeronef}}</dd>
|
||||
<dt class="text-cyan fw-normal">Imat:</dt>
|
||||
<dd>{{jump.imat}}</dd>
|
||||
<dt class="text-cyan fw-normal">Voile:</dt>
|
||||
<dd>{{jump.voile}}</dd>
|
||||
<dt class="text-cyan fw-normal">Taille:</dt>
|
||||
<dd>{{jump.taille}} ft<sup>2</sup></dd>
|
||||
<dt class="text-cyan fw-normal">Participants:</dt>
|
||||
<dd>{{jump.participants}}</dd>
|
||||
<dt class="text-cyan fw-normal">Sautants:</dt>
|
||||
<dd>
|
||||
<span *ngFor="let sautant of jump.sautants" class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
|
||||
{{ sautant }}
|
||||
</span>
|
||||
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
|
||||
ju_solide
|
||||
</span>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<dl class="dl-horizontal">
|
||||
<dt class="text-cyan fw-normal">Hauteur:</dt>
|
||||
<dd>{{jump.hauteur}} m</dd>
|
||||
<dt class="text-cyan fw-normal">Deploiement:</dt>
|
||||
<dd>{{jump.deploiement}} m</dd>
|
||||
<dt class="text-cyan fw-normal">Categorie:</dt>
|
||||
<dd>{{jump.categorie}}</dd>
|
||||
<dt class="text-cyan fw-normal">Module:</dt>
|
||||
<dd>{{jump.module}}</dd>
|
||||
<dt class="text-cyan fw-normal">Accessoires:</dt>
|
||||
<dd>{{jump.accessoires}}</dd>
|
||||
<dt class="text-cyan fw-normal">Zone:</dt>
|
||||
<dd>{{jump.zone}}</dd>
|
||||
<dt class="text-cyan fw-normal">Dossier media:</dt>
|
||||
<dd>{{jump.dossier}}</dd>
|
||||
<dt class="text-cyan fw-normal">Video:</dt>
|
||||
<dd>{{jump.video}}</dd>
|
||||
<dt class="text-cyan fw-normal">Programme:</dt>
|
||||
<dd>{{jump.programme}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<span class="flex-spacer"></span>
|
||||
<button mat-button (click)="closeDialog()" class="me-1">Fermer</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Injectable, } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { Jump } from 'src/app/core/models';
|
||||
import { JumpsService } from 'src/app/core/services';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class JumpResolver implements Resolve<Jump> {
|
||||
constructor(
|
||||
private _jumpsService: JumpsService,
|
||||
private router: Router
|
||||
) { }
|
||||
|
||||
resolve(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Observable<any> {
|
||||
|
||||
return this._jumpsService.get(route.params['slug'])
|
||||
.pipe(catchError(() => this.router.navigateByUrl('/')));
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { JumpComponent } from './jump.component';
|
||||
import { JumpResolver } from './jump-resolver.service';
|
||||
import { AuthGuard } from 'src/app/core/services';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: ':slug',
|
||||
component: JumpComponent,
|
||||
canActivate: [AuthGuard],
|
||||
resolve: {
|
||||
jump: JumpResolver
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class JumpRoutingModule { }
|
||||
@@ -1,73 +1,132 @@
|
||||
<div class="content">
|
||||
<h1 class="my-0">Saut n°{{ jump.numero }} <small class="text-muted">le {{ jump.date | date: 'dd/MM/yyyy' }} à {{ jump.lieu }}</small></h1>
|
||||
<hr />
|
||||
<section class="row mb-3">
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<dl class="dl-horizontal">
|
||||
<dt class="text-accent fw-normal">Lieu:</dt>
|
||||
<dd>{{jump.lieu}}</dd>
|
||||
<dt class="text-accent fw-normal">Oaci:</dt>
|
||||
<dd>{{jump.oaci}}</dd>
|
||||
<dt class="text-accent fw-normal">Aeronef:</dt>
|
||||
<dd>{{jump.aeronef}}</dd>
|
||||
<dt class="text-accent fw-normal">Imat:</dt>
|
||||
<dd>{{jump.imat}}</dd>
|
||||
<dt class="text-accent fw-normal">Voile:</dt>
|
||||
<dd>{{jump.voile}}</dd>
|
||||
<dt class="text-accent fw-normal">Taille:</dt>
|
||||
<dd>{{jump.taille}} ft<sup>2</sup></dd>
|
||||
<dt class="text-accent fw-normal">Programme:</dt>
|
||||
<dd>{{jump.programme}}</dd>
|
||||
<dt class="text-accent fw-normal">Participants:</dt>
|
||||
<dd>{{jump.participants}}</dd>
|
||||
</dl>
|
||||
<div @flyInOut>
|
||||
<div class="content">
|
||||
<div class="d-flex">
|
||||
<div class="flex-fill">
|
||||
<h1 class="mb-1">{{title}}</h1>
|
||||
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
|
||||
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="flex-spacer"></span>
|
||||
<div class="align-self-center me-2">
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuAeronef" aria-label="Menu Aeronef">
|
||||
<mat-icon fontIcon="more_vert"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuAeronef="matMenu">
|
||||
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
|
||||
<button mat-menu-item>
|
||||
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<dl class="dl-horizontal">
|
||||
<dt class="text-accent fw-normal">Hauteur:</dt>
|
||||
<dd>{{jump.hauteur}} m</dd>
|
||||
<dt class="text-accent fw-normal">Deploiement:</dt>
|
||||
<dd>{{jump.deploiement}} m</dd>
|
||||
<dt class="text-accent fw-normal">Categorie:</dt>
|
||||
<dd>{{jump.categorie}}</dd>
|
||||
<dt class="text-accent fw-normal">Module:</dt>
|
||||
<dd>{{jump.module}}</dd>
|
||||
<dt class="text-accent fw-normal">Accessoires:</dt>
|
||||
<dd>{{jump.accessoires}}</dd>
|
||||
<dt class="text-accent fw-normal">Zone:</dt>
|
||||
<dd>{{jump.zone}}</dd>
|
||||
<dt class="text-accent fw-normal">Emplacement des media:</dt>
|
||||
<dd>{{jump.dossier}}</dd>
|
||||
<dt class="text-accent fw-normal">Video:</dt>
|
||||
<dd>{{jump.video}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-xs-12 mb-2">
|
||||
<span *ngFor="let sautant of jump.sautants" class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
|
||||
{{ sautant }}
|
||||
</span>
|
||||
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
|
||||
ju_solide
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
<section class="border-top pt-4 d-flex">
|
||||
<!--
|
||||
<huapp-jump-meta [jump]="jump" class="me-2"></huapp-jump-meta>
|
||||
<span class="flex-spacer"></span>
|
||||
-->
|
||||
<span class="flex-spacer"></span>
|
||||
<span [hidden]="!canModify">
|
||||
<button mat-button color="primary" (click)="openEditDialog(jump)" class="me-1">
|
||||
<mat-icon aria-label="Modifier" fontIcon="edit"></mat-icon>
|
||||
Modifier
|
||||
</button>
|
||||
<button mat-button color="danger" (click)="openDeleteDialog(jump)">
|
||||
<mat-icon aria-label="Supprimer" fontIcon="delete"></mat-icon>
|
||||
Supprimer
|
||||
</button>
|
||||
</span>
|
||||
</section>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ subtitle }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-2">
|
||||
@if(displayCharts) {
|
||||
<div class="row">
|
||||
<div class="col-xxl-6 col-xs-12">
|
||||
<app-linearea-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor"></app-linearea-chart>
|
||||
</div>
|
||||
<div class="col-xxl-6 col-xs-12">
|
||||
<app-bars-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor" [legend]="true"></app-bars-chart>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<mat-divider class="mt-3 mb-0"></mat-divider>
|
||||
<table class="table table-striped table-dark table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th *ngFor='let name of seriesHeader; let i=index' class="text-end"> {{ name }} </th>
|
||||
<th class="text-end">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor='let values of seriesRow; let i=index'>
|
||||
<th class="{{seriesColorClass[i]}} text-end"> ● {{ seriesName[i] }} </th>
|
||||
<td *ngFor='let value of values; let i=index' class="font-monospace text-end">
|
||||
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template>
|
||||
</td>
|
||||
<th class="{{seriesColorClass[i]}} font-monospace text-end">{{ seriesRowTotal[i] }}</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th class="text-end">Total</th>
|
||||
<th *ngFor='let value of seriesColTotal; let i=index' class="font-monospace text-end">
|
||||
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template>
|
||||
</th>
|
||||
<th class="font-monospace text-end">{{ grandTotal }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-end">Moyenne</th>
|
||||
<th *ngFor='let value of seriesRowAvg; let i=index' class="font-monospace text-end">
|
||||
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
|
||||
</th>
|
||||
<th class="font-monospace text-end">{{ grandAvg | number : '1.0-1' }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="text-end">Moyenne <small>3 dernières années</small></th>
|
||||
<th *ngFor='let value of seriesRowAvgLastYears; let i=index' class="font-monospace text-end">
|
||||
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
|
||||
</th>
|
||||
<th class="font-monospace text-end">{{ grandAvgLastYears | number : '1.0-1' }}</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card class="mt-3">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Types de sauts</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-2">
|
||||
@if(displayCharts) {
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRowCumulated" [colors]="seriesTypesColor" [legend]="true"></app-line-chart>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="mt-2 mb-3"></mat-divider>
|
||||
<div class="row">
|
||||
@for (item of getCategories(); track $index) {
|
||||
<div class="col-lg-2 col-sm-4 col-xs-6">
|
||||
<app-circle-chart [headers]="seriesHeader" [names]="seriesName" [values]="[item.count, (lastJump.numero-item.count)]" [color]="$index" [label]="item.categorie"></app-circle-chart>
|
||||
</div>
|
||||
}
|
||||
@for (item of getModules(); track $index) {
|
||||
<div class="col-lg-2 col-sm-4 col-xs-6">
|
||||
<app-circle-chart [headers]="seriesHeader" [names]="seriesName" [values]="[item.count, (lastJump.numero-item.count)]" [color]="getModuleColor($index)" [label]="item.module"></app-circle-chart>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<!--
|
||||
<div class="row">
|
||||
<div class="col-xxl-6 col-xs-12">
|
||||
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRow" [colors]="seriesTypesColor" [legend]="true"></app-line-chart>
|
||||
</div>
|
||||
<div class="col-xxl-6 col-xs-12">
|
||||
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRowCumulated" [colors]="seriesTypesColor" [legend]="true"></app-line-chart>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
@@ -8,8 +8,8 @@ describe('JumpComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [JumpComponent]
|
||||
});
|
||||
imports: [JumpComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(JumpComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -1,139 +1,248 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatNativeDateModule } from '@angular/material/core';
|
||||
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatSnackBar, MatSnackBarHorizontalPosition, MatSnackBarModule, MatSnackBarVerticalPosition } from '@angular/material/snack-bar';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
|
||||
import { Errors, Jump, User } from 'src/app/core/models';
|
||||
import { JumpsService, UserService } from 'src/app/core/services';
|
||||
import { JumpDeleteDialogComponent, JumpEditDialogComponent } from 'src/app/components/jump/dialogs'
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { MenuItems } from 'src/app/components/shared';
|
||||
import { BarsChartComponent, CircleChartComponent, LineChartComponent, LineAreaChartComponent } from 'src/app/components/shared/helpers-chart';
|
||||
import { UtilitiesService } from 'src/app/core/services';
|
||||
import { Jump, JumpByCategorie, JumpByDate, JumpByDateByModule, JumpByModule, JumpsPageData, JumpYears } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-jump',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule, RouterModule,
|
||||
MatButtonModule, MatDialogModule, MatIconModule, MatNativeDateModule, MatSnackBarModule,
|
||||
JumpDeleteDialogComponent, JumpEditDialogComponent
|
||||
CommonModule,
|
||||
MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatExpansionModule, MatIconModule, MatMenuModule,
|
||||
BarsChartComponent, CircleChartComponent, LineChartComponent, LineAreaChartComponent
|
||||
],
|
||||
selector: 'huapp-jump',
|
||||
templateUrl: './jump.component.html',
|
||||
styleUrls: ['./jump.component.scss']
|
||||
styleUrl: './jump.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateX(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateX(-100%)' }),
|
||||
animate(200)
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateX(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
})
|
||||
export class JumpComponent implements OnInit, OnDestroy {
|
||||
private _currentUser: Subscription = new Subscription();
|
||||
private _data: Subscription = new Subscription();
|
||||
public errors!: Errors;
|
||||
public jump: Jump = {} as Jump;
|
||||
public currentUser: User = {} as User;
|
||||
public canModify = false;
|
||||
private _jumpsByCategorie: Array<JumpByCategorie> = [];
|
||||
private _jumpsByModule: Array<JumpByModule> = [];
|
||||
private _jumpsByDate: Array<JumpByDate> = [];
|
||||
public lastJump: Jump = {} as Jump;
|
||||
public title: string = 'Sauts';
|
||||
public subtitle: string = 'Nombre total de sauts par mois';
|
||||
public displayCharts = false;
|
||||
public destroyRef = inject(DestroyRef);
|
||||
public min = 0;
|
||||
public max = 0;
|
||||
public grandAvg = 0;
|
||||
public grandAvgLastYears = 0;
|
||||
public grandTotal = 0;
|
||||
public seriesHeader: string[] = [];
|
||||
public seriesName: string[] = [];
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
};
|
||||
public seriesTypesHeader: string[] = [];
|
||||
public seriesTypesName: string[] = [];
|
||||
public seriesTypesRow: Array<Array<number>> = [];
|
||||
public seriesTypesRowCumulated: Array<Array<number>> = [];
|
||||
public seriesTypesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'pastels'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'pastels')
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
public seriesRowTotal: number[] = [];
|
||||
public seriesRowAvg: number[] = [];
|
||||
public seriesRowAvgLastYears: number[] = [];
|
||||
public seriesColTotal: number[] = [];
|
||||
public seriesColTotalClosed: number[] = [];
|
||||
public seriesColTotalLastYears: number[] = [];
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private dialog: MatDialog,
|
||||
private _jumpsService: JumpsService,
|
||||
private _userService: UserService,
|
||||
private _snackBar: MatSnackBar
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
// Retreive the prefetched jump
|
||||
const data$: Observable<any> = this.route.data;
|
||||
this._data = data$.subscribe((data: { jump: Jump }) => {
|
||||
this.jump = data.jump;
|
||||
});
|
||||
// Load the current user's data
|
||||
const currentUser$: Observable<User> = this._userService.currentUser;
|
||||
this._currentUser = currentUser$.subscribe((userData: User) => {
|
||||
this.currentUser = userData;
|
||||
this.canModify = this.currentUser.username === this.jump.author.username;
|
||||
const data$: Observable<{ jumpsPageData: JumpsPageData }> = this.route.data as Observable<{ jumpsPageData: JumpsPageData }>;
|
||||
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { jumpsPageData: JumpsPageData }) => {
|
||||
const pageData: JumpsPageData = data.jumpsPageData;
|
||||
this.lastJump = pageData.lastjump;
|
||||
const currentYear: number = new Date().getFullYear();
|
||||
const types: Array<string> = ['FF', 'Solo', 'VR', 'Wingsuit'];
|
||||
const verticals: Array<string> = ['Chutas', 'Head Up', 'Head Down'];
|
||||
const others: Array<string> = ['Track', 'Trace'];
|
||||
const modules: Array<string> = [...verticals, ...others];
|
||||
this.min = pageData.jumpsByYears[0].year;
|
||||
this.max = pageData.jumpsByYears[(pageData.jumpsByYears.length-1)].year;
|
||||
|
||||
this.seriesHeader = this._utilitiesService.getMonthsList();
|
||||
const values: Array<number> = this.seriesHeader.map(() => 0);
|
||||
this.seriesColTotal = [...values];
|
||||
this.seriesColTotalClosed = [...values];
|
||||
this.seriesColTotalLastYears = [...values];
|
||||
this.seriesName = pageData.jumpsByYears.map((row: JumpYears) => {
|
||||
this.seriesRow.push([...values]);
|
||||
return row.year.toString();
|
||||
});
|
||||
|
||||
this._jumpsByDate = pageData.jumpsByDate.map((row: JumpByDate) => {
|
||||
this.seriesRow[(row.year-this.min)][(row.month-1)] = row.count;
|
||||
this.seriesColTotal[(row.month-1)] += row.count;
|
||||
if (row.year < currentYear) {
|
||||
this.seriesColTotalClosed[(row.month-1)] += row.count;
|
||||
}
|
||||
if (row.year >= (currentYear-3) && row.year < currentYear) {
|
||||
this.seriesColTotalLastYears[(row.month-1)] += row.count;
|
||||
}
|
||||
return row;
|
||||
});
|
||||
this.seriesRow.forEach((row: number[]) => {
|
||||
const total = row.reduce((partialSum, a) => partialSum + a, 0);
|
||||
this.seriesRowTotal.push(total);
|
||||
});
|
||||
this.seriesColTotalClosed.forEach((row: number) => {
|
||||
const value: number = (row/(this.seriesName.length-2)); // -2 pour ne pas compter la première année
|
||||
this.seriesRowAvg.push(parseInt(value.toFixed(0)));
|
||||
});
|
||||
this.seriesColTotalLastYears.forEach((row: number) => {
|
||||
const value: number = (row/3); // Les 3 dernières années
|
||||
this.seriesRowAvgLastYears.push(parseInt(value.toFixed(0)));
|
||||
});
|
||||
this.grandAvg = this.seriesRowAvg.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
|
||||
this.grandAvgLastYears = this.seriesRowAvgLastYears.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
|
||||
this.grandTotal = this.seriesColTotal.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
|
||||
|
||||
pageData.jumpsByCategory.forEach((row: JumpByCategorie) => {
|
||||
if (types.indexOf(row.categorie) !== -1) {
|
||||
this._jumpsByCategorie.push(row);
|
||||
}
|
||||
});
|
||||
|
||||
//const focus: Array<string> = ['FF', 'Solo']; // ['FF']
|
||||
let verticalCount:number = 0;
|
||||
let otherCount:number = 0;
|
||||
pageData.jumpsByModule.forEach((row: JumpByModule) => {
|
||||
//if (focus.indexOf(row.categorie) !== -1 && modules.indexOf(row.module) !== -1) {
|
||||
if (modules.indexOf(row.module) !== -1) {
|
||||
if (verticals.indexOf(row.module) !== -1) {
|
||||
verticalCount += row.count;
|
||||
} else {
|
||||
otherCount += row.count;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (verticalCount > 0) {
|
||||
const row:JumpByModule = {
|
||||
categorie: 'FF',
|
||||
module: 'Vertical',
|
||||
count: verticalCount
|
||||
}
|
||||
this._jumpsByModule.push(row);
|
||||
}
|
||||
if (otherCount > 0) {
|
||||
const row:JumpByModule = {
|
||||
categorie: 'FF',
|
||||
module: 'Track/Trace',
|
||||
count: otherCount
|
||||
}
|
||||
this._jumpsByModule.push(row);
|
||||
}
|
||||
|
||||
for (let year = 0; year < pageData.jumpsByYears.length; year++) {
|
||||
for (let index = 0; index < 12; index++) {
|
||||
const month: string = ((index+1) < 10) ? `0${(index+1)}` : `${(index+1)}`;
|
||||
this.seriesTypesHeader.push(`${month}-${pageData.jumpsByYears[year].year}`);
|
||||
}
|
||||
}
|
||||
const emptyValues: Array<number> = this.seriesTypesHeader.map(() => 0);
|
||||
this.seriesTypesName = types;
|
||||
this.seriesTypesName.forEach(() => {
|
||||
this.seriesTypesRow.push([...emptyValues]);
|
||||
});
|
||||
pageData.jumpsByDateByModule.forEach((row: JumpByDateByModule) => {
|
||||
const indexOf: number = this.seriesTypesName.indexOf(row.categorie);
|
||||
if (indexOf !== -1) {
|
||||
this.seriesTypesRow[indexOf][(((row.year-this.min)*12)+row.month-1)] += row.count;
|
||||
}
|
||||
});
|
||||
this.seriesTypesRowCumulated = this.seriesTypesRow.map((row: Array<number>) => {
|
||||
let accumulated: number = 0;
|
||||
return row.map((value: number) => {
|
||||
accumulated += value;
|
||||
return accumulated;
|
||||
});
|
||||
});
|
||||
this.seriesTypesHeader = this.seriesTypesHeader.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
|
||||
this.seriesTypesRow = this.seriesTypesRow.map((row: Array<number>) => {
|
||||
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
|
||||
});
|
||||
this.seriesTypesRowCumulated = this.seriesTypesRowCumulated.map((row: Array<number>) => {
|
||||
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
|
||||
});
|
||||
/*
|
||||
console.log('this.min', this.min);
|
||||
console.log('this.max', this.max);
|
||||
console.log('this.lastJump', this.lastJump);
|
||||
console.log('this.seriesHeader', this.seriesHeader);
|
||||
console.log('this.seriesName', this.seriesName);
|
||||
console.log('this.seriesRow', this.seriesRow);
|
||||
console.log('this.seriesRowTotal', this.seriesRowTotal);
|
||||
console.log('this.seriesRowAvg', this.seriesRowAvg);
|
||||
console.log('this.seriesRowAvgLastYears', this.seriesRowAvgLastYears);
|
||||
console.log('this._jumpsByCategorie', this._jumpsByCategorie);
|
||||
console.log('this._jumpsByModule', this._jumpsByModule);
|
||||
console.log('this.seriesTypesHeader', this.seriesTypesHeader);
|
||||
console.log('this.seriesTypesName', this.seriesTypesName);
|
||||
console.log('this.seriesTypesRow', this.seriesTypesRow);
|
||||
console.log('this.seriesTypesRowCumulated', this.seriesTypesRowCumulated);
|
||||
*/
|
||||
this.displayCharts = true;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._data.unsubscribe();
|
||||
this._currentUser.unsubscribe();
|
||||
}
|
||||
|
||||
openDeleteDialog(jump: Jump): void {
|
||||
const dialogRef = this.dialog.open(JumpDeleteDialogComponent, {
|
||||
width: '60vw',
|
||||
data: jump
|
||||
});
|
||||
dialogRef.afterClosed().pipe(take(1))
|
||||
.subscribe(result => {
|
||||
if (result != undefined) {
|
||||
this._onDelete(result.slug);
|
||||
}
|
||||
});
|
||||
public getCategories(): Array<JumpByCategorie> {
|
||||
return this._jumpsByCategorie;
|
||||
}
|
||||
|
||||
openEditDialog(jump: Jump): void {
|
||||
const dialogRef = this.dialog.open(JumpEditDialogComponent, {
|
||||
width: '70vw',
|
||||
data: jump
|
||||
});
|
||||
dialogRef.afterClosed().pipe(take(1))
|
||||
.subscribe(result => {
|
||||
if (result != undefined) {
|
||||
this._onEdit(result);
|
||||
}
|
||||
});
|
||||
public getModules(): Array<JumpByModule> {
|
||||
return this._jumpsByModule;
|
||||
}
|
||||
|
||||
private _onDelete(slug: string): void {
|
||||
try {
|
||||
this._jumpsService.destroy(slug)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.router.navigateByUrl('/logbook');
|
||||
},
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
public getModuleColor(index: number): number {
|
||||
return (this._jumpsByCategorie.length + index);
|
||||
}
|
||||
|
||||
private _onEdit(jump: Jump): void {
|
||||
try {
|
||||
let previousValues: Jump = {} as Jump;
|
||||
Object.assign(previousValues, this.jump);
|
||||
this._jumpsService.update(jump)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (jump) => {
|
||||
this._resetErrors();
|
||||
this._openSnackBar(`Le saut n°${jump.numero} a été mis à jour !`, 'Annuler');
|
||||
this.jump = jump;
|
||||
},
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private _openSnackBar(content: string, title: string) {
|
||||
this._snackBar.open(content, title, {
|
||||
horizontalPosition: <MatSnackBarHorizontalPosition>'end',
|
||||
verticalPosition: <MatSnackBarVerticalPosition>'bottom',
|
||||
duration: 4000,
|
||||
});
|
||||
}
|
||||
|
||||
private _resetErrors(): void {
|
||||
this.errors = { errors: {} };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
|
||||
import { JumpComponent } from './jump.component';
|
||||
import { JumpResolver } from './jump-resolver.service';
|
||||
|
||||
import { JumpRoutingModule } from './jump-routing.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
JumpRoutingModule,
|
||||
JumpComponent
|
||||
],
|
||||
providers: [
|
||||
JumpResolver
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class JumpModule { }
|
||||
@@ -0,0 +1,291 @@
|
||||
<h2 mat-dialog-title class="bg-warn border-bottom border-light-subtle d-flex mb-3 pt-3 pb-2">
|
||||
Ajouter un saut<span class="flex-spacer"></span>
|
||||
<mat-icon fontIcon="close" class="pointer" (click)="closeDialog()"></mat-icon>
|
||||
</h2>
|
||||
<mat-dialog-content>
|
||||
<div class="container">
|
||||
<!--
|
||||
<div class="row">
|
||||
<div class="col-xs-12 border-bottom border-dark pb-2 mb-2">
|
||||
<mat-form-field appearance="outline" class="mt-2 me-2" color="warn">
|
||||
<mat-label>Numéro ou plage de saut à ajouter</mat-label>
|
||||
<input matInput [(ngModel)]="params.numeros" required />
|
||||
<mat-hint>645 / +5 / 650-660</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="mt-2 me-3" color="warn">
|
||||
<mat-label>Choisissez une date</mat-label>
|
||||
<input matInput [matDatepicker]="datepicker" [(ngModel)]="params.date" (dateChange)="onDateChange()" required />
|
||||
<mat-hint>MM/DD/YYYY</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="datepicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #datepicker>
|
||||
<mat-datepicker-actions>
|
||||
<button mat-button matDatepickerCancel>Annuler</button>
|
||||
<button mat-raised-button color="warn" matDatepickerApply>Appliquer</button>
|
||||
</mat-datepicker-actions>
|
||||
</mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-checkbox (change)="setLastJump($event.checked)" [checked]="useLast.all">
|
||||
<mat-icon fontIcon="history" class="align-middle me-2"></mat-icon> <span class="d-inline-block align-middle">Utiliser les valeurs du dernier saut</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
<form class="row" [formGroup]="jumpForm" (ngSubmit)="submitForm()">
|
||||
<div class="col-xs-12 border-bottom border-dark pb-2 mb-2">
|
||||
<mat-form-field appearance="outline" class="mt-2 me-2" color="warn">
|
||||
<mat-label>Numéro ou plage de saut à ajouter</mat-label>
|
||||
<input matInput formControlName="numero" required />
|
||||
<mat-hint>645 / +5 / 650-660</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="mt-2 me-3" color="warn">
|
||||
<mat-label>Choisissez une date</mat-label>
|
||||
<input matInput [matDatepicker]="datepicker" formControlName="date" (dateChange)="onDateChange()" required cdkFocusInitial />
|
||||
<mat-hint>MM/DD/YYYY</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="datepicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #datepicker>
|
||||
<mat-datepicker-actions>
|
||||
<button mat-button matDatepickerCancel>Annuler</button>
|
||||
<button mat-raised-button color="warn" matDatepickerApply>Appliquer</button>
|
||||
</mat-datepicker-actions>
|
||||
</mat-datepicker>
|
||||
</mat-form-field>
|
||||
<mat-checkbox (change)="setLastJump($event.checked)" [checked]="useLast.all">
|
||||
<mat-icon fontIcon="history" class="align-middle me-2"></mat-icon> <span class="d-inline-block align-middle">Utiliser les valeurs du dernier saut</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Dropzone
|
||||
<mat-checkbox class="float-end" (change)="setLastDropzone($event.checked)" [checked]="useLast.dropzone">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Lieu</mat-label>
|
||||
<input matInput formControlName="lieu" [matAutocomplete]="autoDropzone">
|
||||
<mat-autocomplete #autoDropzone="matAutocomplete" [displayWith]="displayLieuFn">
|
||||
@for (option of filteredOptions.dropzones | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelLieu(option)">{{option.lieu}} - {{option.oaci}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['lieu'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('lieu')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Oaci</mat-label>
|
||||
<input matInput formControlName="oaci" [matAutocomplete]="autoOaci">
|
||||
<mat-autocomplete #autoOaci="matAutocomplete" [displayWith]="displayOaciFn">
|
||||
@for (option of filteredOptions.oaci | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelOaci(option)">{{option.lieu}} - {{option.oaci}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['oaci'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('oaci')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Aéronef
|
||||
<mat-checkbox class="float-end" (change)="setLastAeronef($event.checked)" [checked]="useLast.aeronef">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Aéronef</mat-label>
|
||||
<input matInput formControlName="aeronef" [matAutocomplete]="autoAeronef">
|
||||
<mat-autocomplete #autoAeronef="matAutocomplete" [displayWith]="displayAeronefFn">
|
||||
@for (option of filteredOptions.aeronefs | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelAeronef(option)">{{option.aeronef}} - {{option.imat}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['aeronef'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('aeronef')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Imatriculation</mat-label>
|
||||
<input matInput formControlName="imat" [matAutocomplete]="autoImat">
|
||||
<mat-autocomplete #autoImat="matAutocomplete" [displayWith]="displayImatFn">
|
||||
@for (option of filteredOptions.imats | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelImat(option)">{{option.aeronef}} - {{option.imat}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['imat'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('imat')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Voile
|
||||
<mat-checkbox class="float-end" (change)="setLastVoile($event.checked)" [checked]="useLast.voile">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Modèle</mat-label>
|
||||
<input matInput formControlName="voile" [matAutocomplete]="autoCanopy">
|
||||
<mat-autocomplete #autoCanopy="matAutocomplete" [displayWith]="displayVoileFn">
|
||||
@for (option of filteredOptions.canopies | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelVoile(option)">{{option.voile}} - {{option.taille}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['voile'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('voile')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Taille en ft<sup>2</sup></mat-label>
|
||||
<input matInput formControlName="taille" [matAutocomplete]="autoTaille">
|
||||
<mat-autocomplete #autoTaille="matAutocomplete" [displayWith]="displayTailleFn">
|
||||
@for (option of filteredOptions.tailles | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelTaille(option)">{{option.voile}} - {{option.taille}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
<span matTextSuffix>ft<sup>2</sup></span>
|
||||
@if (this.jumpForm.controls['taille'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('taille')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Altitude
|
||||
<mat-checkbox class="float-end" (change)="setLastAltitude($event.checked)" [checked]="useLast.altitude">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Hauteur de largage</mat-label>
|
||||
<input matInput formControlName="hauteur" type="number" min="0" step="1">
|
||||
@if (this.jumpForm.controls['hauteur'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('hauteur')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Hauteur de déploiement</mat-label>
|
||||
<input matInput formControlName="deploiement" type="number" min="0" step="1">
|
||||
@if (this.jumpForm.controls['deploiement'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('deploiement')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Discipline
|
||||
<mat-checkbox class="float-end" (change)="setLastDiscipline($event.checked)" [checked]="useLast.discipline">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Categorie</mat-label>
|
||||
<input matInput formControlName="categorie" [matAutocomplete]="autoCategorie">
|
||||
<mat-autocomplete #autoCategorie="matAutocomplete" [displayWith]="displayCategorieFn">
|
||||
@for (option of filteredOptions.categories | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelCategorie(option)">{{option.categorie}}{{option.categorie && option.module ? ' - ' : ''}}{{option.module}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['categorie'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('categorie')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Module</mat-label>
|
||||
<input matInput formControlName="module" [matAutocomplete]="autoModule">
|
||||
<mat-autocomplete #autoModule="matAutocomplete" [displayWith]="displayModuleFn">
|
||||
@for (option of filteredOptions.modules | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelModule(option)">{{option.categorie}}{{option.categorie && option.module ? ' - ' : ''}}{{option.module}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['module'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('module')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Divers
|
||||
<mat-checkbox class="float-end" (change)="setLastDivers($event.checked)" [checked]="useLast.divers">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Accessoires</mat-label>
|
||||
<input matInput formControlName="accessoires">
|
||||
@if (this.jumpForm.controls['accessoires'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('accessoires')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Zone</mat-label>
|
||||
<input matInput formControlName="zone">
|
||||
@if (this.jumpForm.controls['zone'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('zone')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Groupe
|
||||
<mat-checkbox class="float-end" (change)="setLastGroupe($event.checked)" [checked]="useLast.groupe">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Sautants</mat-label>
|
||||
<input matInput placeholder="Ajouter un sautant" [formControl]="tagField" (keyup.enter)="addSautant()">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Nombre de participants</mat-label>
|
||||
<input matInput formControlName="participants" type="number" min="0" step="1">
|
||||
@if (this.jumpForm.controls['participants'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('participants')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<div class="mb-2">
|
||||
@for (sautant of jump.sautants; track sautant) {
|
||||
<span class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle pointer me-1" fontIcon="close" (click)="removeSautant(sautant)"></mat-icon>
|
||||
{{ sautant }}
|
||||
</span>
|
||||
}
|
||||
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon>
|
||||
ju_solide
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">
|
||||
Programme
|
||||
<mat-checkbox class="float-end" (change)="setLastProgramme($event.checked)" [checked]="useLast.programme">
|
||||
<mat-icon fontIcon="history" class="align-middle"></mat-icon>
|
||||
</mat-checkbox>
|
||||
</h4>
|
||||
<mat-form-field appearance="fill" class="me-2 w-100" color="warn">
|
||||
<mat-label>Programme</mat-label>
|
||||
<textarea matInput formControlName="programme"></textarea>
|
||||
@if (this.jumpForm.controls['programme'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('programme')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-secondary mb-3 lh-lg">Vidéo</h4>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Dossier</mat-label>
|
||||
<input matInput formControlName="dossier">
|
||||
@if (this.jumpForm.controls['dossier'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('dossier')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2" color="warn">
|
||||
<mat-label>Video</mat-label>
|
||||
<input matInput formControlName="video">
|
||||
@if (this.jumpForm.controls['video'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('video')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<span class="d-inline-block align-middle ms-1">{{ filepath }}{{ filename }}</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<span class="flex-spacer"></span>
|
||||
<button mat-button (click)="closeDialog()" class="me-1">Annuler</button>
|
||||
<!--<button mat-button color="warn" [disabled]="!jumpForm.valid" [mat-dialog-close]="{jump: jump, params: params}" cdkFocusInitial>Enregistrer</button>-->
|
||||
<button mat-button color="warn" [disabled]="!jumpForm.valid" (click)="submitForm()">Enregistrer</button>
|
||||
</mat-dialog-actions>
|
||||
+382
-31
@@ -1,6 +1,7 @@
|
||||
import { Component, Inject, OnDestroy } from '@angular/core';
|
||||
import { DatePipe, NgFor, NgIf } from '@angular/common';
|
||||
import { AsyncPipe, DatePipe } from '@angular/common';
|
||||
import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
@@ -10,23 +11,39 @@ import { MatInputModule } from '@angular/material/input';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
|
||||
import { Jump, JumpAddParams } from 'src/app/core/models';
|
||||
import { AeronefByImat, CanopyModelBySize, DropZoneByOaci, Jump, JumpAddParams, JumpByModule } from 'src/app/core/models';
|
||||
import { AeronefsService, CanopiesService, DropZonesService, JumpsService } from 'src/app/core/services';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-add-dialog',
|
||||
selector: 'app-jump-add-dialog',
|
||||
templateUrl: 'jump-add.dialog.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe, NgFor, NgIf, FormsModule, ReactiveFormsModule,
|
||||
MatButtonModule, MatCheckboxModule, MatDatepickerModule,
|
||||
MatDialogModule, MatFormFieldModule, MatIconModule,
|
||||
MatInputModule, MatSelectModule, MatOptionModule
|
||||
]
|
||||
AsyncPipe,
|
||||
DatePipe,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatAutocompleteModule,
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
MatDatepickerModule,
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatOptionModule
|
||||
]
|
||||
})
|
||||
export class JumpAddDialogComponent implements OnDestroy {
|
||||
private _formChanges: Array<Subscription> = [];
|
||||
private _aeronefByImat: Subscription = new Subscription();
|
||||
private _canopyModelBySize: Subscription = new Subscription();
|
||||
private _dropZoneByOaci: Subscription = new Subscription();
|
||||
private _jumpByModule: Subscription = new Subscription();
|
||||
public tagField = new FormControl<string>('', { nonNullable: true});
|
||||
public jumpForm: FormGroup;
|
||||
public jump: Jump;
|
||||
@@ -56,12 +73,34 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
programme: false,
|
||||
voile: false
|
||||
};
|
||||
public inputOptions: {
|
||||
aeronefs: Array<AeronefByImat>;
|
||||
canopies: Array<CanopyModelBySize>;
|
||||
dropzones: Array<DropZoneByOaci>;
|
||||
categories: Array<JumpByModule>;
|
||||
};
|
||||
public filteredOptions: {
|
||||
aeronefs: Observable<Array<AeronefByImat>>;
|
||||
imats: Observable<Array<AeronefByImat>>;
|
||||
canopies: Observable<Array<CanopyModelBySize>>;
|
||||
tailles: Observable<Array<CanopyModelBySize>>;
|
||||
dropzones: Observable<Array<DropZoneByOaci>>;
|
||||
oaci: Observable<Array<DropZoneByOaci>>;
|
||||
categories: Observable<Array<JumpByModule>>;
|
||||
modules: Observable<Array<JumpByModule>>;
|
||||
};
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<JumpAddDialogComponent>,
|
||||
private fb: FormBuilder,
|
||||
private _aeronefsService: AeronefsService,
|
||||
private _canopiesService: CanopiesService,
|
||||
private _dropZonesService: DropZonesService,
|
||||
private _jumpsService: JumpsService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: {jump: Jump, lastJump: Jump}
|
||||
) {
|
||||
this.inputOptions = { aeronefs: [], canopies: [], dropzones: [], categories: [] };
|
||||
//this.filteredOptions.aeronefs = this._aeronefsService.getAllByImat();
|
||||
this.jump = data.jump;
|
||||
this.lastJump = data.lastJump;
|
||||
this.nextJump = (this.lastJump.numero + 1);
|
||||
@@ -71,7 +110,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
video: true
|
||||
};
|
||||
this.jump.numero = this.nextJump;
|
||||
let controlsConfig = {
|
||||
const controlsConfig = {
|
||||
date: ['', Validators.required],
|
||||
numero: [this.nextJump, Validators.required],
|
||||
lieu: ['', Validators.required],
|
||||
@@ -103,12 +142,95 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
this.filename = this._computeFilename();
|
||||
})
|
||||
);
|
||||
/*
|
||||
this.filteredAeronefOptions = this.jumpForm.controls['aeronef'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const aeronef = typeof value === 'string' ? value : '';
|
||||
return aeronef ? this._filterString(aeronef as string, this.aeronefOptions) : this.aeronefOptions.slice();
|
||||
}),
|
||||
);
|
||||
this.filteredImatOptions = this.jumpForm.controls['imat'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const imat = typeof value === 'string' ? value : value?.imat;
|
||||
return imat ? this._filterImat(imat as string) : this.imatOptions.slice();
|
||||
}),
|
||||
);
|
||||
//this.filteredAeronefOptions = this._aeronefsService.getAllByImat();
|
||||
*/
|
||||
this.filteredOptions = {
|
||||
aeronefs: this.jumpForm.controls['aeronef'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const aeronef = typeof value === 'string' ? value : value?.aeronef;
|
||||
return aeronef ? this._filterImat(aeronef as string) : this.inputOptions.aeronefs.slice();
|
||||
}),
|
||||
),
|
||||
imats: this.jumpForm.controls['imat'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const imat = typeof value === 'string' ? value : value?.imat;
|
||||
return imat ? this._filterImat(imat as string) : this.inputOptions.aeronefs.slice();
|
||||
}),
|
||||
),
|
||||
canopies: this.jumpForm.controls['voile'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const voile = typeof value === 'string' ? value : value?.voile;
|
||||
return voile ? this._filterTaille(voile as string) : this.inputOptions.canopies.slice();
|
||||
}),
|
||||
),
|
||||
tailles: this.jumpForm.controls['taille'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const taille = typeof value === 'string' ? value : value?.taille.toString();
|
||||
return taille ? this._filterTaille(taille as string) : this.inputOptions.canopies.slice();
|
||||
}),
|
||||
),
|
||||
dropzones: this.jumpForm.controls['lieu'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const lieu = typeof value === 'string' ? value : value?.lieu;
|
||||
return lieu ? this._filterOaci(lieu as string) : this.inputOptions.dropzones.slice();
|
||||
}),
|
||||
),
|
||||
oaci: this.jumpForm.controls['oaci'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const oaci = typeof value === 'string' ? value : value?.oaci;
|
||||
return oaci ? this._filterOaci(oaci as string) : this.inputOptions.dropzones.slice();
|
||||
}),
|
||||
),
|
||||
categories: this.jumpForm.controls['categorie'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const category = typeof value === 'string' ? value : value?.categorie;
|
||||
return category ? this._filterModule(category as string) : this.inputOptions.categories.slice();
|
||||
}),
|
||||
),
|
||||
modules: this.jumpForm.controls['module'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const module = typeof value === 'string' ? value : value?.module;
|
||||
return module ? this._filterModule(module as string) : this.inputOptions.categories.slice();
|
||||
}),
|
||||
)
|
||||
};
|
||||
this._loadAeronefByImat();
|
||||
this._loadCanopyModelBySize();
|
||||
this._loadDropZoneByOaci();
|
||||
this._loadJumpByModule();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._formChanges.forEach((sub: Subscription) => {
|
||||
sub.unsubscribe();
|
||||
});
|
||||
this._aeronefByImat.unsubscribe();
|
||||
this._canopyModelBySize.unsubscribe();
|
||||
this._dropZoneByOaci.unsubscribe();
|
||||
this._jumpByModule.unsubscribe();
|
||||
}
|
||||
|
||||
public onDateChange(): void {
|
||||
@@ -117,9 +239,9 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
|
||||
private _computeFilename(numero?: string): string {
|
||||
let file: string = '';
|
||||
let timestamp: number = Date.parse(this.jumpForm.controls['date'].value);
|
||||
const timestamp: number = Date.parse(this.jumpForm.controls['date'].value);
|
||||
if (isNaN(timestamp) == false) {
|
||||
let date: Date = new Date(timestamp);
|
||||
const date: Date = new Date(timestamp);
|
||||
file += `${(date.getFullYear() + '').padStart(2, '0')}-${((date.getMonth() + 1) + '').padStart(2, '0')}-${(date.getDate() + '').padStart(2, '0')}_`;
|
||||
}
|
||||
if (numero !== undefined) {
|
||||
@@ -156,6 +278,78 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
private _filterImat(imat: string): Array<AeronefByImat> {
|
||||
const filterValue = imat.toLowerCase();
|
||||
return this.inputOptions.aeronefs.filter(option => (option.aeronef.toLowerCase().includes(filterValue) || option.imat.toLowerCase().includes(filterValue)));
|
||||
}
|
||||
|
||||
private _filterModule(module: string): Array<JumpByModule> {
|
||||
const filterValue = module.toLowerCase();
|
||||
return this.inputOptions.categories.filter(option => (option.categorie.toLowerCase().includes(filterValue) || option.module.toLowerCase().includes(filterValue)));
|
||||
}
|
||||
|
||||
private _filterOaci(oaci: string): Array<DropZoneByOaci> {
|
||||
const filterValue = oaci.toLowerCase();
|
||||
return this.inputOptions.dropzones.filter(option => (option.lieu.toLowerCase().includes(filterValue) || option.oaci.toLowerCase().includes(filterValue)));
|
||||
}
|
||||
|
||||
private _filterTaille(taille: string): Array<CanopyModelBySize> {
|
||||
const filterValue = taille.toLowerCase();
|
||||
return this.inputOptions.canopies.filter(option => (option.voile.toLowerCase().includes(filterValue) || option.taille.toString().includes(filterValue)));
|
||||
}
|
||||
|
||||
private _loadAeronefByImat(): void {
|
||||
const aeronefs$ = this._aeronefsService.getAllByImat();
|
||||
this._aeronefByImat = aeronefs$.subscribe((aggregate: Array<AeronefByImat>) => {
|
||||
/*
|
||||
this.inputOptions.imats = aggregate.map((row: AeronefByImat) => {
|
||||
if (this.inputOptions.aeronefs.indexOf(row.aeronef) === -1) {
|
||||
this.inputOptions.aeronefs.push(row.aeronef);
|
||||
}
|
||||
return row;
|
||||
});
|
||||
*/
|
||||
this.inputOptions.aeronefs = aggregate;
|
||||
});
|
||||
}
|
||||
|
||||
private _loadCanopyModelBySize(): void {
|
||||
const canopies$ = this._canopiesService.getAllBySizeByModel();
|
||||
this._canopyModelBySize = canopies$.subscribe((aggregate: Array<CanopyModelBySize>) => {
|
||||
/*
|
||||
this.inputOptions.tailles = aggregate.map((row: CanopyModelBySize) => {
|
||||
if (this.inputOptions.canopies.indexOf(row.voile) === -1) {
|
||||
this.inputOptions.canopies.push(row.voile);
|
||||
}
|
||||
return row;
|
||||
});
|
||||
*/
|
||||
this.inputOptions.canopies = aggregate;
|
||||
});
|
||||
}
|
||||
|
||||
private _loadDropZoneByOaci(): void {
|
||||
const dropzones$ = this._dropZonesService.getAllByOaci();
|
||||
this._dropZoneByOaci = dropzones$.subscribe((aggregate: Array<DropZoneByOaci>) => {
|
||||
/*
|
||||
this.inputOptions.oaci = aggregate.map((row: DropZoneByOaci) => {
|
||||
if (this.inputOptions.dropzones.indexOf(row.lieu) === -1) {
|
||||
this.inputOptions.dropzones.push(row.lieu);
|
||||
}
|
||||
return row;
|
||||
});
|
||||
*/
|
||||
this.inputOptions.dropzones = aggregate;
|
||||
});
|
||||
}
|
||||
|
||||
private _loadJumpByModule(): void {
|
||||
const jumps$ = this._jumpsService.getAllByModule();
|
||||
this._jumpByModule = jumps$.subscribe((aggregate: Array<JumpByModule>) => {
|
||||
this.inputOptions.categories = aggregate;
|
||||
});
|
||||
}
|
||||
|
||||
private _setAllLast(checked: boolean) {
|
||||
this.useLast.all = checked;
|
||||
this.useLast.aeronef = checked;
|
||||
@@ -187,6 +381,106 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
displayAeronefFn(aeronef: AeronefByImat): string {
|
||||
let res = '';
|
||||
if (aeronef) {
|
||||
if (typeof aeronef === 'string') {
|
||||
res = aeronef;
|
||||
} else {
|
||||
res = aeronef.aeronef ? aeronef.aeronef : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
displayImatFn(aeronef: AeronefByImat): string {
|
||||
let res = '';
|
||||
if (aeronef) {
|
||||
if (typeof aeronef === 'string') {
|
||||
res = aeronef;
|
||||
} else {
|
||||
res = aeronef.imat ? aeronef.imat : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
displayLieuFn(dropzone: DropZoneByOaci): string {
|
||||
//return dropzone && dropzone.oaci ? `${dropzone.lieu} - ${dropzone.oaci}` : '';
|
||||
let res = '';
|
||||
if (dropzone) {
|
||||
if (typeof dropzone === 'string') {
|
||||
res = dropzone;
|
||||
} else {
|
||||
res = dropzone.lieu ? dropzone.lieu : '';
|
||||
}
|
||||
}
|
||||
//return dropzone && dropzone.lieu ? dropzone.lieu : '';
|
||||
return res;
|
||||
}
|
||||
|
||||
displayOaciFn(dropzone: DropZoneByOaci): string {
|
||||
//return dropzone && dropzone.oaci ? `${dropzone.lieu} - ${dropzone.oaci}` : '';
|
||||
let res = '';
|
||||
if (dropzone) {
|
||||
if (typeof dropzone === 'string') {
|
||||
res = dropzone;
|
||||
} else {
|
||||
res = dropzone.oaci ? dropzone.oaci : '';
|
||||
}
|
||||
}
|
||||
//return dropzone && dropzone.oaci ? dropzone.oaci : '';
|
||||
return res;
|
||||
}
|
||||
|
||||
displayTailleFn(canopy: CanopyModelBySize): string {
|
||||
let res = '';
|
||||
if (canopy) {
|
||||
if (typeof canopy === 'string') {
|
||||
res = canopy;
|
||||
} else {
|
||||
res = canopy.taille ? canopy.taille.toString() : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
displayVoileFn(canopy: CanopyModelBySize): string {
|
||||
let res = '';
|
||||
if (canopy) {
|
||||
if (typeof canopy === 'string') {
|
||||
res = canopy;
|
||||
} else {
|
||||
res = canopy.voile ? canopy.voile : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
displayCategorieFn(jump: JumpByModule): string {
|
||||
let res = '';
|
||||
if (jump) {
|
||||
if (typeof jump === 'string') {
|
||||
res = jump;
|
||||
} else {
|
||||
res = jump.categorie ? jump.categorie : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
displayModuleFn(jump: JumpByModule): string {
|
||||
let res = '';
|
||||
if (jump) {
|
||||
if (typeof jump === 'string') {
|
||||
res = jump;
|
||||
} else {
|
||||
res = jump.module ? jump.module : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
getErrorMessage(name: string): string {
|
||||
if (this.jumpForm.controls[name].errors !== null) {
|
||||
return 'Ce champ est requis.';
|
||||
@@ -194,6 +488,60 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
return '';
|
||||
}
|
||||
|
||||
onSelAeronef(aeronef: AeronefByImat) {
|
||||
if (aeronef && aeronef.aeronef) {
|
||||
//this.jumpForm.controls['imat'].setValue(aeronef.imat);
|
||||
this.jumpForm.controls['imat'].setValue(aeronef);
|
||||
}
|
||||
}
|
||||
|
||||
onSelImat(aeronef: AeronefByImat) {
|
||||
if (aeronef && aeronef.imat) {
|
||||
//this.jumpForm.controls['aeronef'].setValue(aeronef.aeronef);
|
||||
this.jumpForm.controls['aeronef'].setValue(aeronef);
|
||||
}
|
||||
}
|
||||
|
||||
onSelLieu(dropzone: DropZoneByOaci) {
|
||||
if (dropzone && dropzone.oaci) {
|
||||
//this.jumpForm.controls['oaci'].setValue(dropzone.oaci);
|
||||
this.jumpForm.controls['oaci'].setValue(dropzone);
|
||||
}
|
||||
}
|
||||
|
||||
onSelOaci(dropzone: DropZoneByOaci) {
|
||||
if (dropzone && dropzone.lieu) {
|
||||
//this.jumpForm.controls['lieu'].setValue(dropzone.lieu);
|
||||
this.jumpForm.controls['lieu'].setValue(dropzone);
|
||||
}
|
||||
}
|
||||
|
||||
onSelTaille(canopy: CanopyModelBySize) {
|
||||
if (canopy && canopy.voile) {
|
||||
//this.jumpForm.controls['voile'].setValue(canopy.voile);
|
||||
this.jumpForm.controls['voile'].setValue(canopy);
|
||||
}
|
||||
}
|
||||
|
||||
onSelVoile(canopy: CanopyModelBySize) {
|
||||
if (canopy && canopy.taille) {
|
||||
//this.jumpForm.controls['taille'].setValue(canopy.taille.toString());
|
||||
this.jumpForm.controls['taille'].setValue(canopy);
|
||||
}
|
||||
}
|
||||
|
||||
onSelCategorie(jump: JumpByModule) {
|
||||
if (jump && jump.categorie) {
|
||||
this.jumpForm.controls['module'].setValue(jump);
|
||||
}
|
||||
}
|
||||
|
||||
onSelModule(jump: JumpByModule) {
|
||||
if (jump && jump.module) {
|
||||
this.jumpForm.controls['categorie'].setValue(jump);
|
||||
}
|
||||
}
|
||||
|
||||
removeSautant(index: string): void {
|
||||
this.useLast.groupe = false;
|
||||
this.useLast.all = this._isLastForAllSections();
|
||||
@@ -212,7 +560,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
this.jumpForm.controls['hauteur'].setValue(this.lastJump.hauteur);
|
||||
this.jumpForm.controls['deploiement'].setValue(this.lastJump.deploiement);
|
||||
this.jumpForm.controls['voile'].setValue(this.lastJump.voile);
|
||||
this.jumpForm.controls['taille'].setValue(this.lastJump.taille);
|
||||
this.jumpForm.controls['taille'].setValue(this.lastJump.taille?.toString());
|
||||
this.jumpForm.controls['categorie'].setValue(this.lastJump.categorie);
|
||||
this.jumpForm.controls['module'].setValue(this.lastJump.module);
|
||||
this.jumpForm.controls['participants'].setValue(this.lastJump.participants);
|
||||
@@ -266,7 +614,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
this.jump.sautants = [];
|
||||
}
|
||||
this.jump.numero = this.nextJump;
|
||||
};
|
||||
}
|
||||
|
||||
setLastAeronef(checked: boolean = false) {
|
||||
if (checked) {
|
||||
@@ -363,7 +711,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
if (checked) {
|
||||
this.useLast.voile = true;
|
||||
this.jumpForm.controls['voile'].setValue(this.lastJump.voile);
|
||||
this.jumpForm.controls['taille'].setValue(this.lastJump.taille);
|
||||
this.jumpForm.controls['taille'].setValue(this.lastJump.taille?.toString());
|
||||
} else {
|
||||
this.useLast.voile = false;
|
||||
this.jumpForm.controls['voile'].setValue('');
|
||||
@@ -377,19 +725,23 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
* Création d'un tableau de saut contenant de 1 à n saut en fonction
|
||||
* de la valeur du champ numero du formulaire jumpForm
|
||||
*/
|
||||
let jumps: Array<Jump> = [];
|
||||
let jump: Jump = {} as Jump;
|
||||
let numeros: Array<number> = [];
|
||||
const jumps: Array<Jump> = [];
|
||||
const jump: Jump = {} as Jump;
|
||||
const numeros: Array<number> = [];
|
||||
let date: Date;
|
||||
/* Controle de la validité du champ date et conversion au format ISO */
|
||||
let timestamp: number = Date.parse(this.jumpForm.controls['date'].value);
|
||||
const timestamp: number = Date.parse(this.jumpForm.controls['date'].value);
|
||||
if (isNaN(timestamp) == false) {
|
||||
date = new Date(timestamp);
|
||||
} else {
|
||||
date = new Date();
|
||||
}
|
||||
let value = this.jumpForm.controls['numero'].value;
|
||||
if (typeof value !== 'string') {
|
||||
value = value.toString();
|
||||
}
|
||||
|
||||
if (isNaN(+this.jumpForm.controls['numero'].value)) {
|
||||
if (isNaN(+value) || value.split('+').length == 2) {
|
||||
/*
|
||||
* 'numero' n'est pas un entier , il s'agit donc d'une plage, d'une quantité ou de plusieurs valeur
|
||||
* decomposer la valeur 'numero' et ajouter chaque sauts au tableau retourné par la fenêtre dialog
|
||||
@@ -415,7 +767,7 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
const quantity: string[] = values[0].split('+');
|
||||
if (quantity.length == 2) {
|
||||
/* Ajout de sauts par quantité */
|
||||
const start: number = jumpNum;
|
||||
//const start: number = jumpNum;
|
||||
for (let index = 1; index <= parseInt(quantity[1]); index++) {
|
||||
jumpNum ++;
|
||||
numeros.push(jumpNum);
|
||||
@@ -434,16 +786,16 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
|
||||
/* Valeurs communes aux 1 à n saut(s) à ajouter */
|
||||
jump.date = date.toISOString();
|
||||
jump.lieu = this.jumpForm.controls['lieu'].value;
|
||||
jump.oaci = this.jumpForm.controls['oaci'].value;
|
||||
jump.aeronef = this.jumpForm.controls['aeronef'].value;
|
||||
jump.imat = this.jumpForm.controls['imat'].value;
|
||||
jump.lieu = typeof this.jumpForm.controls['lieu'].value === 'string' ? this.jumpForm.controls['lieu'].value : this.jumpForm.controls['lieu'].value.lieu;
|
||||
jump.oaci = typeof this.jumpForm.controls['oaci'].value === 'string' ? this.jumpForm.controls['oaci'].value : this.jumpForm.controls['oaci'].value.oaci;
|
||||
jump.aeronef = typeof this.jumpForm.controls['aeronef'].value === 'string' ? this.jumpForm.controls['aeronef'].value : this.jumpForm.controls['aeronef'].value.aeronef;
|
||||
jump.imat = typeof this.jumpForm.controls['imat'].value === 'string' ? this.jumpForm.controls['imat'].value : this.jumpForm.controls['imat'].value.imat;
|
||||
jump.voile = typeof this.jumpForm.controls['voile'].value === 'string' ? this.jumpForm.controls['voile'].value : this.jumpForm.controls['voile'].value.voile;
|
||||
jump.taille = typeof this.jumpForm.controls['taille'].value === 'string' ? this.jumpForm.controls['taille'].value : this.jumpForm.controls['taille'].value.taille;
|
||||
jump.categorie = typeof this.jumpForm.controls['categorie'].value === 'string' ? this.jumpForm.controls['categorie'].value : this.jumpForm.controls['categorie'].value.categorie;
|
||||
jump.module = typeof this.jumpForm.controls['module'].value === 'string' ? this.jumpForm.controls['module'].value : this.jumpForm.controls['module'].value.module;
|
||||
jump.hauteur = this.jumpForm.controls['hauteur'].value;
|
||||
jump.deploiement = this.jumpForm.controls['deploiement'].value;
|
||||
jump.voile = this.jumpForm.controls['voile'].value;
|
||||
jump.taille = this.jumpForm.controls['taille'].value;
|
||||
jump.categorie = this.jumpForm.controls['categorie'].value;
|
||||
jump.module = this.jumpForm.controls['module'].value;
|
||||
jump.participants = this.jumpForm.controls['participants'].value;
|
||||
jump.sautants = this.jump.sautants;
|
||||
//jump.sautants = this.jumpForm.controls['sautants'].value;
|
||||
@@ -453,10 +805,9 @@ export class JumpAddDialogComponent implements OnDestroy {
|
||||
//jump.dossier = this.jumpForm.controls['dossier'].value;
|
||||
//jump.video = this.jumpForm.controls['video'].value;
|
||||
|
||||
//console.log(numeros);
|
||||
/* Ajout du saut au tableau à retourner */
|
||||
numeros.forEach((numero: number) => {
|
||||
let item: Jump = {} as Jump;
|
||||
const item: Jump = {} as Jump;
|
||||
Object.assign(item, jump);
|
||||
item.numero = numero;
|
||||
item.dossier = this._computeFilepath();
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
<h2 mat-dialog-title class="bg-accent border-bottom border-light-subtle d-flex mb-3 pt-3 pb-2">
|
||||
<h2 mat-dialog-title class="bg-danger border-bottom border-light-subtle d-flex mb-3 pt-3 pb-2">
|
||||
Supprimer le saut n°{{jump.numero}}<span class="flex-spacer"></span>
|
||||
<mat-icon fontIcon="close" class="pointer" (click)="closeDialog()"></mat-icon>
|
||||
</h2>
|
||||
<mat-dialog-content>
|
||||
<div class="container">
|
||||
<div class="mt-2 mb-4 text-center text-danger border-warning alert alert-danger">
|
||||
<div class="mt-2 mb-4 text-center text-danger border-danger alert alert-danger">
|
||||
<p class="fs-6 m-0">
|
||||
Veuillez confirmer la suppression de ce saut.<br />
|
||||
<span class="fw-semibold">Attention</span>, cette <span class="fw-semibold">action</span> est <span class="fw-semibold">irréversible</span>.
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { Jump } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-delete-dialog',
|
||||
selector: 'app-jump-delete-dialog',
|
||||
templateUrl: 'jump-delete.dialog.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
@@ -0,0 +1,219 @@
|
||||
<h2 mat-dialog-title class="bg-primary border-bottom border-light-subtle d-flex mb-3 pt-3 pb-2">
|
||||
Modifier un saut<span class="flex-spacer"></span>
|
||||
<mat-icon fontIcon="close" class="pointer" (click)="closeDialog()"></mat-icon>
|
||||
</h2>
|
||||
<mat-dialog-content>
|
||||
<div class="container">
|
||||
<form class="row" [formGroup]="jumpForm" (ngSubmit)="submitForm()">
|
||||
<div class="col-xs-12">
|
||||
<mat-form-field appearance="outline" class="mt-2 me-2" color="primary">
|
||||
<mat-label>Numéro du saut</mat-label>
|
||||
<input matInput formControlName="numero">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="mt-2 me-3" color="primary">
|
||||
<mat-label>Choisissez une date</mat-label>
|
||||
<input matInput [matDatepicker]="datepicker" formControlName="date" />
|
||||
<mat-hint>MM/DD/YYYY</mat-hint>
|
||||
<mat-datepicker-toggle matIconSuffix [for]="datepicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #datepicker>
|
||||
<mat-datepicker-actions>
|
||||
<button mat-button matDatepickerCancel>Annuler</button>
|
||||
<button mat-raised-button color="primary" matDatepickerApply>Appliquer</button>
|
||||
</mat-datepicker-actions>
|
||||
</mat-datepicker>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Dropzone</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Lieu</mat-label>
|
||||
<input matInput formControlName="lieu" [matAutocomplete]="autoDropzone">
|
||||
<mat-autocomplete #autoDropzone="matAutocomplete" [displayWith]="displayLieuFn">
|
||||
@for (option of filteredOptions.dropzones | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelLieu(option)">{{option.lieu}} - {{option.oaci}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['lieu'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('lieu')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Oaci</mat-label>
|
||||
<input matInput formControlName="oaci" [matAutocomplete]="autoOaci">
|
||||
<mat-autocomplete #autoOaci="matAutocomplete" [displayWith]="displayOaciFn">
|
||||
@for (option of filteredOptions.oaci | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelOaci(option)">{{option.lieu}} - {{option.oaci}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['oaci'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('oaci')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Aéronef</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Aéronef</mat-label>
|
||||
<input matInput formControlName="aeronef" [matAutocomplete]="autoAeronef">
|
||||
<mat-autocomplete #autoAeronef="matAutocomplete" [displayWith]="displayAeronefFn">
|
||||
@for (option of filteredOptions.aeronefs | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelAeronef(option)">{{option.aeronef}} - {{option.imat}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['aeronef'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('aeronef')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Imatriculation</mat-label>
|
||||
<input matInput formControlName="imat" [matAutocomplete]="autoImat">
|
||||
<mat-autocomplete #autoImat="matAutocomplete" [displayWith]="displayImatFn">
|
||||
@for (option of filteredOptions.imats | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelImat(option)">{{option.aeronef}} - {{option.imat}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['imat'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('imat')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Voile</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Modèle</mat-label>
|
||||
<input matInput formControlName="voile" [matAutocomplete]="autoCanopy">
|
||||
<mat-autocomplete #autoCanopy="matAutocomplete" [displayWith]="displayVoileFn">
|
||||
@for (option of filteredOptions.canopies | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelVoile(option)">{{option.voile}} - {{option.taille}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['voile'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('voile')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Taille en ft<sup>2</sup></mat-label>
|
||||
<input matInput formControlName="taille" [matAutocomplete]="autoTaille">
|
||||
<mat-autocomplete #autoTaille="matAutocomplete" [displayWith]="displayTailleFn">
|
||||
@for (option of filteredOptions.tailles | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelTaille(option)">{{option.voile}} - {{option.taille}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
<span matTextSuffix>ft<sup>2</sup></span>
|
||||
@if (this.jumpForm.controls['taille'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('taille')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Altitude</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Hauteur de largage</mat-label>
|
||||
<input matInput formControlName="hauteur" type="number" min="0" step="1">
|
||||
@if (this.jumpForm.controls['hauteur'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('hauteur')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Hauteur de déploiement</mat-label>
|
||||
<input matInput formControlName="deploiement" type="number" min="0" step="1">
|
||||
@if (this.jumpForm.controls['deploiement'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('deploiement')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Discipline</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Categorie</mat-label>
|
||||
<input matInput formControlName="categorie" [matAutocomplete]="autoCategorie">
|
||||
<mat-autocomplete #autoCategorie="matAutocomplete" [displayWith]="displayCategorieFn">
|
||||
@for (option of filteredOptions.categories | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelCategorie(option)">{{option.categorie}}{{option.categorie && option.module ? ' - ' : ''}}{{option.module}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['categorie'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('categorie')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Module</mat-label>
|
||||
<input matInput formControlName="module" [matAutocomplete]="autoModule">
|
||||
<mat-autocomplete #autoModule="matAutocomplete" [displayWith]="displayModuleFn">
|
||||
@for (option of filteredOptions.modules | async; track option) {
|
||||
<mat-option [value]="option" (onSelectionChange)="onSelModule(option)">{{option.categorie}}{{option.categorie && option.module ? ' - ' : ''}}{{option.module}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
@if (this.jumpForm.controls['module'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('module')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Divers</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Accessoires</mat-label>
|
||||
<input matInput formControlName="accessoires">
|
||||
@if (this.jumpForm.controls['accessoires'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('accessoires')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Zone</mat-label>
|
||||
<input matInput formControlName="zone">
|
||||
@if (this.jumpForm.controls['zone'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('zone')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Groupe</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Sautants</mat-label>
|
||||
<input matInput placeholder="Ajouter un sautant" [formControl]="tagField" (keyup.enter)="addSautant()">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Nombre de participants</mat-label>
|
||||
<input matInput formControlName="participants" type="number" min="0" step="1">
|
||||
@if (this.jumpForm.controls['participants'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('participants')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<div class="mb-2">
|
||||
@for (sautant of jump.sautants; track sautant) {
|
||||
<span class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle pointer me-1" fontIcon="close" (click)="removeSautant(sautant)"></mat-icon>
|
||||
{{ sautant }}
|
||||
</span>
|
||||
}
|
||||
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon>
|
||||
ju_solide
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Programme</h4>
|
||||
<mat-form-field appearance="fill" class="me-2 w-100">
|
||||
<mat-label>Programme</mat-label>
|
||||
<textarea matInput formControlName="programme"></textarea>
|
||||
@if (this.jumpForm.controls['programme'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('programme')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<h4 class="border-bottom border-primary pb-1 mb-3">Vidéo</h4>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Dossier</mat-label>
|
||||
<input matInput formControlName="dossier">
|
||||
@if (this.jumpForm.controls['dossier'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('dossier')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill" class="me-2">
|
||||
<mat-label>Video</mat-label>
|
||||
<input matInput formControlName="video">
|
||||
@if (this.jumpForm.controls['video'].status === 'INVALID') {
|
||||
<mat-error>{{getErrorMessage('video')}}</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<span class="flex-spacer"></span>
|
||||
<button mat-button (click)="closeDialog()" class="me-1">Annuler</button>
|
||||
<button mat-button color="primary" [disabled]="!jumpForm.valid" (click)="submitForm()" cdkFocusInitial>Enregistrer</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,407 @@
|
||||
import { Component, Inject, OnDestroy } from '@angular/core';
|
||||
import { AsyncPipe, DatePipe } from '@angular/common';
|
||||
import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
|
||||
import { AeronefByImat, CanopyModelBySize, DropZoneByOaci, Jump, JumpByModule } from 'src/app/core/models';
|
||||
import { AeronefsService, CanopiesService, DropZonesService, JumpsService } from 'src/app/core/services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-jump-edit-dialog',
|
||||
templateUrl: 'jump-edit.dialog.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
DatePipe,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatAutocompleteModule,
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
MatDatepickerModule,
|
||||
MatDialogModule,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
MatOptionModule
|
||||
]
|
||||
})
|
||||
export class JumpEditDialogComponent implements OnDestroy {
|
||||
private _aeronefByImat: Subscription = new Subscription();
|
||||
private _canopyModelBySize: Subscription = new Subscription();
|
||||
private _dropZoneByOaci: Subscription = new Subscription();
|
||||
private _jumpByModule: Subscription = new Subscription();
|
||||
public tagField = new FormControl<string>('', { nonNullable: true});
|
||||
public jumpForm: FormGroup;
|
||||
public inputOptions: {
|
||||
aeronefs: Array<AeronefByImat>;
|
||||
canopies: Array<CanopyModelBySize>;
|
||||
dropzones: Array<DropZoneByOaci>;
|
||||
categories: Array<JumpByModule>;
|
||||
};
|
||||
public filteredOptions: {
|
||||
aeronefs: Observable<Array<AeronefByImat>>;
|
||||
imats: Observable<Array<AeronefByImat>>;
|
||||
canopies: Observable<Array<CanopyModelBySize>>;
|
||||
tailles: Observable<Array<CanopyModelBySize>>;
|
||||
dropzones: Observable<Array<DropZoneByOaci>>;
|
||||
oaci: Observable<Array<DropZoneByOaci>>;
|
||||
categories: Observable<Array<JumpByModule>>;
|
||||
modules: Observable<Array<JumpByModule>>;
|
||||
};
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<JumpEditDialogComponent>,
|
||||
private fb: FormBuilder,
|
||||
private _aeronefsService: AeronefsService,
|
||||
private _canopiesService: CanopiesService,
|
||||
private _dropZonesService: DropZonesService,
|
||||
private _jumpsService: JumpsService,
|
||||
@Inject(MAT_DIALOG_DATA) public jump: Jump
|
||||
) {
|
||||
this.inputOptions = { aeronefs: [], canopies: [], dropzones: [], categories: [] };
|
||||
const controlsConfig = {
|
||||
slug: [this.jump.slug, Validators.required],
|
||||
date: [this.jump.date, Validators.required],
|
||||
numero: [this.jump.numero, Validators.required],
|
||||
lieu: [this.jump.lieu, Validators.required],
|
||||
oaci: this.jump.oaci,
|
||||
aeronef: [this.jump.aeronef, Validators.required],
|
||||
imat: this.jump.imat,
|
||||
hauteur: [this.jump.hauteur, Validators.required],
|
||||
deploiement: this.jump.deploiement,
|
||||
voile: this.jump.voile,
|
||||
taille: [this.jump.taille?.toString(), Validators.required],
|
||||
categorie: this.jump.categorie,
|
||||
module: this.jump.module,
|
||||
participants: [this.jump.participants, Validators.required],
|
||||
programme: this.jump.programme,
|
||||
accessoires: this.jump.accessoires,
|
||||
zone: this.jump.zone,
|
||||
dossier: this.jump.dossier,
|
||||
video: this.jump.video
|
||||
};
|
||||
this.jumpForm = this.fb.group(controlsConfig);
|
||||
|
||||
this.filteredOptions = {
|
||||
aeronefs: this.jumpForm.controls['aeronef'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const aeronef = typeof value === 'string' ? value : value?.aeronef;
|
||||
return aeronef ? this._filterImat(aeronef as string) : this.inputOptions.aeronefs.slice();
|
||||
}),
|
||||
),
|
||||
imats: this.jumpForm.controls['imat'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const imat = typeof value === 'string' ? value : value?.imat;
|
||||
return imat ? this._filterImat(imat as string) : this.inputOptions.aeronefs.slice();
|
||||
}),
|
||||
),
|
||||
canopies: this.jumpForm.controls['voile'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const voile = typeof value === 'string' ? value : value?.voile;
|
||||
return voile ? this._filterTaille(voile as string) : this.inputOptions.canopies.slice();
|
||||
}),
|
||||
),
|
||||
tailles: this.jumpForm.controls['taille'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const taille = typeof value === 'string' ? value : value?.taille.toString();
|
||||
return taille ? this._filterTaille(taille as string) : this.inputOptions.canopies.slice();
|
||||
}),
|
||||
),
|
||||
dropzones: this.jumpForm.controls['lieu'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const lieu = typeof value === 'string' ? value : value?.lieu;
|
||||
return lieu ? this._filterOaci(lieu as string) : this.inputOptions.dropzones.slice();
|
||||
}),
|
||||
),
|
||||
oaci: this.jumpForm.controls['oaci'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const oaci = typeof value === 'string' ? value : value?.oaci;
|
||||
return oaci ? this._filterOaci(oaci as string) : this.inputOptions.dropzones.slice();
|
||||
}),
|
||||
),
|
||||
categories: this.jumpForm.controls['categorie'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const category = typeof value === 'string' ? value : value?.categorie;
|
||||
return category ? this._filterModule(category as string) : this.inputOptions.categories.slice();
|
||||
}),
|
||||
),
|
||||
modules: this.jumpForm.controls['module'].valueChanges.pipe(
|
||||
startWith(''),
|
||||
map(value => {
|
||||
const module = typeof value === 'string' ? value : value?.module;
|
||||
return module ? this._filterModule(module as string) : this.inputOptions.categories.slice();
|
||||
}),
|
||||
)
|
||||
};
|
||||
this._loadAeronefByImat();
|
||||
this._loadCanopyModelBySize();
|
||||
this._loadDropZoneByOaci();
|
||||
this._loadJumpByModule();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._aeronefByImat.unsubscribe();
|
||||
this._canopyModelBySize.unsubscribe();
|
||||
this._dropZoneByOaci.unsubscribe();
|
||||
this._jumpByModule.unsubscribe();
|
||||
}
|
||||
|
||||
private _filterImat(imat: string): Array<AeronefByImat> {
|
||||
const filterValue = imat.toLowerCase();
|
||||
return this.inputOptions.aeronefs.filter(option => (option.aeronef.toLowerCase().includes(filterValue) || option.imat.toLowerCase().includes(filterValue)));
|
||||
}
|
||||
|
||||
private _filterModule(module: string): Array<JumpByModule> {
|
||||
const filterValue = module.toLowerCase();
|
||||
return this.inputOptions.categories.filter(option => (option.categorie.toLowerCase().includes(filterValue) || option.module.toLowerCase().includes(filterValue)));
|
||||
}
|
||||
|
||||
private _filterOaci(oaci: string): Array<DropZoneByOaci> {
|
||||
const filterValue = oaci.toLowerCase();
|
||||
return this.inputOptions.dropzones.filter(option => (option.lieu.toLowerCase().includes(filterValue) || option.oaci.toLowerCase().includes(filterValue)));
|
||||
}
|
||||
|
||||
private _filterTaille(taille: string): Array<CanopyModelBySize> {
|
||||
const filterValue = taille.toLowerCase();
|
||||
return this.inputOptions.canopies.filter(option => (option.voile.toLowerCase().includes(filterValue) || option.taille.toString().includes(filterValue)));
|
||||
}
|
||||
|
||||
private _loadAeronefByImat(): void {
|
||||
const aeronefs$ = this._aeronefsService.getAllByImat();
|
||||
this._aeronefByImat = aeronefs$.subscribe((aggregate: Array<AeronefByImat>) => {
|
||||
this.inputOptions.aeronefs = aggregate;
|
||||
});
|
||||
}
|
||||
|
||||
private _loadCanopyModelBySize(): void {
|
||||
const canopies$ = this._canopiesService.getAllBySizeByModel();
|
||||
this._canopyModelBySize = canopies$.subscribe((aggregate: Array<CanopyModelBySize>) => {
|
||||
this.inputOptions.canopies = aggregate;
|
||||
});
|
||||
}
|
||||
|
||||
private _loadDropZoneByOaci(): void {
|
||||
const dropzones$ = this._dropZonesService.getAllByOaci();
|
||||
this._dropZoneByOaci = dropzones$.subscribe((aggregate: Array<DropZoneByOaci>) => {
|
||||
this.inputOptions.dropzones = aggregate;
|
||||
});
|
||||
}
|
||||
|
||||
private _loadJumpByModule(): void {
|
||||
const jumps$ = this._jumpsService.getAllByModule();
|
||||
this._jumpByModule = jumps$.subscribe((aggregate: Array<JumpByModule>) => {
|
||||
this.inputOptions.categories = aggregate;
|
||||
});
|
||||
}
|
||||
|
||||
addSautant(): void {
|
||||
const tag = this.tagField.value;
|
||||
if (tag != null && tag.trim() !== '' && this.jump.sautants.indexOf(tag) < 0) {
|
||||
this.jump.sautants.push(tag);
|
||||
}
|
||||
const participants = (this.jump.sautants.length + 1);
|
||||
this.jumpForm.controls['participants'].setValue(participants);
|
||||
this.tagField.reset('');
|
||||
}
|
||||
|
||||
closeDialog(): void {
|
||||
// close the dialog without result
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
displayAeronefFn(aeronef: AeronefByImat): string {
|
||||
let res = '';
|
||||
if (aeronef) {
|
||||
if (typeof aeronef === 'string') {
|
||||
res = aeronef;
|
||||
} else {
|
||||
res = aeronef.aeronef ? aeronef.aeronef : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
displayImatFn(aeronef: AeronefByImat): string {
|
||||
let res = '';
|
||||
if (aeronef) {
|
||||
if (typeof aeronef === 'string') {
|
||||
res = aeronef;
|
||||
} else {
|
||||
res = aeronef.imat ? aeronef.imat : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
displayLieuFn(dropzone: DropZoneByOaci): string {
|
||||
let res = '';
|
||||
if (dropzone) {
|
||||
if (typeof dropzone === 'string') {
|
||||
res = dropzone;
|
||||
} else {
|
||||
res = dropzone.lieu ? dropzone.lieu : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
displayOaciFn(dropzone: DropZoneByOaci): string {
|
||||
let res = '';
|
||||
if (dropzone) {
|
||||
if (typeof dropzone === 'string') {
|
||||
res = dropzone;
|
||||
} else {
|
||||
res = dropzone.oaci ? dropzone.oaci : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
displayTailleFn(canopy: CanopyModelBySize): string {
|
||||
let res = '';
|
||||
if (canopy) {
|
||||
if (typeof canopy === 'string') {
|
||||
res = canopy;
|
||||
} else {
|
||||
res = canopy.taille ? canopy.taille.toString() : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
displayVoileFn(canopy: CanopyModelBySize): string {
|
||||
let res = '';
|
||||
if (canopy) {
|
||||
if (typeof canopy === 'string') {
|
||||
res = canopy;
|
||||
} else {
|
||||
res = canopy.voile ? canopy.voile : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
displayCategorieFn(jump: JumpByModule): string {
|
||||
let res = '';
|
||||
if (jump) {
|
||||
if (typeof jump === 'string') {
|
||||
res = jump;
|
||||
} else {
|
||||
res = jump.categorie ? jump.categorie : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
displayModuleFn(jump: JumpByModule): string {
|
||||
let res = '';
|
||||
if (jump) {
|
||||
if (typeof jump === 'string') {
|
||||
res = jump;
|
||||
} else {
|
||||
res = jump.module ? jump.module : '';
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
getErrorMessage(name: string): string {
|
||||
if (this.jumpForm.controls[name].errors !== null) {
|
||||
return `Ce champ est requis.`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
onSelAeronef(aeronef: AeronefByImat) {
|
||||
if (aeronef && aeronef.aeronef) {
|
||||
this.jumpForm.controls['imat'].setValue(aeronef);
|
||||
}
|
||||
}
|
||||
|
||||
onSelImat(aeronef: AeronefByImat) {
|
||||
if (aeronef && aeronef.imat) {
|
||||
this.jumpForm.controls['aeronef'].setValue(aeronef);
|
||||
}
|
||||
}
|
||||
|
||||
onSelLieu(dropzone: DropZoneByOaci) {
|
||||
if (dropzone && dropzone.oaci) {
|
||||
this.jumpForm.controls['oaci'].setValue(dropzone);
|
||||
}
|
||||
}
|
||||
|
||||
onSelOaci(dropzone: DropZoneByOaci) {
|
||||
if (dropzone && dropzone.lieu) {
|
||||
this.jumpForm.controls['lieu'].setValue(dropzone);
|
||||
}
|
||||
}
|
||||
|
||||
onSelTaille(canopy: CanopyModelBySize) {
|
||||
if (canopy && canopy.voile) {
|
||||
this.jumpForm.controls['voile'].setValue(canopy);
|
||||
}
|
||||
}
|
||||
|
||||
onSelVoile(canopy: CanopyModelBySize) {
|
||||
if (canopy && canopy.taille) {
|
||||
this.jumpForm.controls['taille'].setValue(canopy);
|
||||
}
|
||||
}
|
||||
|
||||
onSelCategorie(jump: JumpByModule) {
|
||||
if (jump && jump.categorie) {
|
||||
this.jumpForm.controls['module'].setValue(jump);
|
||||
}
|
||||
}
|
||||
|
||||
onSelModule(jump: JumpByModule) {
|
||||
if (jump && jump.module) {
|
||||
this.jumpForm.controls['categorie'].setValue(jump);
|
||||
}
|
||||
}
|
||||
|
||||
removeSautant(index: string): void {
|
||||
this.jump.sautants = this.jump.sautants.filter((sautant) => sautant !== index);
|
||||
const participants = (this.jump.sautants.length + 1);
|
||||
this.jumpForm.controls['participants'].setValue(participants);
|
||||
}
|
||||
|
||||
submitForm(): void {
|
||||
// update the model
|
||||
this.updateJump(this.jumpForm.value);
|
||||
this.jump.lieu = typeof this.jumpForm.controls['lieu'].value === 'string' ? this.jumpForm.controls['lieu'].value : this.jumpForm.controls['lieu'].value.lieu;
|
||||
this.jump.oaci = typeof this.jumpForm.controls['oaci'].value === 'string' ? this.jumpForm.controls['oaci'].value : this.jumpForm.controls['oaci'].value.oaci;
|
||||
this.jump.aeronef = typeof this.jumpForm.controls['aeronef'].value === 'string' ? this.jumpForm.controls['aeronef'].value : this.jumpForm.controls['aeronef'].value.aeronef;
|
||||
this.jump.imat = typeof this.jumpForm.controls['imat'].value === 'string' ? this.jumpForm.controls['imat'].value : this.jumpForm.controls['imat'].value.imat;
|
||||
this.jump.voile = typeof this.jumpForm.controls['voile'].value === 'string' ? this.jumpForm.controls['voile'].value : this.jumpForm.controls['voile'].value.voile;
|
||||
this.jump.taille = typeof this.jumpForm.controls['taille'].value === 'string' ? this.jumpForm.controls['taille'].value : this.jumpForm.controls['taille'].value.taille;
|
||||
this.jump.categorie = typeof this.jumpForm.controls['categorie'].value === 'string' ? this.jumpForm.controls['categorie'].value : this.jumpForm.controls['categorie'].value.categorie;
|
||||
this.jump.module = typeof this.jumpForm.controls['module'].value === 'string' ? this.jumpForm.controls['module'].value : this.jumpForm.controls['module'].value.module;
|
||||
// close the dialog with result
|
||||
this.dialogRef.close(this.jump);
|
||||
}
|
||||
|
||||
updateJump(values: NonNullable<unknown>): void {
|
||||
Object.assign(this.jump, values);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<h2 mat-dialog-title class="bg-cyan border-bottom border-light-subtle d-flex mb-3 pt-3 pb-2">
|
||||
Saut n°{{jump.numero}}<span class="flex-spacer"></span>
|
||||
<mat-icon fontIcon="close" class="pointer" (click)="closeDialog()"></mat-icon>
|
||||
</h2>
|
||||
<mat-dialog-content>
|
||||
<div class="container">
|
||||
<h5 class="border-bottom text-light-info border-light-subtle pb-2 mb-3">Le {{ jump.date | date: 'dd/MM/yyyy' }} à {{ jump.lieu }} :</h5>
|
||||
<div class="row mb-3">
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<dl class="dl-horizontal">
|
||||
<dt class="text-cyan fw-normal">Lieu:</dt>
|
||||
<dd>{{jump.lieu}}</dd>
|
||||
<dt class="text-cyan fw-normal">Oaci:</dt>
|
||||
<dd>{{jump.oaci}}</dd>
|
||||
<dt class="text-cyan fw-normal">Aeronef:</dt>
|
||||
<dd>{{jump.aeronef}}</dd>
|
||||
<dt class="text-cyan fw-normal">Imat:</dt>
|
||||
<dd>{{jump.imat}}</dd>
|
||||
<dt class="text-cyan fw-normal">Voile:</dt>
|
||||
<dd>{{jump.voile}}</dd>
|
||||
<dt class="text-cyan fw-normal">Taille:</dt>
|
||||
<dd>{{jump.taille}} ft<sup>2</sup></dd>
|
||||
<dt class="text-cyan fw-normal">Participants:</dt>
|
||||
<dd>{{jump.participants}}</dd>
|
||||
<dt class="text-cyan fw-normal">Sautants:</dt>
|
||||
<dd>
|
||||
@for (sautant of jump.sautants; track sautant) {
|
||||
<span class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
|
||||
{{ sautant }}
|
||||
</span>
|
||||
}
|
||||
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
|
||||
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
|
||||
ju_solide
|
||||
</span>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<dl class="dl-horizontal">
|
||||
<dt class="text-cyan fw-normal">Hauteur:</dt>
|
||||
<dd>{{jump.hauteur}} m</dd>
|
||||
<dt class="text-cyan fw-normal">Deploiement:</dt>
|
||||
<dd>{{jump.deploiement}} m</dd>
|
||||
<dt class="text-cyan fw-normal">Categorie:</dt>
|
||||
<dd>{{jump.categorie}}</dd>
|
||||
<dt class="text-cyan fw-normal">Module:</dt>
|
||||
<dd>{{jump.module}}</dd>
|
||||
<dt class="text-cyan fw-normal">Accessoires:</dt>
|
||||
<dd>{{jump.accessoires}}</dd>
|
||||
<dt class="text-cyan fw-normal">Zone:</dt>
|
||||
<dd>{{jump.zone}}</dd>
|
||||
<dt class="text-cyan fw-normal">Dossier media:</dt>
|
||||
<dd>{{jump.dossier}}</dd>
|
||||
<dt class="text-cyan fw-normal">Video:</dt>
|
||||
<dd>{{jump.video}}</dd>
|
||||
<dt class="text-cyan fw-normal">Programme:</dt>
|
||||
<dd>{{jump.programme}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<span class="flex-spacer"></span>
|
||||
<button mat-button (click)="closeDialog()" class="me-1">Fermer</button>
|
||||
</mat-dialog-actions>
|
||||
+8
-5
@@ -1,5 +1,5 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { DatePipe, NgFor } from '@angular/common';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
@@ -8,13 +8,16 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { Jump } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-view-dialog',
|
||||
selector: 'app-jump-view-dialog',
|
||||
templateUrl: 'jump-view.dialog.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DatePipe, NgFor,
|
||||
MatButtonModule, MatDialogModule, MatDividerModule, MatIconModule
|
||||
]
|
||||
DatePipe,
|
||||
MatButtonModule,
|
||||
MatDialogModule,
|
||||
MatDividerModule,
|
||||
MatIconModule
|
||||
]
|
||||
})
|
||||
export class JumpViewDialogComponent {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { LogbookComponent } from './logbook.component';
|
||||
import { AuthGuard, AuthResolver } from 'src/app/core/services';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: LogbookComponent,
|
||||
canActivate: [AuthGuard],
|
||||
resolve: {
|
||||
isAuthenticated: AuthResolver
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class LogbookRoutingModule { }
|
||||
@@ -1,37 +1,107 @@
|
||||
<div class="content">
|
||||
<mat-card>
|
||||
<mat-card-header class="bg-accent rounded-top py-3 px-4">
|
||||
<mat-card-title>
|
||||
{{ title }} <span class="fs-5" [hidden]="!lastJump.numero">({{ lastJump.numero }})</span>
|
||||
</mat-card-title>
|
||||
<mat-card-subtitle>
|
||||
<span [hidden]="!lastJump.numero" class="me-2 fst-italic">
|
||||
Dernier saut enregistré : # {{ lastJump.numero }} le {{ lastJump.date | date: 'dd/MM/yyyy' }} à {{ lastJump.lieu }}
|
||||
<div @flyInOut>
|
||||
<div class="content">
|
||||
<div class="d-flex">
|
||||
<div class="flex-fill">
|
||||
<h1 class="mb-1">{{title}}</h1>
|
||||
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
|
||||
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
|
||||
</span>
|
||||
</mat-card-subtitle>
|
||||
</div>
|
||||
<span class="flex-spacer"></span>
|
||||
<button mat-icon-button (click)="openAddDialog()" aria-label="Ajouter">
|
||||
<mat-icon fontIcon="add_notes"></mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuJump" aria-label="Menu Jump">
|
||||
<mat-icon fontIcon="more_vert"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuJump="matMenu">
|
||||
<button mat-menu-item (click)="openAddDialog()">
|
||||
<div class="align-self-center me-2">
|
||||
<button mat-icon-button (click)="openAddDialog()" aria-label="Ajouter">
|
||||
<mat-icon fontIcon="add_notes"></mat-icon>
|
||||
<span>Ajouter un saut</span>
|
||||
</button>
|
||||
<ng-container *ngFor="let menuitem of menuItems.getMenuLogbook()">
|
||||
<button mat-menu-item [routerLink]="['/', menuitem.state]" *ngIf="menuitem.type === 'link'">
|
||||
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuJump" aria-label="Menu Jump">
|
||||
<mat-icon fontIcon="more_vert"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuJump="matMenu">
|
||||
<button mat-menu-item (click)="openAddDialog()">
|
||||
<mat-icon fontIcon="add_notes"></mat-icon>
|
||||
<span>Ajouter un saut</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
</mat-menu>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="my-4">
|
||||
<huapp-jump-table #jumpTable [config]="listConfig" [isUser]="isUser" [showAdd]="canModify" [title]="title">
|
||||
</huapp-jump-table>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@for (menuitem of menuItems.getMenuLogbook(); track menuitem) {
|
||||
@if (menuitem.type === 'link') {
|
||||
<button mat-menu-item [routerLink]="['/', menuitem.state]">
|
||||
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
@if (jumpsToHundred() <= 20) {
|
||||
<div class="d-flex pb-2 pe-2">
|
||||
<span class="flex-spacer"></span>
|
||||
<mat-icon fontIcon="warning" color="teal"></mat-icon>
|
||||
<div class="text-end text-teal align-self-center ms-2">
|
||||
{{ lastJump.numero + jumpsToHundred() }}<sup>ème</sup> à venir dans {{ jumpsToHundred() }}
|
||||
{{ jumpsToHundred() > 1 ? 'sauts' : 'saut' }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<mat-card>
|
||||
<mat-card-header class="p-0">
|
||||
<mat-expansion-panel class="w-100">
|
||||
<mat-expansion-panel-header class="bg-navy-light">
|
||||
<mat-panel-title>Filtrer les sauts</mat-panel-title>
|
||||
@if (jumpsCount < lastJump.numero) {
|
||||
<mat-panel-description>
|
||||
{{ jumpsCount }} {{ jumpsCount > 1 ? 'sauts' : 'saut' }} sur {{ lastJump.numero }} correspondent à la recherche
|
||||
</mat-panel-description>
|
||||
}
|
||||
</mat-expansion-panel-header>
|
||||
<div class="d-flex mt-3 mb-0">
|
||||
<div class="flex-fill">
|
||||
<div class="label-container d-flex justify-content-between mx-2">
|
||||
<label for="yearRange">Année du saut</label>
|
||||
<label for="yearRange">de <span class="fw-bold">{{listConfig.filters.yearRangeStart}}</span> à <span class="fw-bold">{{listConfig.filters.yearRangeEnd}}</span></label>
|
||||
</div>
|
||||
<mat-slider [min]="rangesMinMax.year.start" [max]="rangesMinMax.year.end" step="1" discrete>
|
||||
<input id="yearRange" matSliderStartThumb [(ngModel)]="listConfig.filters.yearRangeStart">
|
||||
<input matSliderEndThumb [(ngModel)]="listConfig.filters.yearRangeEnd">
|
||||
</mat-slider>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<div class="label-container d-flex justify-content-between mx-2">
|
||||
<label for="numeroRange">Numéro du saut</label>
|
||||
<label for="numeroRange">de <span class="fw-bold">{{listConfig.filters.numeroRangeStart}}</span> à <span class="fw-bold">{{listConfig.filters.numeroRangeEnd}}</span></label>
|
||||
</div>
|
||||
<mat-slider [min]="rangesMinMax.numero.start" [max]="rangesMinMax.numero.end" step="1" discrete>
|
||||
<input id="numeroRange" matSliderStartThumb [(ngModel)]="listConfig.filters.numeroRangeStart">
|
||||
<input matSliderEndThumb [(ngModel)]="listConfig.filters.numeroRangeEnd">
|
||||
</mat-slider>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<div class="label-container d-flex justify-content-between mx-2">
|
||||
<label for="tailleRange">Taille de la voile</label>
|
||||
<label for="tailleRange">de <span class="fw-bold">{{listConfig.filters.tailleRangeStart}}</span> ft<sup>2</sup> à <span class="fw-bold">{{listConfig.filters.tailleRangeEnd}}</span> ft<sup>2</sup></label>
|
||||
</div>
|
||||
<mat-slider [min]="rangesMinMax.taille.start" [max]="rangesMinMax.taille.end" step="1" discrete>
|
||||
<input id="tailleRange" matSliderStartThumb [(ngModel)]="listConfig.filters.tailleRangeStart">
|
||||
<input matSliderEndThumb [(ngModel)]="listConfig.filters.tailleRangeEnd">
|
||||
</mat-slider>
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<div class="label-container d-flex justify-content-between mx-2">
|
||||
<label for="participantsRange">Taille du groupe</label>
|
||||
<label for="participantsRange">de <span class="fw-bold">{{listConfig.filters.participantsRangeStart}}</span> à <span class="fw-bold">{{listConfig.filters.participantsRangeEnd}}</span></label>
|
||||
</div>
|
||||
<mat-slider [min]="rangesMinMax.participants.start" [max]="rangesMinMax.participants.end" step="1" discrete>
|
||||
<input id="participantsRange" matSliderStartThumb [(ngModel)]="listConfig.filters.participantsRangeStart">
|
||||
<input matSliderEndThumb [(ngModel)]="listConfig.filters.participantsRangeEnd">
|
||||
</mat-slider>
|
||||
</div>
|
||||
<span class="flex-spacer"></span>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-2">
|
||||
<app-jump-table (countChange)="setTotalResults($event)" #jumpTable [config]="listConfig" [isUser]="isUser" [showAdd]="canModify" [title]="title">
|
||||
</app-jump-table>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
.mat-expansion-panel {
|
||||
border-bottom-right-radius: initial;
|
||||
border-bottom-left-radius: initial;
|
||||
}
|
||||
.mat-mdc-slider {
|
||||
max-width: 300px;
|
||||
width: 100%;
|
||||
}
|
||||
.label-container {
|
||||
max-width: 284px;
|
||||
}
|
||||
@@ -8,8 +8,8 @@ describe('LogbookComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [LogbookComponent]
|
||||
});
|
||||
imports: [LogbookComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(LogbookComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, AfterContentChecked, OnInit, OnDestroy } from '@angular/core';
|
||||
import { trigger, state, style, animate, transition } from '@angular/animations';
|
||||
import { Component, AfterContentChecked, OnInit, OnDestroy, computed, signal, Signal } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatSliderModule } from '@angular/material/slider';
|
||||
import { MatSnackBar, MatSnackBarConfig, MatSnackBarHorizontalPosition, MatSnackBarModule, MatSnackBarVerticalPosition } from '@angular/material/snack-bar';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { MenuItems, JumpAddDialogComponent, JumpTableComponent } from 'src/app/components/shared';
|
||||
import { Errors, Jump, JumpListConfig, User } from 'src/app/core/models';
|
||||
import { JumpAddDialogComponent } from 'src/app/components/logbook/dialogs';
|
||||
import { MenuItems, JumpTableComponent } from 'src/app/components/shared';
|
||||
import { Errors, Jump, JumpListConfig, Range, User } from 'src/app/core/models';
|
||||
import { JumpsService, JumpTableService, UserService } from 'src/app/core/services';
|
||||
import data from 'src/jumps.json';
|
||||
//import data from 'src/jumps_02.json';
|
||||
@@ -19,20 +26,35 @@ import data from 'src/jumps.json';
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule, RouterModule,
|
||||
MatButtonModule, MatCardModule, MatIconModule,
|
||||
MatMenuModule, MatDialogModule, MatSnackBarModule,
|
||||
CommonModule, RouterModule, FormsModule,
|
||||
MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule,
|
||||
MatIconModule, MatInputModule, MatMenuModule, MatDialogModule,
|
||||
MatSliderModule, MatSnackBarModule,
|
||||
JumpTableComponent
|
||||
],
|
||||
selector: 'huapp-logbook',
|
||||
selector: 'app-logbook',
|
||||
templateUrl: './logbook.component.html',
|
||||
styleUrls: ['./logbook.component.scss']
|
||||
styleUrl: './logbook.component.scss',
|
||||
animations: [
|
||||
trigger('flyInOut', [
|
||||
state('in', style({ transform: 'translateY(0)' })),
|
||||
transition('void => *', [
|
||||
style({ transform: 'translateY(-100%)' }),
|
||||
animate(200)
|
||||
]),
|
||||
transition('* => void', [
|
||||
style({ transform: 'translateY(100%)' }),
|
||||
animate(200)
|
||||
])
|
||||
])
|
||||
]
|
||||
})
|
||||
export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked {
|
||||
private _currentUser: Subscription = new Subscription();
|
||||
private _data: Subscription = new Subscription();
|
||||
private _lastjump: Subscription = new Subscription();
|
||||
private _lastjump$: Observable<Jump> = new Observable();
|
||||
public jumpsCount = 0;
|
||||
public errors!: Errors;
|
||||
public title = 'Carnet de sauts';
|
||||
public jump: Jump = {} as Jump;
|
||||
@@ -45,6 +67,21 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
public isSubmitting = false;
|
||||
public tableRefresh = false;
|
||||
public jumpRefresh = false;
|
||||
public jumpsSinceHundred: Signal<number> = signal(0);
|
||||
public jumpsToHundred: Signal<number> = signal(100);
|
||||
public rangesMinMax: {
|
||||
numero: Range;
|
||||
taille: Range;
|
||||
participants: Range;
|
||||
hauteur: Range;
|
||||
year: Range;
|
||||
} = {
|
||||
numero: {start: 1, end: 100000},
|
||||
taille: {start: 1, end: 400},
|
||||
participants: {start: 1, end: 200},
|
||||
hauteur: {start: 0, end: 10000},
|
||||
year: {start: 1970, end: 2020}
|
||||
};
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@@ -59,23 +96,67 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
|
||||
ngOnInit() {
|
||||
//this.importJumps();
|
||||
const data$: Observable<any> = this.route.data;
|
||||
this._data = data$.subscribe((data: { isAuthenticated: boolean }) => {
|
||||
const data$: Observable<{isAuthenticated: boolean, lastjump: Jump}> = this.route.data as Observable<{isAuthenticated: boolean, lastjump: Jump}>;
|
||||
this._data = data$.subscribe((data: {isAuthenticated: boolean, lastjump: Jump}) => {
|
||||
this.isAuthenticated = data.isAuthenticated;
|
||||
if (!this.isAuthenticated) {
|
||||
this.router.navigateByUrl('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
const currentUser$: Observable<User> = this._userService.currentUser;
|
||||
this._currentUser = currentUser$.subscribe((userData: User) => {
|
||||
this.currentUser = userData;
|
||||
this.canModify = this.currentUser.role === 'Admin';
|
||||
});
|
||||
|
||||
const currentYear: number = new Date().getFullYear();
|
||||
this.lastJump = data['lastjump'];
|
||||
this.rangesMinMax = {
|
||||
numero: {start: 1, end: data.lastjump.numero},
|
||||
taille: {start: data.lastjump.taille!, end: 230},
|
||||
participants: {start: 1, end: 20},
|
||||
hauteur: {start: 1000, end: 8000},
|
||||
year: {start: 2018, end: currentYear}
|
||||
}
|
||||
this.listConfig.filters.numeroRangeStart = this.rangesMinMax.numero.start;
|
||||
this.listConfig.filters.numeroRangeEnd = this.rangesMinMax.numero.end;
|
||||
this.listConfig.filters.tailleRangeStart = this.rangesMinMax.taille.start;
|
||||
this.listConfig.filters.tailleRangeEnd = this.rangesMinMax.taille.end;
|
||||
this.listConfig.filters.participantsRangeStart = this.rangesMinMax.participants.start;
|
||||
this.listConfig.filters.participantsRangeEnd = (this.rangesMinMax.participants.end / 2);
|
||||
this.listConfig.filters.hauteurRangeStart = this.rangesMinMax.hauteur.start;
|
||||
this.listConfig.filters.hauteurRangeEnd = this.rangesMinMax.hauteur.end;
|
||||
this.listConfig.filters.yearRangeStart = this.rangesMinMax.year.start;
|
||||
this.listConfig.filters.yearRangeEnd = this.rangesMinMax.year.end;
|
||||
this.jumpsSinceHundred = computed(() => (this.lastJump.numero % 100));
|
||||
this.jumpsToHundred = computed(() => (100 - this.jumpsSinceHundred()));
|
||||
/*
|
||||
this._lastjump$ = this._jumpsService.getLastJump();
|
||||
this._lastjump = this._lastjump$.subscribe((jump) => {
|
||||
const currentYear: number = new Date().getFullYear();
|
||||
this.lastJump = jump;
|
||||
this.rangesMinMax = {
|
||||
numero: {start: 1, end: jump.numero},
|
||||
taille: {start: jump.taille!, end: 230},
|
||||
participants: {start: 1, end: 20},
|
||||
hauteur: {start: 1000, end: 8000},
|
||||
year: {start: 2018, end: currentYear}
|
||||
}
|
||||
this.listConfig.filters.numeroRangeStart = this.rangesMinMax.numero.start;
|
||||
this.listConfig.filters.numeroRangeEnd = this.rangesMinMax.numero.end;
|
||||
this.listConfig.filters.tailleRangeStart = this.rangesMinMax.taille.start;
|
||||
this.listConfig.filters.tailleRangeEnd = this.rangesMinMax.taille.end;
|
||||
this.listConfig.filters.participantsRangeStart = this.rangesMinMax.participants.start;
|
||||
this.listConfig.filters.participantsRangeEnd = (this.rangesMinMax.participants.end / 2);
|
||||
this.listConfig.filters.hauteurRangeStart = this.rangesMinMax.hauteur.start;
|
||||
this.listConfig.filters.hauteurRangeEnd = this.rangesMinMax.hauteur.end;
|
||||
this.listConfig.filters.yearRangeStart = this.rangesMinMax.year.start;
|
||||
this.listConfig.filters.yearRangeEnd = this.rangesMinMax.year.end;
|
||||
this.jumpsSinceHundred = computed(() => (this.lastJump.numero % 100));
|
||||
this.jumpsToHundred = computed(() => (100 - this.jumpsSinceHundred()));
|
||||
});
|
||||
*/
|
||||
});
|
||||
}
|
||||
|
||||
@@ -107,7 +188,7 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
console.log(`Le saut n°${jump.numero} a été ajouté !`, 'OK');
|
||||
},
|
||||
error: (err) => {
|
||||
console.log(`Le saut n\'a pas été ajouté !`, 'Error', err);
|
||||
console.log(`Le saut n'a pas été ajouté !`, 'Error', err);
|
||||
this.errors = err;
|
||||
}
|
||||
});
|
||||
@@ -134,6 +215,10 @@ export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked
|
||||
});
|
||||
}
|
||||
|
||||
setTotalResults(value: number): void {
|
||||
this.jumpsCount = value;
|
||||
}
|
||||
|
||||
private _onEntry(jumps: Array<Jump>): void {
|
||||
const max: number = jumps.length;
|
||||
let refresh: boolean = false;
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AuthResolver } from 'src/app/core/services';
|
||||
import { LogbookComponent } from './logbook.component';
|
||||
import { LogbookRoutingModule } from './logbook-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
LogbookRoutingModule,
|
||||
LogbookComponent
|
||||
],
|
||||
exports: [],
|
||||
providers: [AuthResolver]
|
||||
})
|
||||
export class LogbookModule { }
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Injectable, } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { Profile } from 'src/app/core/models';
|
||||
import { ProfilesService } from 'src/app/core/services';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class ProfileResolver implements Resolve<Profile> {
|
||||
constructor(
|
||||
private profilesService: ProfilesService,
|
||||
private router: Router
|
||||
) { }
|
||||
|
||||
resolve(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Observable<any> {
|
||||
|
||||
return this.profilesService.get(route.params['username'])
|
||||
.pipe(catchError(() => this.router.navigateByUrl('/')));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { ProfileResolver } from './profile-resolver.service';
|
||||
import { ProfileComponent } from './profile.component';
|
||||
import { AuthGuard } from 'src/app/core/services';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: ':username',
|
||||
component: ProfileComponent,
|
||||
canActivate: [AuthGuard],
|
||||
resolve: {
|
||||
profile: ProfileResolver
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class ProfileRoutingModule { }
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
/* Profile */
|
||||
@use 'variable' as var;
|
||||
|
||||
.banner {
|
||||
background-color: var.$sidebar-footer;
|
||||
padding: 1.6rem 0 1.3rem 0;
|
||||
.user-img {
|
||||
width: var.$user-img-size;
|
||||
height: var.$user-img-size;
|
||||
border-radius: var.$user-img-size;
|
||||
}
|
||||
h4 {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
p {
|
||||
margin: 0 auto .5rem;
|
||||
color: var.$muted;
|
||||
max-width: 450px;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
.mat-mdc-card + .mat-mdc-card {
|
||||
margin-top: 10px;
|
||||
}
|
||||
@@ -8,8 +8,8 @@ describe('ProfileComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProfileComponent]
|
||||
});
|
||||
imports: [ProfileComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(ProfileComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -1,23 +1,54 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { Errors, User } from 'src/app/core/models';
|
||||
import { UserService } from 'src/app/core/services';
|
||||
import { ListErrorsComponent } from 'src/app/components/shared';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [],
|
||||
selector: 'huapp-profile',
|
||||
imports: [
|
||||
RouterLink,
|
||||
MatButtonModule, MatCardModule, MatDividerModule, MatIconModule,
|
||||
ListErrorsComponent
|
||||
],
|
||||
selector: 'app-profile',
|
||||
templateUrl: './profile.component.html',
|
||||
styleUrls: ['./profile.component.scss']
|
||||
styleUrl: './profile.component.scss'
|
||||
})
|
||||
export class ProfileComponent implements OnInit, OnDestroy {
|
||||
private _user: Subscription = new Subscription();
|
||||
title = 'Mon compte';
|
||||
bannerTitle = '';
|
||||
user: Partial<User> = {} as Partial<User>;
|
||||
errors: Errors = { errors: {} };
|
||||
btnSettingsTitle = 'Paramètres';
|
||||
btnPasswordTitle = 'Changer le mot de passe';
|
||||
|
||||
constructor() { }
|
||||
constructor(
|
||||
private titleService: Title,
|
||||
private userService: UserService
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
//
|
||||
this.titleService.setTitle(`Head Up - ${this.title}`);
|
||||
Object.assign(this.user, this.userService.getCurrentUser());
|
||||
if (this.user.username) {
|
||||
this.bannerTitle = `@${this.user.username}`;
|
||||
} else {
|
||||
this.bannerTitle = `${this.user.firstname} ${this.user.lastname}`;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
//this._scavenger.unsubscribe();
|
||||
this._user.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
|
||||
import { ProfileComponent } from './profile.component';
|
||||
import { ProfileResolver } from './profile-resolver.service';
|
||||
import { ProfileRoutingModule } from './profile-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
ProfileRoutingModule,
|
||||
ProfileComponent
|
||||
],
|
||||
providers: [
|
||||
ProfileResolver
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class ProfileModule { }
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Injectable, } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { Question } from 'src/app/core/models';
|
||||
import { QCMService } from 'src/app/core/services';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class QCMResolver implements Resolve<Question> {
|
||||
constructor(
|
||||
private _qcmService: QCMService,
|
||||
private router: Router
|
||||
) { }
|
||||
|
||||
resolve(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot
|
||||
): Observable<any> {
|
||||
|
||||
return this._qcmService.get(route.params['type'])
|
||||
.pipe(catchError(() => this.router.navigateByUrl('/dashboard')));
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { QCMComponent } from './qcm.component';
|
||||
import { QCMResolver } from './qcm-resolver.service';
|
||||
import { AuthGuard } from 'src/app/core/services';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: ':type',
|
||||
component: QCMComponent,
|
||||
canActivate: [AuthGuard],
|
||||
resolve: {
|
||||
qcm: QCMResolver
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class QCMRoutingModule { }
|
||||
@@ -1,5 +1,60 @@
|
||||
<div class="content">
|
||||
<h1>{{title}}</h1>
|
||||
<hr />
|
||||
<p>qcm works!</p>
|
||||
<div class="d-flex">
|
||||
<div class="flex-fill">
|
||||
<h1 class="mb-1">
|
||||
{{ title }}
|
||||
<span class="ms-2 text-muted fs-4">{{ questionsCount }} {{ questionsCount > 1 ? 'questions' : 'question' }}</span>
|
||||
</h1>
|
||||
<span class="me-2 fs-6">{{ subtitle }}</span>
|
||||
</div>
|
||||
<span class="flex-spacer"></span>
|
||||
<div class="align-self-center me-2">
|
||||
<button mat-icon-button [matMenuTriggerFor]="menuAeronef" aria-label="Menu Aeronef">
|
||||
<mat-icon fontIcon="more_vert"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuAeronef="matMenu">
|
||||
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
|
||||
<button mat-menu-item>
|
||||
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<app-list-errors [errors]="errors"></app-list-errors>
|
||||
<mat-card [hidden]="!questions.length">
|
||||
<mat-card-content class="mt-4">
|
||||
<form [formGroup]="qcmForm">
|
||||
@for (category of qcm.categories; track category; let index = $index) {
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel class="mb-3" [expanded]="expandedIndex === index" (opened)="setExpandedIndex(index)">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title class="fs-4 d-flex align-items-center justify-content-between">
|
||||
<span><span class="text-navy-light">{{ category.num }} - </span>{{ category.name }}</span>
|
||||
</mat-panel-title>
|
||||
<mat-panel-description>
|
||||
{{ category.questions.length }} {{ category.questions.length > 1 ? 'questions' : 'question' }}
|
||||
- 0 réponse
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<div class="mb-3 ms-4" formArrayName="questions">
|
||||
@for (question of category.questions; track question; let index = $index) {
|
||||
<div class="mb-3" [formGroupName]="index">
|
||||
<label class="fs-5 fw-medium mb-2" for="radio-group-{{ category.num }}-{{ question.num }}">{{ question.num }} - {{ question.libelle }}</label>
|
||||
<mat-radio-group id="radio-group-{{ category.num }}-{{ question.num }}" class="d-flex flex-column" (change)="onChange()">
|
||||
@for (choice of question.choices; track choice) {
|
||||
<mat-radio-button class="example-radio-button" [value]="choice.index" [color]="choice.correct ? 'primary' : 'warn'">{{ choice.libelle }}</mat-radio-button>
|
||||
}
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
}
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
@@ -1,42 +1,91 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormControl, FormGroup, FormBuilder, FormArray, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatRadioModule } from '@angular/material/radio';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { Errors, QCM, User } from 'src/app/core/models';
|
||||
import { QCMService, UserService } from 'src/app/core/services';
|
||||
import { MenuItems } from 'src/app/components/shared';
|
||||
import { ListErrorsComponent, } from 'src/app/components/shared';
|
||||
import { Errors, Qcm, User } from 'src/app/core/models';
|
||||
import { QcmService, UserService } from 'src/app/core/services';
|
||||
import data from 'src/qcm-bpa.json';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-qcm',
|
||||
selector: 'app-qcm',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
imports: [
|
||||
FormsModule, ReactiveFormsModule,
|
||||
MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule,
|
||||
MatIconModule, MatMenuModule, MatRadioModule,
|
||||
ListErrorsComponent
|
||||
],
|
||||
templateUrl: './qcm.component.html',
|
||||
styleUrls: ['./qcm.component.scss']
|
||||
styleUrl: './qcm.component.scss'
|
||||
})
|
||||
export class QCMComponent implements OnInit, OnDestroy {
|
||||
export class QcmComponent implements OnInit, OnDestroy {
|
||||
private _currentUser: Subscription = new Subscription();
|
||||
private _data: Subscription = new Subscription();
|
||||
public qcm: QCM = {} as QCM;
|
||||
public qcm: Qcm = {} as Qcm;
|
||||
public currentUser: User = {} as User;
|
||||
public canModify = false;
|
||||
public title = 'QCM Brevets';
|
||||
public subtitle = 'Préparation au questionnaires';
|
||||
public errors: Errors = { errors: {} };
|
||||
public destroyRef = inject(DestroyRef);
|
||||
public expandedIndex = 0;
|
||||
public questionsCount = 0;
|
||||
public qcmForm!: FormGroup;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private _qcmService: QCMService,
|
||||
private _userService: UserService
|
||||
) { }
|
||||
private fb: FormBuilder,
|
||||
private _qcmService: QcmService,
|
||||
private _userService: UserService,
|
||||
public menuItems: MenuItems
|
||||
) {
|
||||
this.qcmForm = this.fb.group({
|
||||
questions: this.fb.array([])
|
||||
});
|
||||
}
|
||||
|
||||
get questions(): FormArray {
|
||||
return <FormArray>this.qcmForm.controls['questions'];
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// Retreive the prefetched jump
|
||||
const data$: Observable<any> = this.route.data;
|
||||
this._data = data$.subscribe((data: { qcm: QCM }) => {
|
||||
//this.importQuestions();
|
||||
//this.importChoices();
|
||||
// Retreive the prefetched QCM
|
||||
const data$: Observable<{ qcm: Qcm }> = this.route.data as Observable<{ qcm: Qcm }>;
|
||||
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { qcm: Qcm }) => {
|
||||
this.qcm = data.qcm;
|
||||
if (this.qcm.name == 'bpa') {
|
||||
if (this.qcm.name === 'bpa') {
|
||||
this.title = 'QCM du BPA';
|
||||
this.subtitle = 'Préparation au questionnaire du BPA';
|
||||
} else {
|
||||
this.title = `QCM Brevets ${this.qcm.name.toUpperCase()}`;
|
||||
this.title = `QCM du brevet ${this.qcm.name.toUpperCase()}`;
|
||||
this.subtitle = `Préparation au questionnaire du brevet ${this.qcm.name.toUpperCase()}`;
|
||||
}
|
||||
//console.log(this.title, this.qcm);
|
||||
this.qcm.categories.forEach(category => {
|
||||
this.questionsCount += category.questions.length;
|
||||
category.questions.forEach(question => {
|
||||
this.questions.push(
|
||||
this.fb.group({
|
||||
id: new FormControl({ value: question.num, disabled: false }),
|
||||
choice: ['']
|
||||
//choice: new FormControl({ value: '', disabled: false })
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
// Load the current user's data
|
||||
const currentUser$: Observable<User> = this._userService.currentUser;
|
||||
@@ -50,4 +99,59 @@ export class QCMComponent implements OnInit, OnDestroy {
|
||||
this._data.unsubscribe();
|
||||
this._currentUser.unsubscribe();
|
||||
}
|
||||
|
||||
setExpandedIndex(index: number) {
|
||||
this.expandedIndex = index;
|
||||
}
|
||||
|
||||
onChange() {
|
||||
//this.isDisabled = true;
|
||||
}
|
||||
|
||||
importChoices() {
|
||||
try {
|
||||
data.categories.forEach((category) => {
|
||||
category.questions.forEach(question => {
|
||||
this._qcmService.saveChoices(question)
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe({
|
||||
next: (question) => {
|
||||
console.log('question : ', question);
|
||||
//console.log(`Le saut n°${jump.numero} a été ajouté !`, 'OK');
|
||||
},
|
||||
error: (err) => {
|
||||
console.log('err : ', err);
|
||||
//console.log(`Le saut n'a pas été ajouté !`, 'Error', err);
|
||||
this.errors = err;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
importQuestions() {
|
||||
try {
|
||||
data.categories.forEach((category) => {
|
||||
//Object.assign(this.jump, category);
|
||||
this._qcmService.saveQuestions(category)
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe({
|
||||
next: (category) => {
|
||||
console.log('category : ', category);
|
||||
//console.log(`Le saut n°${jump.numero} a été ajouté !`, 'OK');
|
||||
},
|
||||
error: (err) => {
|
||||
console.log('err : ', err);
|
||||
//console.log(`Le saut n'a pas été ajouté !`, 'Error', err);
|
||||
this.errors = err;
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
|
||||
import { QCMComponent } from './qcm.component';
|
||||
import { QCMResolver } from './qcm-resolver.service';
|
||||
|
||||
import { QCMRoutingModule } from './qcm-routing.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
QCMRoutingModule,
|
||||
QCMComponent
|
||||
],
|
||||
providers: [
|
||||
QCMResolver
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class QCMModule { }
|
||||
@@ -0,0 +1,45 @@
|
||||
<div class="banner bg-navy light text-center mat-elevation-z2">
|
||||
<div class="d-flex justify-content-center text-start">
|
||||
<img [src]="user.image" class="user-img" [alt]="user.username" />
|
||||
<div class="ms-3">
|
||||
<h4>{{ bannerTitle }}</h4>
|
||||
<button mat-raised-button color="cyan" [routerLink]="['/profile', user.username]">
|
||||
<mat-icon aria-label="Voir le compte" fontIcon="person"></mat-icon> Mon compte
|
||||
</button>
|
||||
<button mat-raised-button color="warn" routerLink="/settings" class="ms-2">
|
||||
<mat-icon aria-label="Modifier le compte" fontIcon="settings"></mat-icon> Paramètres
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="credentials-page content">
|
||||
<h1>{{title}}</h1>
|
||||
<mat-divider></mat-divider>
|
||||
<app-list-errors [errors]="errors"></app-list-errors>
|
||||
<div class="container page mt-4">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3 col-xs-12">
|
||||
<form class="row row-cols-lg-auto" [formGroup]="credentialsForm" (ngSubmit)="submitForm()">
|
||||
<fieldset [disabled]="isSubmitting" class="w-100">
|
||||
<legend [hidden]="true">Mise à jour du mot de passe</legend>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<mat-label>Mot de passe</mat-label>
|
||||
<input matInput type="password" placeholder="Nouveau mot de passe" formControlName="password">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<mat-label>Confirmation du mot de passe</mat-label>
|
||||
<input matInput type="password" placeholder="Confirmez votre nouveau mot de passe" formControlName="confirmPassword">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button mat-raised-button color="primary" [disabled]="!credentialsForm.valid" type="submit" class="float-sm-end">{{ btnUpdateTitle }}</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,34 @@
|
||||
/* Credentials */
|
||||
@use 'variable' as var;
|
||||
|
||||
.banner {
|
||||
background-color: var.$sidebar-footer;
|
||||
padding: 1.6rem 0 1.3rem 0;
|
||||
.user-img {
|
||||
width: var.$user-img-size;
|
||||
height: var.$user-img-size;
|
||||
border-radius: var.$user-img-size;
|
||||
}
|
||||
h4 {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
p {
|
||||
margin: 0 auto .5rem;
|
||||
color: var.$muted;
|
||||
max-width: 450px;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
/* Firefox */
|
||||
input[type=number] {
|
||||
appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CredentialsComponent } from './credentials.component';
|
||||
|
||||
describe('CredentialsComponent', () => {
|
||||
let component: CredentialsComponent;
|
||||
let fixture: ComponentFixture<CredentialsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CredentialsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CredentialsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { AbstractControl, FormsModule, ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators, ValidatorFn, ValidationErrors } from '@angular/forms';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { Errors, User } from 'src/app/core/models';
|
||||
import { UserService } from 'src/app/core/services';
|
||||
import { ListErrorsComponent } from 'src/app/components/shared';
|
||||
|
||||
@Component({
|
||||
selector: 'app-credentials',
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterLink, FormsModule, ReactiveFormsModule,
|
||||
MatButtonModule, MatIconModule, MatFormFieldModule, MatInputModule, MatDividerModule,
|
||||
ListErrorsComponent
|
||||
],
|
||||
templateUrl: './credentials.component.html',
|
||||
styleUrl: './credentials.component.scss'
|
||||
})
|
||||
export class CredentialsComponent implements OnInit, OnDestroy {
|
||||
private _user: Subscription = new Subscription();
|
||||
title = 'Modifier le mot de passe';
|
||||
bannerTitle = '';
|
||||
btnUpdateTitle = 'Mettre à jour';
|
||||
btnLogoutTitle = 'Se déconnecter';
|
||||
user: Partial<User> = {} as Partial<User>;
|
||||
credentialsForm: UntypedFormGroup;
|
||||
errors: Errors = { errors: {} };
|
||||
isSubmitting = false;
|
||||
destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private titleService: Title,
|
||||
private userService: UserService,
|
||||
private fb: UntypedFormBuilder
|
||||
) {
|
||||
this.credentialsForm = this.fb.group({
|
||||
password: ['', Validators.required],
|
||||
confirmPassword: ['', Validators.required]
|
||||
}, { validators: this.checkPasswords });
|
||||
}
|
||||
|
||||
get f() {
|
||||
return this.credentialsForm.controls;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle(`Head Up - ${this.title}`);
|
||||
Object.assign(this.user, this.userService.getCurrentUser());
|
||||
this.credentialsForm.patchValue(this.user);
|
||||
if (this.user.username) {
|
||||
this.bannerTitle = `@${this.user.username}`;
|
||||
} else {
|
||||
this.bannerTitle = `${this.user.firstname} ${this.user.lastname}`;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._user.unsubscribe();
|
||||
}
|
||||
|
||||
submitForm() {
|
||||
this.isSubmitting = true;
|
||||
this.updateUser(this.credentialsForm.value);
|
||||
const user$: Observable<{ user: User }> = this.userService.update(this.user).pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._user = user$.subscribe({
|
||||
next: ({ user }) => void this.router.navigateByUrl('/profile/' + user.username),
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateUser(values: NonNullable<unknown>) {
|
||||
Object.assign(this.user, values);
|
||||
}
|
||||
|
||||
checkPasswords: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
|
||||
const pass = group.get('password')!.value;
|
||||
const confirmPass = group.get('confirmPassword')!.value
|
||||
return pass === confirmPass ? null : { notSame: true }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './credentials/credentials.component';
|
||||
export * from './settings.component';
|
||||
@@ -1,18 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { SettingsComponent } from './settings.component';
|
||||
import { AuthGuard } from 'src/app/core/services';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: SettingsComponent,
|
||||
canActivate: [AuthGuard]
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SettingsRoutingModule { }
|
||||
@@ -1,22 +1,24 @@
|
||||
<div class="settings-page">
|
||||
<div class="banner light text-center pt-4 pb-3 mat-elevation-z2">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<img [src]="user.image" class="user-img" [alt]="user.username" />
|
||||
<h4>{{ bannerTitle }}</h4>
|
||||
<button mat-stroked-button color="accent" [routerLink]="['/profile', user.username]">
|
||||
<mat-icon aria-label="Voir le compte" fontIcon="person"></mat-icon> Mon compte
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="banner bg-navy light text-center mat-elevation-z2">
|
||||
<div class="d-flex justify-content-center text-start">
|
||||
<img [src]="user.image" class="user-img" [alt]="user.username" />
|
||||
<div class="ms-3">
|
||||
<h4>{{ bannerTitle }}</h4>
|
||||
<button mat-raised-button color="cyan" [routerLink]="['/profile', user.username]">
|
||||
<mat-icon aria-label="Voir le compte" fontIcon="person"></mat-icon> Mon compte
|
||||
</button>
|
||||
<button mat-raised-button color="warn" [routerLink]="['/settings', 'credentials']" class="ms-2">
|
||||
<mat-icon aria-label="Modifier le compte" fontIcon="password"></mat-icon> {{ btnPasswordTitle }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container page">
|
||||
</div>
|
||||
<div class="settings-page content">
|
||||
<h1>{{title}}</h1>
|
||||
<mat-divider></mat-divider>
|
||||
<app-list-errors [errors]="errors"></app-list-errors>
|
||||
<div class="container page mt-4">
|
||||
<div class="row">
|
||||
<div class="col-md-6 offset-md-3 col-xs-12">
|
||||
<h1 class="text-xs-center mb-3">{{ title }}</h1>
|
||||
<huapp-list-errors [errors]="errors"></huapp-list-errors>
|
||||
<form class="row row-cols-lg-auto" [formGroup]="settingsForm" (ngSubmit)="submitForm()">
|
||||
<fieldset [disabled]="isSubmitting" class="w-100">
|
||||
<legend [hidden]="true">Informations personnelles</legend>
|
||||
@@ -77,25 +79,12 @@
|
||||
<input matInput readonly type="email" placeholder="Indiquez votre adresse email" formControlName="email">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<mat-label>Mot de passe</mat-label>
|
||||
<input matInput type="password" placeholder="Nouveau mot de passe" formControlName="password">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<mat-label>Confirmation du mot de passe</mat-label>
|
||||
<input matInput type="password" placeholder="Confirmez votre nouveau mot de passe" formControlName="confirmPassword">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="col-12 clearfix">
|
||||
<button mat-raised-button color="primary" [disabled]="!settingsForm.valid" type="submit" class="float-sm-end">{{ btnUpdateTitle }}</button>
|
||||
<button mat-stroked-button color="warn" class="me-3" type="button" (click)="logout()">{{ btnLogoutTitle }}</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
<mat-divider class="my-3 border-light-subtle"></mat-divider>
|
||||
<button mat-stroked-button color="warn" (click)="logout()">{{ btnLogoutTitle }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/* Settings */
|
||||
@use '@angular/material' as mat;
|
||||
@use 'src/styles/variable' as var;
|
||||
//@import 'src/styles/variable';
|
||||
@use 'variable' as var;
|
||||
|
||||
.banner {
|
||||
background-color: var.$sidebar-footer;
|
||||
padding: 1.6rem 0 1.3rem 0;
|
||||
/*
|
||||
background-color: mat.get-color-from-palette(var.$md-custom-warn, 700);
|
||||
margin-bottom: 1rem;
|
||||
&.light {
|
||||
@@ -14,11 +15,11 @@
|
||||
border-color: #555;
|
||||
}
|
||||
}
|
||||
*/
|
||||
.user-img {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
border-radius: 84px;
|
||||
margin-bottom: 0.75rem;
|
||||
width: var.$user-img-size;
|
||||
height: var.$user-img-size;
|
||||
border-radius: var.$user-img-size;
|
||||
}
|
||||
h4 {
|
||||
font-weight: 500;
|
||||
@@ -26,7 +27,7 @@
|
||||
}
|
||||
p {
|
||||
margin: 0 auto .5rem;
|
||||
color: #aaaaaa;
|
||||
color: var.$muted;
|
||||
max-width: 450px;
|
||||
font-weight: 300;
|
||||
}
|
||||
@@ -38,7 +39,6 @@ input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type=number] {
|
||||
appearance: textfield;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { AbstractControl, FormsModule, ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators, ValidatorFn, ValidationErrors } from '@angular/forms';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormsModule, ReactiveFormsModule, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -9,22 +10,21 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { Errors, User } from 'src/app/core/models';
|
||||
import { UserService } from 'src/app/core/services';
|
||||
import { ListErrorsComponent } from 'src/app/components/shared';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-settings-page',
|
||||
styleUrls: ['settings.component.scss'],
|
||||
templateUrl: './settings.component.html',
|
||||
selector: 'app-settings-page',
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterLink, FormsModule, ReactiveFormsModule,
|
||||
MatButtonModule, MatIconModule, MatFormFieldModule, MatInputModule, MatDividerModule,
|
||||
ListErrorsComponent
|
||||
]
|
||||
],
|
||||
styleUrl: './settings.component.scss',
|
||||
templateUrl: './settings.component.html'
|
||||
})
|
||||
export class SettingsComponent implements OnInit, OnDestroy {
|
||||
private _user: Subscription = new Subscription();
|
||||
@@ -32,10 +32,12 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
bannerTitle = '';
|
||||
btnUpdateTitle = 'Mettre à jour';
|
||||
btnLogoutTitle = 'Se déconnecter';
|
||||
user: User = {} as User;
|
||||
btnPasswordTitle = 'Changer le mot de passe';
|
||||
user: Partial<User> = {} as Partial<User>;
|
||||
settingsForm: UntypedFormGroup;
|
||||
errors: Errors = { errors: {} };
|
||||
isSubmitting = false;
|
||||
destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
@@ -50,13 +52,11 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
firstname: ['', Validators.required],
|
||||
lastname: ['', Validators.required],
|
||||
phone: ['', Validators.required],
|
||||
password: ['', Validators.required],
|
||||
confirmPassword: ['', Validators.required],
|
||||
licence: '',
|
||||
poids: '',
|
||||
image: '',
|
||||
bg_image: ''
|
||||
}, { validators: this.checkPasswords });
|
||||
});
|
||||
// Optional: subscribe to changes on the form :
|
||||
// this.settingsForm.valueChanges.pipe(this._scavenger.collect()).subscribe(values => this.updateUser(values));
|
||||
}
|
||||
@@ -87,15 +87,19 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
this.router.navigateByUrl('/login');
|
||||
}
|
||||
|
||||
updatePass() {
|
||||
this.router.navigateByUrl('/settings/credentials');
|
||||
}
|
||||
|
||||
submitForm() {
|
||||
this.isSubmitting = true;
|
||||
|
||||
// update the model
|
||||
this.updateUser(this.settingsForm.value);
|
||||
|
||||
const user$: Observable<User> = this.userService.update(this.user).pipe(take(1));
|
||||
const user$: Observable<{ user: User }> = this.userService.update(this.user).pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._user = user$.subscribe({
|
||||
next: (updatedUser) => this.router.navigateByUrl('/profile/' + updatedUser.username),
|
||||
next: ({ user }) => void this.router.navigateByUrl('/profile/' + user.username),
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
this.isSubmitting = false;
|
||||
@@ -107,10 +111,4 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
Object.assign(this.user, values);
|
||||
}
|
||||
|
||||
checkPasswords: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
|
||||
const pass = group.get('password')!.value;
|
||||
const confirmPass = group.get('confirmPassword')!.value
|
||||
return pass === confirmPass ? null : { notSame: true }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { SettingsComponent } from './settings.component';
|
||||
import { SettingsRoutingModule } from './settings-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SettingsRoutingModule,
|
||||
SettingsComponent
|
||||
]
|
||||
})
|
||||
export class SettingsModule { }
|
||||
+10
-14
@@ -7,36 +7,32 @@
|
||||
<mat-icon fontIcon="more_vert"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuAeronefBar="matMenu">
|
||||
<button mat-menu-item *ngFor="let menuitem of menuItems.getMenuPanel()">
|
||||
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
|
||||
<button mat-menu-item>
|
||||
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
</mat-card-header>
|
||||
<hr>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<mat-card-content>
|
||||
<div *ngIf="displayCharts" class="barchart position-relative w-100 my-1">
|
||||
<canvas baseChart
|
||||
[data]="chartConfig.barChartData"
|
||||
[options]="chartConfig.barChartOptions"
|
||||
[plugins]="chartConfig.barChartPlugins"
|
||||
[legend]="chartConfig.barChartLegend"
|
||||
[type]="'bar'">
|
||||
</canvas>
|
||||
</div>
|
||||
@if (displayCharts) {
|
||||
<app-bar-horizontal-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [colors]="seriesColor" label="Nombre total de sauts" [legend]="false"></app-bar-horizontal-chart>
|
||||
}
|
||||
</mat-card-content>
|
||||
<hr class="mb-0">
|
||||
<mat-divider class="mt-3 mb-0"></mat-divider>
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>Historique annuel</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<huapp-history-table [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [rows]="seriesRow" [colors]="seriesColor"></huapp-history-table>
|
||||
<app-history-table [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [rows]="seriesRow" [colors]="seriesColorClass"></app-history-table>
|
||||
<!--
|
||||
<div fxLayout="row wrap">
|
||||
<div *ngFor='let name of seriesName; let i=index' fxFlex.gt-lg="33" fxFlex.gt-md="50" fxFlex.gt-xs="20" fxFlex="100">
|
||||
<h6 class="{{seriesColor[i]}} m-0">
|
||||
<i class="mdi mdi-checkbox-blank-circle font-10 m-r-10 "></i>{{name}} - {{seriesValue[i]}}
|
||||
<i class="mdi mdi-checkbox-blank-circle font-10 m-r-10 "></i>{{name}} - {{seriesValue[i]}}
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -8,8 +8,8 @@ describe('AeronefsBarComponent', () => {
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AeronefsBarComponent ]
|
||||
})
|
||||
imports: [AeronefsBarComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
|
||||
+32
-35
@@ -1,27 +1,27 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { ChartDataset } from 'chart.js';
|
||||
import { NgChartsModule } from 'ng2-charts';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { MenuItems, HistoryTableComponent } from 'src/app/components/shared';
|
||||
import { MenuItems } from 'src/app/components/shared';
|
||||
import { BarHorizontalChartComponent } from 'src/app/components/shared/helpers-chart';
|
||||
import { HistoryTableComponent } from 'src/app/components/shared/helpers-jump';
|
||||
import { UtilitiesService, AeronefsService } from 'src/app/core/services';
|
||||
import { AeronefByImat, AeronefByYear, BarConfig } from 'src/app/core/models';
|
||||
import { AeronefByImat, AeronefByYear } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-aeronefs-bar',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatButtonModule, MatCardModule, MatExpansionModule, MatIconModule, MatMenuModule,
|
||||
NgChartsModule,
|
||||
HistoryTableComponent
|
||||
MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatExpansionModule, MatIconModule, MatMenuModule,
|
||||
BarHorizontalChartComponent, HistoryTableComponent
|
||||
],
|
||||
selector: 'huapp-aeronefs-bar',
|
||||
templateUrl: './aeronefs-bar.component.html'
|
||||
})
|
||||
export class AeronefsBarComponent implements OnInit, OnDestroy {
|
||||
@@ -33,11 +33,18 @@ export class AeronefsBarComponent implements OnInit, OnDestroy {
|
||||
public subtitle: string = 'Nombre total de sauts par aéronef';
|
||||
public seriesHeader: string[] = [];
|
||||
public seriesName: string[] = [];
|
||||
public seriesValue: any[] = [];
|
||||
public seriesRow: any[] = [];
|
||||
public seriesColor: string[] = this._utilitiesService.getChartColors();
|
||||
public seriesValue: number[] = [];
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
public displayCharts = false;
|
||||
public chartConfig: BarConfig = this._utilitiesService.getHorizontalBarChartConfig();
|
||||
public destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private _aeronefsService: AeronefsService,
|
||||
@@ -55,44 +62,34 @@ export class AeronefsBarComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private _loadAeronefByImat(): void {
|
||||
let values: Array<number> = [];
|
||||
const aeronefs$: Observable<Array<AeronefByImat>> = this._aeronefsService.getAllByImat();
|
||||
const aeronefs$: Observable<Array<AeronefByImat>> = this._aeronefsService.getAllByImat().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._aeronefByImat = aeronefs$.subscribe((aggregate: Array<AeronefByImat>) => {
|
||||
this._aeronefsByImat = aggregate.map((row: AeronefByImat) => {
|
||||
//this.chartConfig.barChartData.labels!.push(`${row._id.aeronef} ${row._id.imat}`);
|
||||
this.seriesName.push(`${row._id.aeronef} ${row._id.imat}`);
|
||||
//this.chartConfig.barChartData.labels!.push(`${row.aeronef} ${row.imat}`);
|
||||
this.seriesName.push(`${row.aeronef} ${row.imat}`);
|
||||
this.seriesValue.push(row.count);
|
||||
return row;
|
||||
});
|
||||
let data: ChartDataset<'bar'> = {
|
||||
data: [...this.seriesValue],
|
||||
label: 'Nombre total de sauts',
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.9),
|
||||
borderColor: 'rgba(255, 255, 255, 1)', //this._utilitiesService.getSeriesColors(1),
|
||||
borderWidth: 0
|
||||
};
|
||||
this.chartConfig.barChartData.labels = [...this.seriesName];
|
||||
this.chartConfig.barChartData.datasets!.push(data);
|
||||
this.displayCharts = true;
|
||||
this._loadAeronefByYear();
|
||||
});
|
||||
}
|
||||
|
||||
private _loadAeronefByYear(): void {
|
||||
this.seriesHeader = ["2018", "2019", "2020", "2021", "2022", "2023"];
|
||||
let values: Array<number> = this.seriesHeader.map(() => 0);
|
||||
this.seriesHeader = ['2018', '2019', '2020', '2021', '2022', '2023', '2024'];
|
||||
const values: Array<number> = this.seriesHeader.map(() => 0);
|
||||
this.seriesName.forEach(() => {
|
||||
this.seriesRow.push([...values]);
|
||||
});
|
||||
const dropzones$: Observable<Array<AeronefByYear>> = this._aeronefsService.getAllByDate();
|
||||
const dropzones$: Observable<Array<AeronefByYear>> = this._aeronefsService.getAllByImatByYear().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._aeronefByYear = dropzones$.subscribe((aggregate: Array<AeronefByYear>) => {
|
||||
this._aeronefsByYear = aggregate.map((row: AeronefByYear) => {
|
||||
let aeronef: string = row._id.aeronef;
|
||||
let imat: string = row._id.imat;
|
||||
let year: string = row._id.year.toString();
|
||||
const aeronef: string = row.aeronef;
|
||||
const imat: string = row.imat;
|
||||
const year: string = row.year.toString();
|
||||
this.seriesRow[this.seriesName.indexOf(`${aeronef} ${imat}`)][this.seriesHeader.indexOf(year)] = row.count;
|
||||
return row;
|
||||
});
|
||||
this.displayCharts = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,40 +7,35 @@
|
||||
<mat-icon fontIcon="more_vert"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuAeronef="matMenu">
|
||||
<button mat-menu-item *ngFor="let menuitem of menuItems.getMenuPanel()">
|
||||
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
|
||||
<button mat-menu-item>
|
||||
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
</mat-card-header>
|
||||
<hr>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<mat-card-content>
|
||||
<div *ngIf="displayCharts" class="piechart position-relative w-100 my-1">
|
||||
<canvas baseChart class="mx-auto"
|
||||
[labels]="chartConfig.doughnutChartLabels"
|
||||
[datasets]="chartConfig.doughnutChartDatasets"
|
||||
[options]="chartConfig.doughnutChartOptions"
|
||||
[legend]="chartConfig.doughnutChartLegend"
|
||||
[type]="'doughnut'">
|
||||
</canvas>
|
||||
</div>
|
||||
<app-pie-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [colors]="seriesColor"></app-pie-chart>
|
||||
</mat-card-content>
|
||||
<hr class="mb-0">
|
||||
<mat-divider class="mt-3 mb-0"></mat-divider>
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>Historique annuel</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<huapp-history-table [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [rows]="seriesRow" [colors]="seriesColor"></huapp-history-table>
|
||||
<app-history-table [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [rows]="seriesRow" [colors]="seriesColorClass"></app-history-table>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
<!--<hr>
|
||||
<!--
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<mat-card-content>
|
||||
<div fxLayout="row wrap">
|
||||
<div *ngFor='let name of seriesName; let i=index' fxFlex.gt-lg="33" fxFlex.gt-md="50" fxFlex.gt-xs="20" fxFlex="100">
|
||||
<h6 class="{{seriesColor[i]}} m-0">
|
||||
<i class="mdi mdi-checkbox-blank-circle font-10 m-r-10 "></i>{{name}} - {{seriesValue[i]}}
|
||||
</h6>
|
||||
<h6 class="{{seriesColor[i]}} m-0">
|
||||
<i class="mdi mdi-checkbox-blank-circle font-10 m-r-10 "></i>{{name}} - {{seriesValue[i]}}
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>-->
|
||||
|
||||
@@ -8,8 +8,8 @@ describe('AeronefsComponent', () => {
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AeronefsComponent ]
|
||||
})
|
||||
imports: [AeronefsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Router, RouterLink, RouterModule } from '@angular/router';
|
||||
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatExpansionModule } from '@angular/material/expansion';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { NgChartsModule } from 'ng2-charts';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { MenuItems, HistoryTableComponent } from 'src/app/components/shared';
|
||||
import { MenuItems } from 'src/app/components/shared';
|
||||
import { HistoryTableComponent } from 'src/app/components/shared/helpers-jump';
|
||||
import { PieChartComponent } from 'src/app/components/shared/helpers-chart';
|
||||
import { UtilitiesService, AeronefsService } from 'src/app/core/services';
|
||||
import { AeronefByImat, AeronefByYear, DoughnutConfig } from 'src/app/core/models';
|
||||
import { AeronefByImat, AeronefByYear } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-aeronefs',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule, RouterModule,
|
||||
MatBadgeModule, MatButtonModule, MatCardModule, MatExpansionModule, MatIconModule, MatMenuModule,
|
||||
NgChartsModule,
|
||||
HistoryTableComponent
|
||||
MatBadgeModule, MatButtonModule, MatCardModule, MatDividerModule,
|
||||
MatExpansionModule, MatIconModule, MatMenuModule,
|
||||
HistoryTableComponent, PieChartComponent
|
||||
],
|
||||
selector: 'huapp-aeronefs',
|
||||
templateUrl: './aeronefs.component.html'
|
||||
})
|
||||
export class AeronefsComponent implements OnInit, OnDestroy {
|
||||
@@ -35,13 +35,18 @@ export class AeronefsComponent implements OnInit, OnDestroy {
|
||||
public seriesHeader: string[] = [];
|
||||
public seriesName: string[] = [];
|
||||
public seriesValue: number[] = [];
|
||||
public seriesRow: any[] = [];
|
||||
public seriesColor: string[] = this._utilitiesService.getChartColors();
|
||||
public displayCharts = false;
|
||||
public chartConfig: DoughnutConfig = this._utilitiesService.getDoughnutChartConfig();
|
||||
public seriesRow: Array<Array<number>> = [];
|
||||
public seriesColor: {
|
||||
backgroundColor: string[],
|
||||
borderColor: string[]
|
||||
} = {
|
||||
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
|
||||
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
|
||||
};
|
||||
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
|
||||
public destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private _aeronefsService: AeronefsService,
|
||||
private _utilitiesService: UtilitiesService,
|
||||
public menuItems: MenuItems
|
||||
@@ -57,32 +62,29 @@ export class AeronefsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private _loadAeronefByImat(): void {
|
||||
const aeronefs$: Observable<Array<AeronefByImat>> = this._aeronefsService.getAllByImat();
|
||||
const aeronefs$: Observable<Array<AeronefByImat>> = this._aeronefsService.getAllByImat().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._aeronefByImat = aeronefs$.subscribe((aggregate: Array<AeronefByImat>) => {
|
||||
this._aeronefsByImat = aggregate.map((row: AeronefByImat) => {
|
||||
this.chartConfig.doughnutChartLabels.push(`${row._id.aeronef} ${row._id.imat}`);
|
||||
this.chartConfig.doughnutChartDatasets[0].data.push(row.count);
|
||||
this.seriesName.push(`${row.aeronef} ${row.imat}`);
|
||||
this.seriesValue.push(row.count);
|
||||
return row;
|
||||
});
|
||||
this.seriesName = [...this.chartConfig.doughnutChartLabels];
|
||||
this.seriesValue = [...this.chartConfig.doughnutChartDatasets[0].data];
|
||||
this.displayCharts = true;
|
||||
this._loadAeronefByYear();
|
||||
});
|
||||
}
|
||||
|
||||
private _loadAeronefByYear(): void {
|
||||
this.seriesHeader = ["2018", "2019", "2020", "2021", "2022", "2023"];
|
||||
let values: Array<number> = this.seriesHeader.map(() => 0);
|
||||
this.seriesHeader = ['2018', '2019', '2020', '2021', '2022', '2023', '2024'];
|
||||
const values: Array<number> = this.seriesHeader.map(() => 0);
|
||||
this.seriesName.forEach(() => {
|
||||
this.seriesRow.push([...values]);
|
||||
});
|
||||
const dropzones$: Observable<Array<AeronefByYear>> = this._aeronefsService.getAllByDate();
|
||||
const dropzones$: Observable<Array<AeronefByYear>> = this._aeronefsService.getAllByImatByYear().pipe(takeUntilDestroyed(this.destroyRef));
|
||||
this._aeronefByYear = dropzones$.subscribe((aggregate: Array<AeronefByYear>) => {
|
||||
this._aeronefsByYear = aggregate.map((row: AeronefByYear) => {
|
||||
let aeronef: string = row._id.aeronef;
|
||||
let imat: string = row._id.imat;
|
||||
let year: string = row._id.year.toString();
|
||||
const aeronef: string = row.aeronef;
|
||||
const imat: string = row.imat;
|
||||
const year: string = row.year.toString();
|
||||
this.seriesRow[this.seriesName.indexOf(`${aeronef} ${imat}`)][this.seriesHeader.indexOf(year)] = row.count;
|
||||
return row;
|
||||
});
|
||||
|
||||
+7
-13
@@ -7,31 +7,25 @@
|
||||
<mat-icon fontIcon="more_vert"></mat-icon>
|
||||
</button>
|
||||
<mat-menu #menuCanopyModel="matMenu">
|
||||
<button mat-menu-item *ngFor="let menuitem of menuItems.getMenuPanel()">
|
||||
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
|
||||
<button mat-menu-item>
|
||||
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
</mat-card-header>
|
||||
<hr>
|
||||
<mat-divider class="my-3"></mat-divider>
|
||||
<mat-card-content>
|
||||
<div *ngIf="displayCharts" class="piechart position-relative w-100 my-1">
|
||||
<canvas baseChart class="mx-auto"
|
||||
[labels]="chartConfig.doughnutChartLabels"
|
||||
[datasets]="chartConfig.doughnutChartDatasets"
|
||||
[options]="chartConfig.doughnutChartOptions"
|
||||
[legend]="chartConfig.doughnutChartLegend"
|
||||
[type]="'doughnut'">
|
||||
</canvas>
|
||||
</div>
|
||||
<app-pie-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [colors]="seriesColor"></app-pie-chart>
|
||||
</mat-card-content>
|
||||
<hr class="mb-0">
|
||||
<mat-divider class="mt-3 mb-0"></mat-divider>
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>Historique annuel</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<huapp-history-table [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [rows]="seriesRow" [colors]="seriesColor"></huapp-history-table>
|
||||
<app-history-table [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [rows]="seriesRow" [colors]="seriesColorClass"></app-history-table>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
</mat-card>
|
||||
|
||||
+2
-2
@@ -8,8 +8,8 @@ describe('CanopyModelsComponent', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CanopyModelsComponent ]
|
||||
})
|
||||
imports: [CanopyModelsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user