Ajout des sources
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
|
||||
"recommendations": ["angular.ng-template"]
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ng serve",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "npm: start",
|
||||
"url": "http://localhost:4200/"
|
||||
},
|
||||
{
|
||||
"name": "ng test",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "npm: test",
|
||||
"url": "http://localhost:9876/debug.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "start",
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"pattern": "$tsc",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": "(.*?)"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": "bundle generation complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "test",
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"pattern": "$tsc",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": "(.*?)"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": "bundle generation complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# HeadupApp
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 16.1.3.
|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"cli": {
|
||||
"packageManager": "npm"
|
||||
},
|
||||
"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",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.development.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "headup_app:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "headup_app:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "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": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+23003
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "headup-app",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^16.1.0",
|
||||
"@angular/cdk": "^16.2.1",
|
||||
"@angular/common": "^16.1.0",
|
||||
"@angular/compiler": "^16.1.0",
|
||||
"@angular/core": "^16.1.0",
|
||||
"@angular/forms": "^16.1.0",
|
||||
"@angular/material": "^16.2.1",
|
||||
"@angular/platform-browser": "^16.1.0",
|
||||
"@angular/platform-browser-dynamic": "^16.1.0",
|
||||
"@angular/router": "^16.1.0",
|
||||
"bootstrap": "^5.3.0",
|
||||
"chartist": "^1.3.0",
|
||||
"ng-chartist": "^8.1.3",
|
||||
"ngx-skeleton-loader": "^8.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^16.1.3",
|
||||
"@angular/cli": "~16.1.3",
|
||||
"@angular/compiler-cli": "^16.1.0",
|
||||
"@types/jasmine": "~4.3.0",
|
||||
"jasmine-core": "~4.6.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.1.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
$path = realpath(__DIR__).'/';
|
||||
$json = file_get_contents($path.'qcm.json');
|
||||
$data = json_decode($json);
|
||||
$search = @$_POST['search'];
|
||||
$wrong = false;
|
||||
$show = true;
|
||||
$checked = '';
|
||||
if (isset($_POST['wrong'])) {
|
||||
$wrong = true;
|
||||
$checked = ' checked';
|
||||
}
|
||||
if ($search) {
|
||||
$show = false;
|
||||
}
|
||||
$html = '<!doctype html>';
|
||||
$html .= '
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>QCM</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-3 mb-4">
|
||||
<form class="row row-cols-lg-auto g-3 align-items-center" action="https://rampeur.fr/qcm.php" method="post">
|
||||
<div class="col-12">
|
||||
<label class="visually-hidden" for="search">Recherche</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="search" name="search" placeholder="Recherche" value="'.$search.'">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="wrong" name="wrong"'.$checked.'>
|
||||
<label class="form-check-label" for="wrong">Tout afficher</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary">Ok</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
';
|
||||
if ($data->qcm) {
|
||||
foreach ($data->qcm as $qcm) {
|
||||
if ($qcm->name == 'bpa') {
|
||||
foreach ($qcm->categories as $category) {
|
||||
$html .= '
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">';
|
||||
if ($wrong) {
|
||||
$html .= '<h3 id="cat_'.$category->num.'">'.$category->num.' '.$category->name.'</h3>';
|
||||
}
|
||||
$found = false;
|
||||
foreach ($category->questions as $question) {
|
||||
if (!$search) {
|
||||
$show = true;
|
||||
} else {
|
||||
$show = false;
|
||||
$pattern = '/'.$search.'/i';
|
||||
if (stripos($question->libelle, $search) !== false) {
|
||||
$show = true;
|
||||
}
|
||||
}
|
||||
if ($show) {
|
||||
$html .= '<h5>'.$question->num.' - '.$question->libelle.'</h5>';
|
||||
$html .= '<p>';
|
||||
foreach ($question->answers as $answer) {
|
||||
if ($answer->correct) {
|
||||
$html .= '<span class="text-success">'.$answer->libelle.'</span>';
|
||||
} else if ($wrong) {
|
||||
$html .= '<span class="text-danger">'.$answer->libelle.'</span>';
|
||||
}
|
||||
if ($wrong) {
|
||||
$html .= '<br />';
|
||||
}
|
||||
}
|
||||
$html .= '</p>';
|
||||
}
|
||||
if ($search && $show) {
|
||||
//break;
|
||||
}
|
||||
}
|
||||
$html .= '
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$html .= '<p class="text-center">0 qcm</p>';
|
||||
}
|
||||
$html .= '
|
||||
</body>
|
||||
</html>';
|
||||
$html .= '';
|
||||
echo $html;
|
||||
?>
|
||||
@@ -0,0 +1,40 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'dashboard',
|
||||
loadChildren: () => import('./components/home/home.module').then(m => m.HomeModule)
|
||||
},
|
||||
{
|
||||
path: 'calculator',
|
||||
loadChildren: () => import('./components/calculator/calculator.module').then(m => m.CalculatorModule)
|
||||
},
|
||||
{
|
||||
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: '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 { }
|
||||
@@ -0,0 +1,9 @@
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * Main wrapper * * * * * * * * * * -->
|
||||
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
|
||||
<huapp-full-layout></huapp-full-layout>
|
||||
<!--
|
||||
<huapp-layout-header></huapp-layout-header>
|
||||
<router-outlet><huapp-spinner></huapp-spinner></router-outlet>
|
||||
<huapp-layout-footer></huapp-layout-footer>
|
||||
-->
|
||||
@@ -0,0 +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]
|
||||
}));
|
||||
|
||||
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 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!');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { UserService } from "src/app/core/services";
|
||||
import { SpinnerComponent } from 'src/app/components';
|
||||
@Component({
|
||||
selector: 'huapp-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'Head Up';
|
||||
|
||||
constructor(private userService: UserService, private titleService: Title) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.userService.populate();
|
||||
this.titleService.setTitle(this.title);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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 { ChartistModule } from "ng-chartist";
|
||||
|
||||
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 { HomeModule } from './components/home/home.module';
|
||||
import { SpinnerComponent, FullComponent } from './components/shared';
|
||||
|
||||
registerLocaleData(localeFr);
|
||||
|
||||
@NgModule({
|
||||
declarations: [ AppComponent ],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
ChartistModule,
|
||||
BrowserAnimationsModule,
|
||||
AuthModule, CoreModule, HomeModule,
|
||||
SpinnerComponent, FullComponent
|
||||
],
|
||||
providers: [ Title, { provide: LOCALE_ID, useValue: 'fr-FR' } ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
@@ -0,0 +1,29 @@
|
||||
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 { }
|
||||
@@ -0,0 +1,71 @@
|
||||
<div class="container page 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>
|
||||
</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>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
fieldset {
|
||||
border-style: none;
|
||||
border-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AuthComponent } from './auth.component';
|
||||
|
||||
describe('AuthComponent', () => {
|
||||
let component: AuthComponent;
|
||||
let fixture: ComponentFixture<AuthComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AuthComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(AuthComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,157 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { NgIf } from '@angular/common';
|
||||
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';
|
||||
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 { take } from 'rxjs/operators';
|
||||
|
||||
import { Errors } from 'src/app/core/models';
|
||||
import { UserService } from 'src/app/core/services';
|
||||
import { ListErrorsComponent, ShowAuthedDirective } from 'src/app/components/shared';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgIf, RouterLink, FormsModule, ReactiveFormsModule,
|
||||
MatFormFieldModule, MatInputModule, MatButtonModule,
|
||||
ListErrorsComponent, ShowAuthedDirective
|
||||
],
|
||||
selector: 'huapp-auth',
|
||||
templateUrl: './auth.component.html',
|
||||
styleUrls: ['./auth.component.scss']
|
||||
})
|
||||
export class AuthComponent implements OnInit, OnDestroy {
|
||||
private _url: Subscription = new Subscription();
|
||||
private _user: Subscription = new Subscription();
|
||||
authType = '';
|
||||
title = '';
|
||||
btnTitle = '';
|
||||
errors!: Errors;
|
||||
isSubmitting = false;
|
||||
authForm!: UntypedFormGroup;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private titleService: Title,
|
||||
private userService: UserService,
|
||||
private fb: UntypedFormBuilder
|
||||
) {
|
||||
this._resetErrors();
|
||||
// use FormBuilder to create a form group
|
||||
this.authForm = this.fb.group({
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
username: '',
|
||||
firstname: '',
|
||||
lastname: '',
|
||||
phone: '',
|
||||
password: ['', Validators.required],
|
||||
confirmPassword: ''
|
||||
}, { validators: this.checkPasswords });
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// you would normally unsubscribe from this observable subscription
|
||||
// the active route observables are exemptions from unsubribe always rule
|
||||
// see notes on: https://angular.io/guide/router#observable-parammap-and-component-reuse
|
||||
this.route.url.pipe(take(1))
|
||||
.subscribe(data => {
|
||||
// Get the last piece of the URL (it's either 'login' or 'register')
|
||||
this.authType = data[data.length - 1].path;
|
||||
// Set a title for the page accordingly
|
||||
if (this.authType === 'register') {
|
||||
this.title = 'Création de compte';
|
||||
this.btnTitle = 'Créer le compte';
|
||||
this.titleService.setTitle(`Head Up - Créer un compte`);
|
||||
} else {
|
||||
this.title = 'Se connecter';
|
||||
this.btnTitle = 'Se connecter';
|
||||
this.titleService.setTitle(`Head Up - Connexion`);
|
||||
}
|
||||
// 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]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._user.unsubscribe();
|
||||
//this._url.unsubscribe();
|
||||
}
|
||||
|
||||
submitForm() {
|
||||
this.isSubmitting = true;
|
||||
|
||||
if (this.authForm.valid) {
|
||||
this._resetErrors();
|
||||
const credentials = this.authForm.value;
|
||||
const user$ = this.userService.attemptAuth(this.authType, credentials).pipe(take(1));
|
||||
this._user = user$.subscribe({
|
||||
next: () => this.router.navigateByUrl('/'),
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getErrorMessage(name: string) {
|
||||
switch (name) {
|
||||
case 'email':
|
||||
if (this.authForm.controls['email'].errors !== null) {
|
||||
return 'Indiquez votre adresse email';
|
||||
}
|
||||
break;
|
||||
case 'firstname':
|
||||
if (this.authForm.controls['firstname'].errors !== null) {
|
||||
return 'Vous devez indiquer votre prénnom';
|
||||
}
|
||||
break;
|
||||
case 'lastname':
|
||||
if (this.authForm.controls['lastname'].errors !== null) {
|
||||
return 'Vous devez indiquer votre nom';
|
||||
}
|
||||
break;
|
||||
case 'phone':
|
||||
if (this.authForm.controls['phone'].errors !== null) {
|
||||
return 'Vous devez indiquer votre numéro de téléphone';
|
||||
}
|
||||
break;
|
||||
case 'password':
|
||||
if (this.authForm.controls['password'].errors !== null) {
|
||||
return 'Un mot de passe est obligatoire';
|
||||
}
|
||||
break;
|
||||
case 'confirmPassword':
|
||||
if (this.authForm.controls['confirmPassword'].errors !== null) {
|
||||
return 'Une confirmation du mot de passe est obligatoire';
|
||||
}
|
||||
break;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
checkPasswords: ValidatorFn = (group: AbstractControl): ValidationErrors | null => {
|
||||
if (this.authType !== 'register') {
|
||||
return null;
|
||||
}
|
||||
const pass = group.get('password')!.value;
|
||||
const confirmPass = group.get('confirmPassword')!.value
|
||||
return (pass === confirmPass && pass !== '') ? null : { notSame: true }
|
||||
}
|
||||
|
||||
private _resetErrors(): void {
|
||||
this.errors = { errors: {} };
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
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 { }
|
||||
@@ -0,0 +1,18 @@
|
||||
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 { }
|
||||
@@ -0,0 +1,278 @@
|
||||
<div class="content">
|
||||
<h1>{{title}}</h1>
|
||||
<div class="col-xs-12">
|
||||
<div class="demo-buttons my-2">
|
||||
<!-- primary buttons -->
|
||||
<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">
|
||||
<!-- accent buttons -->
|
||||
<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">
|
||||
<!-- warn buttons -->
|
||||
<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">
|
||||
<!-- danger buttons -->
|
||||
<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">
|
||||
<!-- info buttons -->
|
||||
<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">
|
||||
<!-- success buttons -->
|
||||
<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>
|
||||
<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>
|
||||
@@ -0,0 +1,26 @@
|
||||
section {
|
||||
display: table;
|
||||
}
|
||||
.demo-label {
|
||||
display: table-cell;
|
||||
font-size: 14px;
|
||||
margin-left: 8px;
|
||||
min-width: 120px;
|
||||
}
|
||||
.demo-button-row {
|
||||
display: table-cell;
|
||||
max-width: 900px;
|
||||
}
|
||||
.demo-button-row .mat-mdc-button-base {
|
||||
margin: 8px 8px 8px 0;
|
||||
}
|
||||
.demo-flex-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.demo-button-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 120px;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CalculatorComponent } from './calculator.component';
|
||||
|
||||
describe('CalculatorComponent', () => {
|
||||
let component: CalculatorComponent;
|
||||
let fixture: ComponentFixture<CalculatorComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [CalculatorComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(CalculatorComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
MatButtonModule, MatDividerModule, MatIconModule
|
||||
],
|
||||
selector: 'huapp-calculator',
|
||||
templateUrl: './calculator.component.html',
|
||||
styleUrls: ['./calculator.component.scss']
|
||||
})
|
||||
export class CalculatorComponent implements OnInit, OnDestroy {
|
||||
title = 'Taille de voile - DT48';
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
//
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
//this._scavenger.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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 { }
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<mat-card>
|
||||
<mat-card-content class="mb-0">
|
||||
<mat-card-title>Aéronefs</mat-card-title>
|
||||
<mat-card-subtitle>Nombre total de sauts par avions</mat-card-subtitle>
|
||||
<div class="barchrt">
|
||||
<x-chartist class="" [data]="barChartAeronef.data" [type]="barChartAeronef.type" [options]="barChartAeronef.options" [responsiveOptions]="barChartAeronef.responsiveOptions"
|
||||
[events]="barChartAeronef.events"> </x-chartist>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
<hr>
|
||||
<mat-card-content>
|
||||
<table class="table table-striped table-dark table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th *ngFor='let name of seriesHeader; let i=index' class="text-right"> {{name}} </th>
|
||||
<th class="text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-mono">
|
||||
<tr *ngFor='let values of seriesRow; let i=index'>
|
||||
<th class="{{seriesColor[i]}} text-right"><i class="mdi mdi-checkbox-blank-circle font-10 m-r-10 "></i> {{seriesName[i]}} </th>
|
||||
<td *ngFor='let value of values; let i=index' class="text-right">
|
||||
<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="{{seriesColor[i]}} text-right">
|
||||
<span *ngIf="seriesValue[i]; else elseBlock" class="pr-1">{{seriesValue[i]}}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{seriesValue[i]}}</span></ng-template>
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</mat-card-content>
|
||||
<!--<hr>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>-->
|
||||
</mat-card>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AeronefsBarComponent } from './aeronefs-bar.component';
|
||||
|
||||
describe('AeronefsBarComponent', () => {
|
||||
let component: AeronefsBarComponent;
|
||||
let fixture: ComponentFixture<AeronefsBarComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AeronefsBarComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AeronefsBarComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
import { Component, OnInit} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { ChartistModule, Configuration } from 'ng-chartist';
|
||||
|
||||
import { BackendService } from 'src/app/core/services';
|
||||
import { ReduceRow } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatCardModule, ChartistModule],
|
||||
selector: 'huapp-aeronefs-bar',
|
||||
templateUrl: './aeronefs-bar.component.html'
|
||||
})
|
||||
export class AeronefsBarComponent implements OnInit {
|
||||
rows: Object[] = [] as Array<Object>;
|
||||
seriesName: string[] = [];
|
||||
seriesValue: any[] = [];
|
||||
seriesColor: string[] = this._dataService.getChartColors();
|
||||
seriesHeader: string[] = [];
|
||||
seriesRow: any[] = [];
|
||||
barChartAeronef: Configuration = {
|
||||
type: 'Bar',
|
||||
data: {
|
||||
"labels": [],
|
||||
"series": []
|
||||
},
|
||||
options: {},
|
||||
responsiveOptions: []
|
||||
};
|
||||
|
||||
constructor(private _dataService: BackendService) { }
|
||||
|
||||
loadDataByDate() {
|
||||
return this._dataService
|
||||
.getAeronefsByDate(2)
|
||||
.subscribe((res) => {
|
||||
this.seriesName.forEach((name: string) => {
|
||||
this.seriesRow.push([0, 0, 0, 0, 0, 0]);
|
||||
});
|
||||
this.seriesHeader = ["2018", "2019", "2020", "2021", "2022", "2023"];
|
||||
res["rows"].forEach((row: ReduceRow) => {
|
||||
let year = row.key[1].toString();
|
||||
let avion = row.key[0];
|
||||
this.seriesRow[this.seriesName.indexOf(avion)][this.seriesHeader.indexOf(year)] = row.value;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
return this._dataService
|
||||
.getAeronefs()
|
||||
.subscribe((res) => {
|
||||
this.rows = res["rows"]
|
||||
this.rows.forEach((row: ReduceRow) => {
|
||||
this.seriesName.push(row.key[0] + ' ' + row.key[1]);
|
||||
this.seriesValue.push([row.value]);
|
||||
});
|
||||
this.barChartAeronef = {
|
||||
type: 'Bar',
|
||||
data: {
|
||||
"labels": this.seriesName,
|
||||
"series": this.seriesValue
|
||||
},
|
||||
options: {
|
||||
chartPadding: {
|
||||
top: 15,
|
||||
right: 20,
|
||||
bottom: 0,
|
||||
left: 20
|
||||
},
|
||||
seriesBarDistance: 15,
|
||||
horizontalBars: true,
|
||||
axisX: {
|
||||
position: 'end',
|
||||
labelOffset: {
|
||||
x: -5,
|
||||
y: 0
|
||||
},
|
||||
showGrid: true,
|
||||
offset: 20
|
||||
},
|
||||
axisY: {
|
||||
showGrid: false,
|
||||
offset: 0,
|
||||
labelInterpolationFnc: function(): string {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
height: (this.seriesName.length*20)
|
||||
},
|
||||
responsiveOptions: [
|
||||
['screen and (min-width: 1024px)', {
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: number, index: number): string {
|
||||
return index % 1 === 0 ? `${value}` : '';
|
||||
}
|
||||
}
|
||||
}],
|
||||
['screen and (min-width: 641px) and (max-width: 1024px)', {
|
||||
seriesBarDistance: 10,
|
||||
axisY: {
|
||||
offset: 30
|
||||
},
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: any, index: number): string {
|
||||
return index % 1 === 0 ? `${value[2]}${value[3]}` : '';
|
||||
}
|
||||
}
|
||||
}],
|
||||
['screen and (max-width: 640px)', {
|
||||
seriesBarDistance: 5,
|
||||
axisY: {
|
||||
offset: 20
|
||||
},
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: any, index: number): string {
|
||||
return index % 1 === 0 ? `${value[2]}${value[3]}` : '';
|
||||
}
|
||||
}
|
||||
}]
|
||||
]
|
||||
};
|
||||
return this.loadDataByDate();
|
||||
});
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<mat-card-title>Aéronefs</mat-card-title>
|
||||
<mat-card-subtitle>Nombre total de sauts par avions</mat-card-subtitle>
|
||||
<div class="piechart">
|
||||
<x-chartist [configuration]="donuteChartModel"> </x-chartist>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
<hr>
|
||||
<mat-card-content>
|
||||
<table class="table table-striped table-dark table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th *ngFor='let name of seriesHeader; let i=index' class="text-right"> {{name}} </th>
|
||||
<th class="text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor='let values of seriesRow; let i=index'>
|
||||
<th class="{{seriesColor[i]}} text-right"><i class="mdi mdi-checkbox-blank-circle font-10 m-r-10 "></i> {{seriesName[i]}} </th>
|
||||
<td *ngFor='let value of values; let i=index' class="text-mono text-right">
|
||||
<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="{{seriesColor[i]}} text-right">
|
||||
<span *ngIf="seriesValue[i]; else elseBlock" class="pr-1">{{seriesValue[i]}}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{seriesValue[i]}}</span></ng-template>
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</mat-card-content>
|
||||
<!--<hr>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>-->
|
||||
</mat-card>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AeronefsComponent } from './aeronefs.component';
|
||||
|
||||
describe('AeronefsComponent', () => {
|
||||
let component: AeronefsComponent;
|
||||
let fixture: ComponentFixture<AeronefsComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AeronefsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AeronefsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
import { Component, OnInit} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { ChartistModule, Configuration } from 'ng-chartist';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { BackendService, AeronefsService, UserService } from 'src/app/core/services';
|
||||
import { Aeronef, AeronefAggregate } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatCardModule, ChartistModule],
|
||||
selector: 'huapp-aeronefs',
|
||||
templateUrl: './aeronefs.component.html'
|
||||
})
|
||||
export class AeronefsComponent implements OnInit {
|
||||
private _aeronefs: Subscription = new Subscription();
|
||||
aggregate!: Array<AeronefAggregate>
|
||||
aeronefs!: Array<Aeronef>;
|
||||
aeronefsCount = 0;
|
||||
rows: Object[] = [] as Array<Object>;
|
||||
seriesName: string[] = [];//= ["Arcachon", "Béni-Mellal", "La Réole", "Royan", "Pamiers", "Soulac", "Sables d'Olonne", "Rochefort"];
|
||||
seriesValue: number[] = [];
|
||||
seriesColor: string[] = this._dataService.getChartColors(); //["blue", "red", "turquoise", "orange", "cyan", "purple-dark", "megna", "raspberry"];
|
||||
seriesHeader: string[] = [];
|
||||
seriesRow: any[] = [];
|
||||
donuteChartModel: Configuration = {
|
||||
type: 'Pie',
|
||||
data: {
|
||||
"labels": [],
|
||||
"series": []
|
||||
},
|
||||
options: {
|
||||
donut: true,
|
||||
height: 260,
|
||||
donutWidth: 40,
|
||||
startAngle: 270,
|
||||
showLabel: true
|
||||
}
|
||||
};
|
||||
|
||||
constructor(
|
||||
private aeronefsService: AeronefsService,
|
||||
private _dataService: BackendService
|
||||
) { }
|
||||
|
||||
/*loadDataByDate() {
|
||||
return this._dataService
|
||||
.getAeronefsByDate(2)
|
||||
.subscribe((res) => {
|
||||
this.seriesName.forEach((name: string) => {
|
||||
this.seriesRow.push([0, 0, 0, 0, 0, 0]);
|
||||
});
|
||||
this.seriesHeader = ["2018", "2019", "2020", "2021", "2022", "2023"];
|
||||
res["rows"].forEach((row: ReduceRow) => {
|
||||
let year = row.key[1].toString();
|
||||
let avion = row.key[0];
|
||||
this.seriesRow[this.seriesName.indexOf(avion)][this.seriesHeader.indexOf(year)] = row.value;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}*/
|
||||
|
||||
ngOnInit() {
|
||||
this.loadAeronefs();
|
||||
return this._dataService
|
||||
.getAeronefs()
|
||||
.subscribe((res) => {
|
||||
console.log(res);
|
||||
/* this.rows = res["rows"]
|
||||
var labels: string[] = [];
|
||||
var series: number[] = [];
|
||||
this.rows.forEach((row: ReduceRow) => {
|
||||
this.seriesName.push(row.key[0] + ' ' + row.key[1]);
|
||||
this.seriesValue.push(row.value);
|
||||
//console.log(row.key, row.value);
|
||||
labels.push(row.value.toString());
|
||||
series.push(row.value);
|
||||
});
|
||||
this.donuteChartModel = {
|
||||
type: 'Pie',
|
||||
data: {
|
||||
"labels": labels,
|
||||
"series": series
|
||||
},
|
||||
options: {
|
||||
donut: true,
|
||||
height: 260,
|
||||
donutWidth: 40,
|
||||
startAngle: 270,
|
||||
showLabel: true
|
||||
}
|
||||
//,
|
||||
// events: <ChartEvent>{
|
||||
// draw: (data) => {
|
||||
// //this.donuteChartModel.data = data;
|
||||
// console.log(data);
|
||||
// }
|
||||
// }
|
||||
};
|
||||
return this.loadDataByDate(); */
|
||||
});
|
||||
}
|
||||
|
||||
loadAeronefs() {
|
||||
const aeronefs$: Observable<Array<AeronefAggregate>> = this.aeronefsService.getAll();
|
||||
this._aeronefs = aeronefs$.subscribe((aggregate: Array<AeronefAggregate>) => {
|
||||
this.aggregate = aggregate;
|
||||
this.aeronefsCount = aggregate.length;
|
||||
var labels: string[] = [];
|
||||
var series: number[] = [];
|
||||
this.aggregate.forEach((row) => {
|
||||
this.seriesName.push(row._id.aeronef + ' ' + row._id.imat);
|
||||
this.seriesValue.push(row.count);
|
||||
labels.push(row.count.toString());
|
||||
//labels.push(row._id.aeronef + ' ' + row._id.imat);
|
||||
series.push(row.count);
|
||||
});
|
||||
this.donuteChartModel = {
|
||||
type: 'Pie',
|
||||
data: {
|
||||
"labels": labels,
|
||||
"series": series
|
||||
},
|
||||
options: {
|
||||
donut: true,
|
||||
height: 260,
|
||||
donutWidth: 50,
|
||||
startAngle: 270,
|
||||
showLabel: true
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<mat-card-title>Modèles de voile</mat-card-title>
|
||||
<mat-card-subtitle>Nombre total de sauts par modèle</mat-card-subtitle>
|
||||
<div class="piechart">
|
||||
<x-chartist [configuration]="donuteCanopyModel"> </x-chartist>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
<hr>
|
||||
<mat-card-content>
|
||||
<table class="table table-striped table-dark table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th *ngFor='let name of seriesHeader; let i=index' class="text-right"> {{name}} </th>
|
||||
<th class="text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-mono">
|
||||
<tr *ngFor='let values of seriesRow; let i=index'>
|
||||
<th class="{{seriesColor[i]}} text-right"><i class="mdi mdi-checkbox-blank-circle font-10 m-r-10 "></i> {{seriesName[i]}} </th>
|
||||
<td *ngFor='let value of values; let i=index' class="text-right">
|
||||
<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="{{seriesColor[i]}} text-right">
|
||||
<span *ngIf="seriesValue[i]; else elseBlock" class="pr-1">{{seriesValue[i]}}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{seriesValue[i]}}</span></ng-template>
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CanopyModelsComponent } from './canopy-models.component';
|
||||
|
||||
describe('CanopyModelsComponent', () => {
|
||||
let component: CanopyModelsComponent;
|
||||
let fixture: ComponentFixture<CanopyModelsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CanopyModelsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CanopyModelsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
import { Component, OnInit} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { ChartistModule, Configuration } from 'ng-chartist';
|
||||
|
||||
import { Observable, Observer, Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { BackendService, CanopiesService } from 'src/app/core/services';
|
||||
import { Canopy, CanopyAggregate, ReduceRow } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatCardModule, ChartistModule],
|
||||
selector: 'huapp-canopy-models',
|
||||
templateUrl: './canopy-models.component.html'
|
||||
})
|
||||
export class CanopyModelsComponent implements OnInit {
|
||||
private _canopies: Subscription = new Subscription();
|
||||
aggregate!: Array<CanopyAggregate>
|
||||
canopies!: Array<Canopy>;
|
||||
canopiesCount = 0;
|
||||
rows: Object[] = [] as Array<Object>;
|
||||
seriesName: string[] = [];
|
||||
seriesValue: number[] = [];
|
||||
seriesColor: string[] = this._dataService.getChartColors();
|
||||
seriesHeader: string[] = [];
|
||||
seriesRow: any[] = [];
|
||||
donuteCanopyModel: Configuration = {
|
||||
type: 'Pie',
|
||||
data: {
|
||||
"labels": [],
|
||||
"series": []
|
||||
},
|
||||
options: {
|
||||
donut: true,
|
||||
height: 260,
|
||||
donutWidth: 40,
|
||||
startAngle: 270,
|
||||
showLabel: true
|
||||
}
|
||||
};
|
||||
|
||||
constructor(
|
||||
private canopiesService: CanopiesService,
|
||||
private _dataService: BackendService
|
||||
) { }
|
||||
|
||||
/*
|
||||
loadDataByDate() {
|
||||
return this._dataService
|
||||
.getCanopyModelsByDate(2)
|
||||
.subscribe((res) => {
|
||||
this.seriesName.forEach((name: string) => {
|
||||
this.seriesRow.push([0, 0, 0, 0, 0, 0]);
|
||||
});
|
||||
this.seriesHeader = ["2018", "2019", "2020", "2021", "2022", "2023"];
|
||||
res["rows"].forEach((row: ReduceRow) => {
|
||||
let year = row.key[1].toString();
|
||||
let voile = row.key[0];
|
||||
this.seriesRow[this.seriesName.indexOf(voile)][this.seriesHeader.indexOf(year)] = row.value;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
ngOnInit() {
|
||||
this.loadCanopies();
|
||||
/*
|
||||
return this._dataService
|
||||
.getCanopyModels()
|
||||
.subscribe((res) => {
|
||||
this.rows = res["rows"]
|
||||
var labels: string[] = [];
|
||||
var series: number[] = [];
|
||||
this.rows.forEach((row: ReduceRow) => {
|
||||
this.seriesName.push(row.key);
|
||||
this.seriesValue.push(row.value);
|
||||
labels.push(row.value.toString());
|
||||
series.push(row.value);
|
||||
});
|
||||
this.donuteCanopyModel = {
|
||||
type: 'Pie',
|
||||
data: {
|
||||
"labels": labels,
|
||||
"series": series
|
||||
},
|
||||
options: {
|
||||
donut: true,
|
||||
height: 260,
|
||||
donutWidth: 40,
|
||||
donutSolid: true,
|
||||
startAngle: 270,
|
||||
showLabel: true
|
||||
}
|
||||
};
|
||||
return this.loadDataByDate();
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
loadCanopies() {
|
||||
const canopies$: Observable<Array<CanopyAggregate>> = this.canopiesService.getAll();
|
||||
this._canopies = canopies$.subscribe((aggregate: Array<CanopyAggregate>) => {
|
||||
this.aggregate = aggregate;
|
||||
//this.canopies = canopies;
|
||||
this.canopiesCount = aggregate.length;
|
||||
var labels: string[] = [];
|
||||
var series: number[] = [];
|
||||
this.aggregate.forEach((row) => {
|
||||
this.seriesName.push(row._id.canopy + ' ' + row._id.taille);
|
||||
this.seriesValue.push(row.count);
|
||||
labels.push(row.count.toString());
|
||||
series.push(row.count);
|
||||
});
|
||||
this.donuteCanopyModel = {
|
||||
type: 'Pie',
|
||||
data: {
|
||||
"labels": labels,
|
||||
"series": series
|
||||
},
|
||||
options: {
|
||||
donut: true,
|
||||
height: 260,
|
||||
donutWidth: 50,
|
||||
startAngle: 270,
|
||||
showLabel: true
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<mat-card-title>Taille de voile</mat-card-title>
|
||||
<mat-card-subtitle>Nombre total de sauts par taille</mat-card-subtitle>
|
||||
<div class="piechart">
|
||||
<x-chartist class="" [data]="donuteChartSize.data" [type]="donuteChartSize.type"
|
||||
[options]="donuteChartSize.options" [responsiveOptions]="donuteChartSize.responsiveOptions"
|
||||
[events]="donuteChartSize.events"> </x-chartist>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
<hr>
|
||||
<mat-card-content>
|
||||
<table class="table table-striped table-dark table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th *ngFor='let name of seriesHeader; let i=index' class="text-right"> {{name}} </th>
|
||||
<th class="text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-mono">
|
||||
<tr *ngFor='let values of seriesRow; let i=index'>
|
||||
<th class="{{seriesColor[i]}} text-right"><i class="mdi mdi-checkbox-blank-circle font-10 m-r-10 "></i> {{seriesName[i]}} </th>
|
||||
<td *ngFor='let value of values; let i=index' class="text-right">
|
||||
<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="{{seriesColor[i]}} text-right">
|
||||
<span *ngIf="seriesValue[i]; else elseBlock" class="pr-1">{{seriesValue[i]}}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{seriesValue[i]}}</span></ng-template>
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--<div fxLayout="row wrap">
|
||||
<div fxLayout="row wrap">
|
||||
<h4 class="{{seriesColor[i]}} m-0" *ngFor='let name of seriesName; let i=index' fxFlex.gt-lg="25" fxFlex.gt-md="50" fxFlex.gt-sm="50" fxFlex.gt-xs="25" fxFlex="100">
|
||||
<i class="mdi mdi-checkbox-blank-circle font-10 m-r-10 "></i>{{name}} ft² : {{seriesValue[i]}}
|
||||
</h4>
|
||||
</div>
|
||||
</div>-->
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CanopySizesComponent } from './canopy-sizes.component';
|
||||
|
||||
describe('CanopySizesComponent', () => {
|
||||
let component: CanopySizesComponent;
|
||||
let fixture: ComponentFixture<CanopySizesComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CanopySizesComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CanopySizesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { ChartistModule, Configuration } from 'ng-chartist';
|
||||
|
||||
import { BackendService } from 'src/app/core/services';
|
||||
import { ReduceRow } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatCardModule, ChartistModule],
|
||||
selector: 'huapp-canopy-sizes',
|
||||
templateUrl: './canopy-sizes.component.html'
|
||||
})
|
||||
export class CanopySizesComponent implements OnInit {
|
||||
rows: Object[] = [] as Array<Object>;
|
||||
seriesName: string[] = [];
|
||||
seriesValue: number[] = [];
|
||||
seriesColor: string[] = this._dataService.getChartColors();
|
||||
seriesHeader: string[] = [];
|
||||
seriesRow: any[] = [];
|
||||
donuteChartSize: Configuration = {
|
||||
type: 'Pie',
|
||||
data: {
|
||||
"labels": [],
|
||||
"series": []
|
||||
},
|
||||
options: {
|
||||
donut: true,
|
||||
height: 260,
|
||||
donutWidth: 40,
|
||||
startAngle: 0,
|
||||
showLabel: true
|
||||
}
|
||||
};
|
||||
|
||||
constructor(private _dataService: BackendService) { }
|
||||
|
||||
loadDataByDate() {
|
||||
return this._dataService
|
||||
.getCanopySizesByDate(2)
|
||||
.subscribe((res) => {
|
||||
this.seriesName.forEach((name: string) => {
|
||||
this.seriesRow.push([0, 0, 0, 0, 0, 0]);
|
||||
});
|
||||
this.seriesHeader = ["2018", "2019", "2020", "2021", "2022", "2023"];
|
||||
res["rows"].forEach((row: ReduceRow) => {
|
||||
let year = row.key[1].toString();
|
||||
let size = row.key[0];
|
||||
this.seriesRow[this.seriesName.indexOf(size)][this.seriesHeader.indexOf(year)] = row.value;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
return this._dataService
|
||||
.getCanopySizes()
|
||||
.subscribe((res) => {
|
||||
this.rows = res["rows"]
|
||||
var labels: string[] = [];
|
||||
var series: number[] = [];
|
||||
this.rows.forEach((row: ReduceRow) => {
|
||||
this.seriesName.push(row.key);
|
||||
this.seriesValue.push(row.value);
|
||||
labels.push(row.value.toString());
|
||||
series.push(row.value);
|
||||
});
|
||||
this.donuteChartSize = {
|
||||
type: 'Pie',
|
||||
data: {
|
||||
"labels": labels,
|
||||
"series": series
|
||||
},
|
||||
options: {
|
||||
donut: true,
|
||||
height: 260,
|
||||
donutWidth: 40,
|
||||
donutSolid: true,
|
||||
startAngle: 330,
|
||||
showLabel: true
|
||||
}
|
||||
};
|
||||
return this.loadDataByDate();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"Bar": {
|
||||
"labels": ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
|
||||
"series": [
|
||||
[0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 7, 1, 17, 22, 12, 18, 0, 0],
|
||||
[0, 35, 8, 0, 0, 10, 31, 67, 26, 24, 0, 0],
|
||||
[0, 0, 0, 0, 0, 9, 8, 0, 11, 24, 17, 4],
|
||||
[0, 0, 0, 29, 37, 11, 22, 20, 17, 13, 13, 0],
|
||||
[0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
"Pie": {
|
||||
"labels": ["261", "45", "178", "16", "13", "11", "4", "2"],
|
||||
"series": [261, 45, 178, 16, 13, 11, 4, 2],
|
||||
"names": ["Arcachon", "Béni-Mellal", "La Réole", "Royan", "Pamiers", "Soulac", "Sables d'Olonne", "Rochefort"]
|
||||
}
|
||||
}
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<mat-card-title>Les drop zones</mat-card-title>
|
||||
<mat-card-subtitle>Nombre total de sauts par DZ</mat-card-subtitle>
|
||||
<div class="piechart">
|
||||
<x-chartist [configuration]="donuteDropZones"> </x-chartist>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
<hr>
|
||||
<mat-card-content class="mb-1">
|
||||
<div fxLayout="row wrap">
|
||||
<div fxLayout="row wrap">
|
||||
<h4 class="{{seriesColor[i]}} m-0" *ngFor='let name of seriesName; let i=index' fxFlex.gt-lg="33" fxFlex.gt-md="50" fxFlex.gt-sm="100" fxFlex.gt-xs="25" fxFlex="100">
|
||||
<i class="mdi mdi-checkbox-blank-circle font-10 m-r-10 "></i>{{name}} : {{seriesValue[i]}}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
<hr>
|
||||
<mat-card-content>
|
||||
<div class="barchrt bar-xs">
|
||||
<x-chartist [configuration]="barChartDropZones"> </x-chartist>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
Executable
Executable
+25
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DropZonesBarComponent } from './drop-zones-bar.component';
|
||||
|
||||
describe('DropZonesBarComponent', () => {
|
||||
let component: DropZonesBarComponent;
|
||||
let fixture: ComponentFixture<DropZonesBarComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DropZonesBarComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DropZonesBarComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
import { Component, OnInit, Input, Output } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { ChartistModule, Configuration } from 'ng-chartist';
|
||||
|
||||
import { BackendService } from 'src/app/core/services';
|
||||
import { DropZone, ReduceRow} from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatCardModule, ChartistModule],
|
||||
selector: 'huapp-drop-zones-bar',
|
||||
templateUrl: './drop-zones-bar.component.html'
|
||||
})
|
||||
export class DropZonesBarComponent implements OnInit {
|
||||
dropzones: Object[] = [] as Array<Object>;
|
||||
seriesName: string[] = [];//= ["Arcachon", "Béni-Mellal", "La Réole", "Royan", "Pamiers", "Soulac", "Sables d'Olonne", "Rochefort"];
|
||||
seriesValue: number[] = [];
|
||||
seriesColor: string[] = this._dataService.getChartColors();
|
||||
seriesHeader: string[] = [];
|
||||
seriesRow: any[] = [];
|
||||
donuteDropZones: Configuration = {
|
||||
type: 'Pie',
|
||||
/*
|
||||
data: {
|
||||
"labels": ["261", "45", "178", "16", "13", "11", "4", "2"],
|
||||
"series": [261, 45, 178, 16, 13, 11, 4, 2]
|
||||
},
|
||||
*/
|
||||
data: {
|
||||
"labels": [],
|
||||
"series": []
|
||||
},
|
||||
options: {
|
||||
donut: true,
|
||||
height: 260,
|
||||
donutWidth: 40,
|
||||
startAngle: 270,
|
||||
showLabel: true
|
||||
}
|
||||
};
|
||||
barChartDropZones: Configuration = {
|
||||
type: 'Bar',
|
||||
data: {
|
||||
"labels": [],
|
||||
"series": []
|
||||
},
|
||||
options: {},
|
||||
responsiveOptions: []
|
||||
};
|
||||
|
||||
constructor(private _dataService: BackendService) { }
|
||||
|
||||
loadDataByDate() {
|
||||
return this._dataService
|
||||
.getDropZonesByDate(2)
|
||||
.subscribe((res) => {
|
||||
console.log(res["rows"]);
|
||||
this.seriesName.forEach((name: string) => {
|
||||
this.seriesRow.push([0, 0, 0, 0, 0, 0]);
|
||||
});
|
||||
this.seriesHeader = ["2018", "2019", "2020", "2021", "2022", "2023"];
|
||||
res["rows"].forEach((row: ReduceRow) => {
|
||||
let year = row.key[1].toString();
|
||||
let lieu = row.key[0];
|
||||
this.seriesRow[this.seriesName.indexOf(lieu)][this.seriesHeader.indexOf(year)] = row.value;
|
||||
//console.log(row.key, row.value);
|
||||
});
|
||||
//console.log(this.seriesName);
|
||||
//console.log(this.seriesHeader);
|
||||
//console.log(this.seriesRow);
|
||||
this.barChartDropZones = {
|
||||
type: 'Bar',
|
||||
data: {
|
||||
"labels": this.seriesHeader,
|
||||
"series": this.seriesRow
|
||||
},
|
||||
options: {
|
||||
seriesBarDistance: 9,
|
||||
horizontalBars: true,
|
||||
high: 170,
|
||||
axisX: {
|
||||
showGrid: false,
|
||||
offset: 20
|
||||
},
|
||||
axisY: {
|
||||
showGrid: true,
|
||||
offset: 40
|
||||
},
|
||||
height: 207
|
||||
},
|
||||
responsiveOptions: [
|
||||
['screen and (min-width: 1024px)', {
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: number, index: number): string {
|
||||
return index % 1 === 0 ? `${value}` : '';
|
||||
}
|
||||
}
|
||||
}],
|
||||
['screen and (min-width: 641px) and (max-width: 1024px)', {
|
||||
seriesBarDistance: 7,
|
||||
axisY: {
|
||||
offset: 30
|
||||
},
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: any, index: number): string {
|
||||
return index % 1 === 0 ? `${value[2]}${value[3]}` : '';
|
||||
}
|
||||
}
|
||||
}],
|
||||
['screen and (max-width: 640px)', {
|
||||
seriesBarDistance: 5,
|
||||
axisY: {
|
||||
offset: 20
|
||||
},
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: any, index: number): string {
|
||||
return index % 1 === 0 ? `${value[2]}${value[3]}` : '';
|
||||
}
|
||||
}
|
||||
}]
|
||||
]
|
||||
};
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
return this._dataService
|
||||
.getDropZones()
|
||||
.subscribe((res) => {
|
||||
//console.log('ngOnInit');
|
||||
//console.log(res["rows"]);
|
||||
this.dropzones = res["rows"]
|
||||
var labels: string[] = [];
|
||||
var series: number[] = [];
|
||||
this.dropzones.forEach((dropzone: DropZone) => {
|
||||
this.seriesName.push(dropzone.key);
|
||||
this.seriesValue.push(dropzone.value);
|
||||
//console.log(dropzone.key, dropzone.value);
|
||||
labels.push(dropzone.value.toString());
|
||||
series.push(dropzone.value);
|
||||
});
|
||||
this.donuteDropZones = {
|
||||
type: 'Pie',
|
||||
data: {
|
||||
"labels": labels,
|
||||
"series": series
|
||||
},
|
||||
options: {
|
||||
donut: true,
|
||||
height: 260,
|
||||
donutWidth: 40,
|
||||
donutSolid: true,
|
||||
startAngle: 270,
|
||||
showLabel: true
|
||||
}
|
||||
/*,
|
||||
events: <ChartEvent>{
|
||||
draw: (data) => {
|
||||
//this.donuteDropZones.data = data;
|
||||
console.log(data);
|
||||
}
|
||||
}
|
||||
*/
|
||||
};
|
||||
return this.loadDataByDate();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"Bar": {
|
||||
"labels": ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
|
||||
"series": [
|
||||
[0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 7, 1, 17, 22, 12, 18, 0, 0],
|
||||
[0, 35, 8, 0, 0, 10, 31, 67, 26, 24, 0, 0],
|
||||
[0, 0, 0, 0, 0, 9, 8, 0, 11, 24, 17, 4],
|
||||
[0, 0, 0, 29, 37, 11, 22, 20, 17, 13, 13, 0],
|
||||
[0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
"Pie": {
|
||||
"labels": ["261", "45", "178", "16", "13", "11", "4", "2"],
|
||||
"series": [261, 45, 178, 16, 13, 11, 4, 2],
|
||||
"names": ["Arcachon", "Béni-Mellal", "La Réole", "Royan", "Pamiers", "Soulac", "Sables d'Olonne", "Rochefort"]
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<mat-card-title>Les drop zones</mat-card-title>
|
||||
<mat-card-subtitle>Nombre total de sauts par DZ</mat-card-subtitle>
|
||||
<div class="piechart">
|
||||
<x-chartist [configuration]="donuteDropZones"> </x-chartist>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
<hr>
|
||||
<mat-card-content>
|
||||
<table class="table table-striped table-dark table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th *ngFor='let name of seriesHeader; let i=index' class="text-right"> {{name}} </th>
|
||||
<th class="text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-mono">
|
||||
<tr *ngFor='let values of seriesRow; let i=index'>
|
||||
<th class="{{seriesColor[i]}} text-right"><i class="mdi mdi-checkbox-blank-circle font-10 m-r-10 "></i> {{seriesName[i]}} </th>
|
||||
<td *ngFor='let value of values; let i=index' class="text-right">
|
||||
<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="{{seriesColor[i]}} text-right">
|
||||
<span *ngIf="seriesValue[i]; else elseBlock" class="pr-1">{{seriesValue[i]}}</span>
|
||||
<ng-template #elseBlock><span class="text-empty pr-1">{{seriesValue[i]}}</span></ng-template>
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DropZonesComponent } from './drop-zones.component';
|
||||
|
||||
describe('DropZonesComponent', () => {
|
||||
let component: DropZonesComponent;
|
||||
let fixture: ComponentFixture<DropZonesComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DropZonesComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DropZonesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
import { Component, OnInit, Input, Output } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { ChartistModule, Configuration } from 'ng-chartist';
|
||||
|
||||
import { Observable, Observer, Subscription } from 'rxjs';
|
||||
|
||||
import { BackendService, DropZonesService } from 'src/app/core/services';
|
||||
import { DropZone, DropZoneAggregate, ReduceRow} from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatCardModule, ChartistModule],
|
||||
selector: 'huapp-drop-zones',
|
||||
templateUrl: './drop-zones.component.html'
|
||||
})
|
||||
export class DropZonesComponent implements OnInit {
|
||||
private _dropzones: Subscription = new Subscription();
|
||||
aggregate!: Array<DropZoneAggregate>
|
||||
dropzones!: Array<DropZone>;
|
||||
dropzonesCount = 0;
|
||||
seriesName: string[] = [];//= ["Arcachon", "Béni-Mellal", "La Réole", "Royan", "Pamiers", "Soulac", "Sables d'Olonne", "Rochefort"];
|
||||
seriesValue: number[] = [];
|
||||
seriesColor: string[] = this._dataService.getChartColors();
|
||||
seriesHeader: string[] = [];
|
||||
seriesRow: any[] = [];
|
||||
donuteDropZones: Configuration = {
|
||||
type: 'Pie',
|
||||
/*
|
||||
data: {
|
||||
"labels": ["261", "45", "178", "16", "13", "11", "4", "2"],
|
||||
"series": [261, 45, 178, 16, 13, 11, 4, 2]
|
||||
},
|
||||
*/
|
||||
data: {
|
||||
"labels": [],
|
||||
"series": []
|
||||
},
|
||||
options: {
|
||||
donut: true,
|
||||
height: 260,
|
||||
donutWidth: 40,
|
||||
startAngle: 270,
|
||||
showLabel: true
|
||||
}
|
||||
};
|
||||
barChartDropZones: Configuration = {
|
||||
type: 'Bar',
|
||||
data: {
|
||||
"labels": [],
|
||||
"series": []
|
||||
},
|
||||
options: {
|
||||
seriesBarDistance: 5,
|
||||
high: 170,
|
||||
axisX: {
|
||||
showGrid: false,
|
||||
offset: 20
|
||||
},
|
||||
axisY: {
|
||||
showGrid: true,
|
||||
offset: 40
|
||||
},
|
||||
height: 207
|
||||
},
|
||||
responsiveOptions: [
|
||||
['screen and (min-width: 1024px)', {
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: number, index: number): string {
|
||||
return index % 1 === 0 ? `${value}` : '';
|
||||
}
|
||||
}
|
||||
}],
|
||||
['screen and (min-width: 641px) and (max-width: 1024px)', {
|
||||
seriesBarDistance: 10,
|
||||
axisY: {
|
||||
offset: 30
|
||||
},
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: any, index: number): string {
|
||||
return index % 1 === 0 ? `${value[2]}${value[3]}` : '';
|
||||
}
|
||||
}
|
||||
}],
|
||||
['screen and (max-width: 640px)', {
|
||||
seriesBarDistance: 5,
|
||||
axisY: {
|
||||
offset: 20
|
||||
},
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: any, index: number): string {
|
||||
return index % 1 === 0 ? `${value[2]}${value[3]}` : '';
|
||||
}
|
||||
}
|
||||
}]
|
||||
]
|
||||
};
|
||||
constructor(
|
||||
private dropzonesService: DropZonesService,
|
||||
private _dataService: BackendService
|
||||
) { }
|
||||
ngOnInit() {
|
||||
this.loadDropZones();
|
||||
/*
|
||||
return this._dataService
|
||||
.getDropZones()
|
||||
.subscribe((res) => {
|
||||
this.dropzones = res["rows"]
|
||||
var labels: string[] = [];
|
||||
var series: number[] = [];
|
||||
this.dropzones.forEach((dropzone: DropZone) => {
|
||||
this.seriesName.push(dropzone.key);
|
||||
this.seriesValue.push(dropzone.value);
|
||||
labels.push(dropzone.value.toString());
|
||||
series.push(dropzone.value);
|
||||
});
|
||||
this.donuteDropZones = {
|
||||
type: 'Pie',
|
||||
data: {
|
||||
"labels": labels,
|
||||
"series": series
|
||||
},
|
||||
options: {
|
||||
donut: true,
|
||||
height: 260,
|
||||
donutWidth: 40,
|
||||
donutSolid: true,
|
||||
startAngle: 270,
|
||||
showLabel: true
|
||||
}
|
||||
,
|
||||
// events: <ChartEvent>{
|
||||
// draw: (data) => {
|
||||
// //this.donuteDropZones.data = data;
|
||||
// console.log(data);
|
||||
// }
|
||||
// }
|
||||
};
|
||||
return this._dataService
|
||||
.getDropZonesByDate(2)
|
||||
.subscribe((res) => {
|
||||
this.seriesName.forEach((name: string) => {
|
||||
this.seriesRow.push([0, 0, 0, 0, 0, 0]);
|
||||
});
|
||||
this.seriesHeader = ["2018", "2019", "2020", "2021", "2022", "2023"];
|
||||
res["rows"].forEach((row: ReduceRow) => {
|
||||
let year = row.key[1].toString();
|
||||
let lieu = row.key[0];
|
||||
this.seriesRow[this.seriesName.indexOf(lieu)][this.seriesHeader.indexOf(year)] = row.value;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
loadDropZones() {
|
||||
const dropzones$: Observable<Array<DropZoneAggregate>> = this.dropzonesService.getAll();
|
||||
this._dropzones = dropzones$.subscribe((aggregate: Array<DropZoneAggregate>) => {
|
||||
this.aggregate = aggregate;
|
||||
//this.dropzones = dropzones;
|
||||
this.dropzonesCount = aggregate.length;
|
||||
var labels: string[] = [];
|
||||
var series: number[] = [];
|
||||
this.aggregate.forEach((row) => {
|
||||
this.seriesName.push(row._id.lieu + ' ' + row._id.oaci);
|
||||
this.seriesValue.push(row.count);
|
||||
labels.push(row.count.toString());
|
||||
series.push(row.count);
|
||||
});
|
||||
this.donuteDropZones = {
|
||||
type: 'Pie',
|
||||
data: {
|
||||
"labels": labels,
|
||||
"series": series
|
||||
},
|
||||
options: {
|
||||
donut: true,
|
||||
height: 260,
|
||||
donutWidth: 50,
|
||||
startAngle: 270,
|
||||
showLabel: true
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
//export * from './jumps-by-month/jumps-by-month.component';
|
||||
export * from './drop-zones/drop-zones.component';
|
||||
//export * from './drop-zones-bar/drop-zones-bar.component';
|
||||
export * from './aeronefs/aeronefs.component';
|
||||
//export * from './aeronefs-bar/aeronefs-bar.component';
|
||||
//export * from './canopy-sizes/canopy-sizes.component';
|
||||
export * from './canopy-models/canopy-models.component';
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"Bar": {
|
||||
"labels": ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
|
||||
"series": [
|
||||
[0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 7, 1, 17, 22, 12, 18, 0, 0],
|
||||
[0, 35, 8, 0, 0, 10, 31, 67, 26, 24, 0, 0],
|
||||
[0, 0, 0, 0, 0, 9, 8, 0, 11, 24, 17, 4],
|
||||
[0, 0, 0, 29, 37, 11, 22, 20, 17, 13, 13, 0],
|
||||
[0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
]
|
||||
},
|
||||
"Pie": {
|
||||
"labels": ["Arcachon", "Béni-Mellal", "La Réole", "Royan", "Pamiers", "Soulac", "Sables d'Olonne", "Rochefort"],
|
||||
"series": [261, 45, 178, 16, 13, 11, 4, 2]
|
||||
}
|
||||
}
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
<mat-card>
|
||||
<mat-card-content class="mb-0">
|
||||
<div class="d-flex flex-wrap">
|
||||
<div>
|
||||
<mat-card-title>Volume mensuel</mat-card-title>
|
||||
<mat-card-subtitle>Nombre total de sauts par mois de {{min}} à {{max}}</mat-card-subtitle>
|
||||
</div>
|
||||
</div>
|
||||
<div class="barchrt">
|
||||
<x-chartist class="" [data]="barChartJumps.data" [type]="barChartJumps.type" [options]="barChartJumps.options" [responsiveOptions]="barChartJumps.responsiveOptions"
|
||||
[events]="barChartJumps.events"> </x-chartist>
|
||||
</div>
|
||||
<div class="text-center mt-3 mb-2">
|
||||
<ul class="list-inline my-0">
|
||||
<li *ngFor='let name of seriesName; let i=index'>
|
||||
<h4 class="{{seriesColor[i]}} m-0">
|
||||
<i class="mdi mdi-checkbox-blank-circle font-10 m-r-10 "></i>{{name}}
|
||||
</h4>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
<mat-card-content class="pt-0">
|
||||
<table class="table table-striped table-dark table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th *ngFor='let name of seriesHeader; let i=index' class="text-right"> {{name}} </th>
|
||||
<th class="text-right">Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor='let values of seriesRow; let i=index'>
|
||||
<th class="{{seriesColor[i]}} text-right"><i class="mdi mdi-checkbox-blank-circle font-10 m-r-10 "></i> {{seriesName[i]}} </th>
|
||||
<td *ngFor='let value of values; let i=index' class="text-mono text-right">
|
||||
<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="text-mono text-right">{{seriesRowTotal[i]}}</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th class="text-right">Total</th>
|
||||
<th *ngFor='let value of seriesColTotal; let i=index' class="text-mono text-right">
|
||||
<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="text-mono text-right">{{grandTotal}}</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
Executable
Executable
+25
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { JumpsByMonthComponent } from './jumps-by-month.component';
|
||||
|
||||
describe('JumpsByMonthComponent', () => {
|
||||
let component: JumpsByMonthComponent;
|
||||
let fixture: ComponentFixture<JumpsByMonthComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ JumpsByMonthComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(JumpsByMonthComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { ChartistModule, Configuration } from 'ng-chartist';
|
||||
|
||||
import { BackendService } from 'src/app/core/services';
|
||||
import { ReduceRow } from 'src/app/core/models';
|
||||
|
||||
declare var require: any;
|
||||
const data= require('./data.json');
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [MatCardModule, ChartistModule],
|
||||
selector: 'huapp-jumps-by-month',
|
||||
templateUrl: './jumps-by-month.component.html'
|
||||
})
|
||||
export class JumpsByMonthComponent implements OnInit {
|
||||
rows: Object[] = [] as Array<Object>;
|
||||
min: number = 0;
|
||||
max: number = 0;
|
||||
grandTotal: number = 0;
|
||||
seriesHeader: string[] = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"];
|
||||
seriesRow: any[] = [];
|
||||
seriesRowTotal: number[] = [];
|
||||
seriesColTotal: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
seriesName: String[] = []; // ["2018", "2019", "2020", "2021", "2022", "2023"];
|
||||
seriesColor: String[] = this._dataService.getChartColors();
|
||||
barChartJumps: Configuration = {
|
||||
type: 'Bar',
|
||||
data: {
|
||||
"labels": [],
|
||||
"series": []
|
||||
},
|
||||
options: {
|
||||
seriesBarDistance: 5,
|
||||
high: 70,
|
||||
axisX: {
|
||||
showGrid: false,
|
||||
offset: 20
|
||||
},
|
||||
axisY: {
|
||||
showGrid: true,
|
||||
offset: 40
|
||||
},
|
||||
height: 300
|
||||
},
|
||||
responsiveOptions: [
|
||||
['screen and (min-width: 1024px)', {
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: number, index: number): string {
|
||||
return index % 1 === 0 ? `${value}` : '';
|
||||
}
|
||||
}
|
||||
}],
|
||||
['screen and (min-width: 641px) and (max-width: 1024px)', {
|
||||
seriesBarDistance: 10,
|
||||
axisY: {
|
||||
offset: 30
|
||||
},
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: any, index: number): string {
|
||||
return index % 1 === 0 ? `${value[0]}` : '';
|
||||
}
|
||||
}
|
||||
}],
|
||||
['screen and (max-width: 640px)', {
|
||||
seriesBarDistance: 5,
|
||||
axisY: {
|
||||
offset: 20
|
||||
},
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: any, index: number): string {
|
||||
return index % 1 === 0 ? `${value[0]}` : '';
|
||||
}
|
||||
}
|
||||
}]
|
||||
]
|
||||
};
|
||||
constructor(private _dataService: BackendService) { }
|
||||
ngOnInit() {
|
||||
return this._dataService
|
||||
.getJumpsByDate(2)
|
||||
.subscribe((res) => {
|
||||
//console.log(res["rows"]);
|
||||
this.rows = res["rows"]
|
||||
this.rows.forEach((row: ReduceRow) => {
|
||||
let year = row.key[0].toString();
|
||||
let month = row.key[1];
|
||||
if (this.seriesName.indexOf(year) < 0) {
|
||||
this.seriesName.push(year);
|
||||
if (row.key[0] < this.min || this.min == 0) {
|
||||
this.min = row.key[0];
|
||||
}
|
||||
if (row.key[0] > this.max) {
|
||||
this.max = row.key[0];
|
||||
}
|
||||
this.seriesRow.push([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
}
|
||||
this.seriesRow[(this.seriesRow.length-1)][(month-1)] = row.value;
|
||||
this.seriesColTotal[(month-1)] += row.value;
|
||||
//console.log(row.key, row.value);
|
||||
});
|
||||
this.seriesRow.forEach((row: number[]) => {
|
||||
const total = row.reduce((partialSum, a) => partialSum + a, 0);
|
||||
this.seriesRowTotal.push(total);
|
||||
});
|
||||
console.log(this.seriesColTotal);
|
||||
this.grandTotal = this.seriesColTotal.reduce((partialSum, a) => partialSum + a, 0);
|
||||
//console.log(this.seriesRow, this.seriesName);
|
||||
this.barChartJumps = {
|
||||
type: 'Bar',
|
||||
data: {
|
||||
"labels": this.seriesHeader,
|
||||
"series": this.seriesRow
|
||||
},
|
||||
options: {
|
||||
seriesBarDistance: 15,
|
||||
high: 70,
|
||||
axisX: {
|
||||
showGrid: false,
|
||||
offset: 20
|
||||
},
|
||||
axisY: {
|
||||
showGrid: true,
|
||||
offset: 40
|
||||
},
|
||||
height: 300
|
||||
},
|
||||
responsiveOptions: [
|
||||
['screen and (min-width: 1024px)', {
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: number, index: number): string {
|
||||
return index % 1 === 0 ? `${value}` : '';
|
||||
}
|
||||
}
|
||||
}],
|
||||
['screen and (min-width: 641px) and (max-width: 1024px)', {
|
||||
seriesBarDistance: 10,
|
||||
axisY: {
|
||||
offset: 30
|
||||
},
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: any, index: number): string {
|
||||
return index % 1 === 0 ? `${value[0]}` : '';
|
||||
}
|
||||
}
|
||||
}],
|
||||
['screen and (max-width: 640px)', {
|
||||
seriesBarDistance: 5,
|
||||
axisY: {
|
||||
offset: 20
|
||||
},
|
||||
axisX: {
|
||||
labelInterpolationFnc: function(value: any, index: number): string {
|
||||
return index % 1 === 0 ? `${value[0]}` : '';
|
||||
}
|
||||
}
|
||||
}]
|
||||
]
|
||||
};
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { HomeComponent } from './home.component';
|
||||
import { AuthResolver } from 'src/app/core/services';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: HomeComponent,
|
||||
resolve: {
|
||||
isAuthenticated: AuthResolver
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class HomeRoutingModule { }
|
||||
@@ -0,0 +1,37 @@
|
||||
<div class="content">
|
||||
<h1>{{title}}</h1>
|
||||
<div *appShowAuthed="true">
|
||||
Connected
|
||||
</div>
|
||||
<div *appShowAuthed="false">
|
||||
Not connected
|
||||
</div>
|
||||
<div>
|
||||
<huapp-aeronefs></huapp-aeronefs>
|
||||
</div>
|
||||
<div>
|
||||
<huapp-drop-zones></huapp-drop-zones>
|
||||
</div>
|
||||
<div>
|
||||
<huapp-canopy-models></huapp-canopy-models>
|
||||
</div>
|
||||
<!--
|
||||
<div fxLayout="row wrap">
|
||||
<div fxFlex.gt-lg="66" fxFlex.gt-md="66" fxFlex.gt-sm="100" fxFlex.gt-xs="100" fxFlex="100">
|
||||
<huapp-jumps-by-month></huapp-jumps-by-month>
|
||||
</div>
|
||||
<div fxFlex.gt-lg="34" fxFlex.gt-md="34" fxFlex.gt-sm="50" fxFlex.gt-xs="100" fxFlex="100">
|
||||
<huapp-drop-zones></huapp-drop-zones>
|
||||
</div>
|
||||
<div fxFlex.gt-lg="30" fxFlex.gt-md="29" fxFlex.gt-sm="50" fxFlex.gt-xs="100" fxFlex="100">
|
||||
<huapp-canopy-sizes></huapp-canopy-sizes>
|
||||
</div>
|
||||
<div fxFlex.gt-lg="30" fxFlex.gt-md="29" fxFlex.gt-sm="50" fxFlex.gt-xs="100" fxFlex="100">
|
||||
<huapp-canopy-models></huapp-canopy-models>
|
||||
</div>
|
||||
<div fxFlex.gt-lg="40" fxFlex.gt-md="42" fxFlex.gt-sm="50" fxFlex.gt-xs="100" fxFlex="100">
|
||||
<huapp-aeronefs-bar></huapp-aeronefs-bar>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HomeComponent } from './home.component';
|
||||
|
||||
describe('HomeComponent', () => {
|
||||
let component: HomeComponent;
|
||||
let fixture: ComponentFixture<HomeComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [HomeComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(HomeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { ActivatedRoute, Data, Router } from '@angular/router';
|
||||
|
||||
import { Observable, Observer, Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { User, Errors, Aeronef, AeronefAggregate, AeronefList, Canopy, CanopyAggregate, DropZone, DropZoneAggregate } from 'src/app/core/models';
|
||||
import { AeronefsService, CanopiesService, DropZonesService, UserService } from 'src/app/core/services';
|
||||
import { ShowAuthedDirective } from 'src/app/components/shared/show-authed.directive';
|
||||
import { AeronefsComponent, DropZonesComponent, CanopyModelsComponent } from './dashboard-components';
|
||||
//import { JumpsByMonthComponent, AeronefsComponent, AeronefsBarComponent, DropZonesComponent, DropZonesBarComponent, CanopySizesComponent, CanopyModelsComponent} from './dashboard-components';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
//AeronefsComponent, AeronefsBarComponent, DropZonesComponent, DropZonesBarComponent,
|
||||
//CanopySizesComponent, CanopyModelsComponent, JumpsByMonthComponent,
|
||||
AeronefsComponent, DropZonesComponent, CanopyModelsComponent, ShowAuthedDirective
|
||||
],
|
||||
selector: 'huapp-home',
|
||||
templateUrl: './home.component.html',
|
||||
styleUrls: ['./home.component.scss']
|
||||
})
|
||||
export class HomeComponent implements OnInit, OnDestroy {
|
||||
private _currentUser: Subscription = new Subscription();
|
||||
private _data: Subscription = new Subscription();
|
||||
private _aeronefs: Subscription = new Subscription();
|
||||
private _canopies: Subscription = new Subscription();
|
||||
private _dropzones: Subscription = new Subscription();
|
||||
title = 'Dashboard';
|
||||
errors!: Errors;
|
||||
isAuthenticated = false;
|
||||
canModify = false;
|
||||
aeronefAggregate!: Array<AeronefAggregate>
|
||||
aeronefs!: Array<Aeronef>;
|
||||
aeronefsCount = 0;
|
||||
canopyAggregate!: Array<CanopyAggregate>
|
||||
canopies!: Array<Canopy>;
|
||||
canopiesCount = 0;
|
||||
dropzoneAggregate!: Array<DropZoneAggregate>
|
||||
dropzones!: Array<DropZone>;
|
||||
dropzonesCount = 0;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private titleService: Title,
|
||||
private aeronefsService: AeronefsService,
|
||||
private canopiesService: CanopiesService,
|
||||
private dropzonesService: DropZonesService,
|
||||
private userService: UserService
|
||||
) {
|
||||
this._resetErrors();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle(this.title);
|
||||
const data$: Observable<any> = this.route.data.pipe(take(1));
|
||||
this._data = data$.subscribe((data: Data) => {
|
||||
//data: Partial<Observer<Data>>
|
||||
this.isAuthenticated = data['isAuthenticated'];
|
||||
if (!this.isAuthenticated) {
|
||||
this.router.navigateByUrl('/login');
|
||||
return;
|
||||
}
|
||||
const currentUser$: Observable<User> = this.userService.currentUser.pipe(take(1));
|
||||
this._currentUser = currentUser$.subscribe((userData: User) => {
|
||||
this.canModify = userData.role === 'Admin';
|
||||
});
|
||||
});
|
||||
this.loadAeronefs();
|
||||
this.loadCanopies();
|
||||
this.loadDropzones();
|
||||
}
|
||||
|
||||
loadAeronefs() {
|
||||
const aeronefs$: Observable<Array<AeronefAggregate>> = this.aeronefsService.getAll();
|
||||
this._aeronefs = aeronefs$.subscribe((aggregate: Array<AeronefAggregate>) => {
|
||||
this.aeronefAggregate = aggregate;
|
||||
this.aeronefsCount = aggregate.length;
|
||||
console.log(this.aeronefs);
|
||||
console.log(this.aeronefsCount);
|
||||
});
|
||||
}
|
||||
loadCanopies() {
|
||||
const canopies$: Observable<Array<CanopyAggregate>> = this.canopiesService.getAll();
|
||||
this._canopies = canopies$.subscribe((aggregate: Array<CanopyAggregate>) => {
|
||||
this.canopyAggregate = aggregate;
|
||||
this.canopiesCount = aggregate.length;
|
||||
// console.log(this.canopies);
|
||||
// console.log(this.canopiesCount);
|
||||
});
|
||||
}
|
||||
loadDropzones() {
|
||||
const dropzones$: Observable<Array<DropZoneAggregate>> = this.dropzonesService.getAll();
|
||||
this._dropzones = dropzones$.subscribe((aggregate: Array<DropZoneAggregate>) => {
|
||||
this.dropzoneAggregate = aggregate;
|
||||
this.dropzonesCount = aggregate.length;
|
||||
// console.log(this.dropzones);
|
||||
// console.log(this.dropzonesCount);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._aeronefs.unsubscribe();
|
||||
this._canopies.unsubscribe();
|
||||
this._dropzones.unsubscribe();
|
||||
}
|
||||
|
||||
private _resetErrors(): void {
|
||||
this.errors = { errors: {} };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AuthResolver } from 'src/app/core/services';
|
||||
import { HomeComponent } from './home.component';
|
||||
import { HomeRoutingModule } from './home-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
HomeRoutingModule,
|
||||
HomeComponent
|
||||
],
|
||||
exports: [],
|
||||
providers: [AuthResolver]
|
||||
})
|
||||
export class HomeModule { }
|
||||
@@ -0,0 +1,6 @@
|
||||
export * from './auth/auth.component';
|
||||
export * from './calculator/calculator.component';
|
||||
export * from './home/home.component';
|
||||
export * from './logbook/logbook.component';
|
||||
export * from './profile/profile.component';
|
||||
export * from './shared';
|
||||
@@ -0,0 +1,21 @@
|
||||
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 { }
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="content">
|
||||
<h1>{{title}}</h1>
|
||||
<!---->
|
||||
<huapp-jump-table #jumpTable [refresh]="refreshInterval" [config]="listConfig" [isUser]="isUser" [showAdd]="canModify" [title]="listTitle">
|
||||
</huapp-jump-table>
|
||||
<!---->
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LogbookComponent } from './logbook.component';
|
||||
|
||||
describe('LogbookComponent', () => {
|
||||
let component: LogbookComponent;
|
||||
let fixture: ComponentFixture<LogbookComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [LogbookComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(LogbookComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { JumpTableComponent } from 'src/app/components/shared/helpers-jump';
|
||||
import { Errors, Jump, JumpListConfig, User } from 'src/app/core/models';
|
||||
import { JumpsService, UserService } from 'src/app/core/services';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import data from 'src/jumps.json';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [JumpTableComponent],
|
||||
selector: 'huapp-logbook',
|
||||
templateUrl: './logbook.component.html',
|
||||
styleUrls: ['./logbook.component.scss']
|
||||
})
|
||||
export class LogbookComponent implements OnInit, OnDestroy {
|
||||
private _currentUser: Subscription = new Subscription();
|
||||
private _data: Subscription = new Subscription();
|
||||
errors!: Errors;
|
||||
title = 'Carnet de saut';
|
||||
listTitle = 'Liste des sauts';
|
||||
listConfig: JumpListConfig = { type: 'all', filters: {} };
|
||||
refreshInterval: number = environment.refresh_interval;
|
||||
isAuthenticated = false;
|
||||
isUser = true;
|
||||
canModify = false;
|
||||
jump: Jump = {} as Jump;
|
||||
currentUser: User = {} as User;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private jumpsService: JumpsService,
|
||||
private userService: UserService
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
//this.importJumps();
|
||||
const data$: Observable<any> = this.route.data;
|
||||
this._data = data$.subscribe((data: { isAuthenticated: boolean }) => {
|
||||
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';
|
||||
});
|
||||
});
|
||||
}
|
||||
importJumps() {
|
||||
try {
|
||||
data.forEach((entry) => {
|
||||
Object.assign(this.jump, entry);
|
||||
//console.log(this.jump);
|
||||
this.jumpsService.save(this.jump)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (jump) => {
|
||||
console.log(`Le saut n°${jump.numero} a été ajouté !`, 'OK');
|
||||
},
|
||||
error: (err) => {
|
||||
console.log(`Le saut n\'a pas été ajouté !`, 'Error', err);
|
||||
this.errors = err;
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._currentUser.unsubscribe();
|
||||
this._data.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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 { }
|
||||
@@ -0,0 +1,25 @@
|
||||
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('/')));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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 { }
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="content">
|
||||
<h1>{{title}}</h1>
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProfileComponent } from './profile.component';
|
||||
|
||||
describe('ProfileComponent', () => {
|
||||
let component: ProfileComponent;
|
||||
let fixture: ComponentFixture<ProfileComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProfileComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(ProfileComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [],
|
||||
selector: 'huapp-profile',
|
||||
templateUrl: './profile.component.html',
|
||||
styleUrls: ['./profile.component.scss']
|
||||
})
|
||||
export class ProfileComponent implements OnInit, OnDestroy {
|
||||
title = 'Mon compte';
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
//
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
//this._scavenger.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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 { }
|
||||
@@ -0,0 +1,18 @@
|
||||
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 { }
|
||||
@@ -0,0 +1,96 @@
|
||||
<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>
|
||||
</div>
|
||||
<div class="container page">
|
||||
<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>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<mat-label>Nom</mat-label>
|
||||
<input matInput type="text" placeholder="Indiquez votre nom" formControlName="lastname">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<mat-label>Prénom</mat-label>
|
||||
<input matInput type="text" placeholder="Indiquez votre prénnom" formControlName="firstname">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<mat-label>Numéro de téléphone</mat-label>
|
||||
<input matInput type="tel" placeholder="Indiquez votre numéro de téléphone" formControlName="phone">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<mat-label>Licence</mat-label>
|
||||
<input matInput type="number" placeholder="Indiquez le un nom de votre société" formControlName="licence">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<mat-label>Photo de profil</mat-label>
|
||||
<input matInput type="url" placeholder="URL de votre photo" formControlName="image">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<mat-label>Arrière plan</mat-label>
|
||||
<input matInput type="url" placeholder="URL de votre arrière plan" formControlName="bg_image">
|
||||
</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">
|
||||
<mat-label>Nom d'utilisateur</mat-label>
|
||||
<input matInput type="text" placeholder="Choisissez un nom d'utilisateur" formControlName="username">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<mat-form-field appearance="fill" class="w-100">
|
||||
<mat-label>eMail</mat-label>
|
||||
<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">
|
||||
<button mat-raised-button color="primary" [disabled]="!settingsForm.valid" type="submit" class="float-sm-end">{{ btnUpdateTitle }}</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>
|
||||
</div>
|
||||
@@ -0,0 +1,45 @@
|
||||
/* Settings */
|
||||
@use '@angular/material' as mat;
|
||||
@import 'src/styles/variable';
|
||||
|
||||
.banner {
|
||||
background-color: mat.get-color-from-palette($md-custom-warn, 700);
|
||||
margin-bottom: 1rem;
|
||||
&.light {
|
||||
background-color: mat.get-color-from-palette($md-custom-accent, 700);
|
||||
color: #555;
|
||||
button {
|
||||
color: #555;
|
||||
border-color: #555;
|
||||
}
|
||||
}
|
||||
.user-img {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
border-radius: 84px;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
h4 {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
p {
|
||||
margin: 0 auto .5rem;
|
||||
color: #aaaaaa;
|
||||
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,24 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SettingsComponent } from './settings.component';
|
||||
|
||||
describe('SettingsComponent', () => {
|
||||
let component: SettingsComponent;
|
||||
let fixture: ComponentFixture<SettingsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SettingsComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SettingsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, AbstractControl, Validators, ValidatorFn, ValidationErrors, FormsModule, ReactiveFormsModule } 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 { 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',
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterLink, FormsModule, ReactiveFormsModule,
|
||||
MatButtonModule, MatIconModule, MatFormFieldModule, MatInputModule, MatDividerModule,
|
||||
ListErrorsComponent
|
||||
]
|
||||
})
|
||||
export class SettingsComponent implements OnInit, OnDestroy {
|
||||
private _user: Subscription = new Subscription();
|
||||
title = 'Vos paramètres';
|
||||
bannerTitle = '';
|
||||
btnUpdateTitle = 'Mettre à jour';
|
||||
btnLogoutTitle = 'Se déconnecter';
|
||||
user: User = {} as User;
|
||||
settingsForm: UntypedFormGroup;
|
||||
errors: Errors = { errors: {} };
|
||||
isSubmitting = false;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private titleService: Title,
|
||||
private userService: UserService,
|
||||
private fb: UntypedFormBuilder
|
||||
) {
|
||||
// create form group using the form builder
|
||||
this.settingsForm = this.fb.group({
|
||||
username: ['', Validators.required],
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
firstname: ['', Validators.required],
|
||||
lastname: ['', Validators.required],
|
||||
phone: ['', Validators.required],
|
||||
password: ['', Validators.required],
|
||||
confirmPassword: ['', Validators.required],
|
||||
licence: '',
|
||||
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));
|
||||
}
|
||||
|
||||
get f() {
|
||||
return this.settingsForm.controls;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle(`Head Up - ${this.title}`);
|
||||
// Make a fresh copy of the current user's object to place in editable form fields
|
||||
Object.assign(this.user, this.userService.getCurrentUser());
|
||||
// Fill the form
|
||||
this.settingsForm.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();
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.userService.purgeAuth();
|
||||
this.router.navigateByUrl('/login');
|
||||
}
|
||||
|
||||
submitForm() {
|
||||
this.isSubmitting = true;
|
||||
|
||||
// update the model
|
||||
this.updateUser(this.settingsForm.value);
|
||||
|
||||
const user$: Observable<User> = this.userService.update(this.user).pipe(take(1));
|
||||
this._user = user$.subscribe({
|
||||
next: (updatedUser) => this.router.navigateByUrl('/profile/' + updatedUser.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,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { SettingsComponent } from './settings.component';
|
||||
import { SettingsRoutingModule } from './settings-routing.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
SettingsRoutingModule,
|
||||
SettingsComponent
|
||||
]
|
||||
})
|
||||
export class SettingsModule { }
|
||||
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
Directive,
|
||||
OnInit,
|
||||
AfterViewInit,
|
||||
AfterContentChecked
|
||||
} from '@angular/core';
|
||||
import { Router, NavigationEnd } from '@angular/router';
|
||||
|
||||
import { AccordionLinkDirective } from './accordionlink.directive';
|
||||
import { filter } from 'rxjs/operators';
|
||||
|
||||
@Directive({
|
||||
selector: '[appAccordion]',
|
||||
standalone: true
|
||||
})
|
||||
export class AccordionDirective implements AfterContentChecked {
|
||||
protected navlinks: Array<AccordionLinkDirective> = [];
|
||||
|
||||
closeOtherLinks(selectedLink: AccordionLinkDirective): void {
|
||||
this.navlinks.forEach((link: AccordionLinkDirective) => {
|
||||
if (link !== selectedLink) {
|
||||
link.selected = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addLink(link: AccordionLinkDirective): void {
|
||||
this.navlinks.push(link);
|
||||
}
|
||||
|
||||
removeGroup(link: AccordionLinkDirective): void {
|
||||
const index = this.navlinks.indexOf(link);
|
||||
if (index !== -1) {
|
||||
this.navlinks.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
checkOpenLinks() {
|
||||
this.navlinks.forEach((link: AccordionLinkDirective) => {
|
||||
if (link.group) {
|
||||
const routeUrl = this.router.url;
|
||||
const currentUrl = routeUrl.split('/');
|
||||
if (currentUrl.indexOf(link.group) > 0) {
|
||||
link.selected = true;
|
||||
this.closeOtherLinks(link);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterContentChecked(): void {
|
||||
this.router.events
|
||||
.pipe(filter(event => event instanceof NavigationEnd))
|
||||
.subscribe(e => this.checkOpenLinks());
|
||||
}
|
||||
|
||||
constructor(private router: Router) {
|
||||
setTimeout(() => this.checkOpenLinks());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Directive, HostListener, Inject } from '@angular/core';
|
||||
|
||||
import { AccordionLinkDirective } from './accordionlink.directive';
|
||||
|
||||
@Directive({
|
||||
selector: '[appAccordionToggle]',
|
||||
standalone: true
|
||||
})
|
||||
export class AccordionAnchorDirective {
|
||||
protected navlink: AccordionLinkDirective;
|
||||
|
||||
constructor(@Inject(AccordionLinkDirective) navlink: AccordionLinkDirective) {
|
||||
this.navlink = navlink;
|
||||
}
|
||||
|
||||
@HostListener('click', ['$event'])
|
||||
onClick(e: any) {
|
||||
this.navlink.toggle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
Directive,
|
||||
HostBinding,
|
||||
Inject,
|
||||
Input,
|
||||
OnInit,
|
||||
OnDestroy
|
||||
} from '@angular/core';
|
||||
|
||||
import { AccordionDirective } from './accordion.directive';
|
||||
|
||||
@Directive({
|
||||
selector: '[appAccordionLink]',
|
||||
standalone: true
|
||||
})
|
||||
export class AccordionLinkDirective implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
public group: any;
|
||||
|
||||
@HostBinding('class.selected')
|
||||
@Input()
|
||||
get selected(): boolean {
|
||||
return this._selected;
|
||||
}
|
||||
|
||||
set selected(value: boolean) {
|
||||
this._selected = value;
|
||||
if (value) {
|
||||
this.nav.closeOtherLinks(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected _selected: boolean = false;
|
||||
protected nav: AccordionDirective;
|
||||
|
||||
constructor(@Inject(AccordionDirective) nav: AccordionDirective) {
|
||||
this.nav = nav;
|
||||
}
|
||||
|
||||
ngOnInit(): any {
|
||||
this.nav.addLink(this);
|
||||
}
|
||||
|
||||
ngOnDestroy(): any {
|
||||
this.nav.removeGroup(this);
|
||||
}
|
||||
|
||||
toggle(): any {
|
||||
this.selected = !this.selected;
|
||||
}
|
||||
}
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
export * from './accordionanchor.directive';
|
||||
export * from './accordionlink.directive';
|
||||
export * from './accordion.directive';
|
||||
@@ -0,0 +1,17 @@
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
export function ConfirmedValidator(controlName: string, matchingControlName: string){
|
||||
return (formGroup: FormGroup) => {
|
||||
const control = formGroup.controls[controlName];
|
||||
const matchingControl = formGroup.controls[matchingControlName];
|
||||
|
||||
if (matchingControl.errors && !matchingControl.errors['confirmedValidator']) {
|
||||
return;
|
||||
}
|
||||
if (control.value !== matchingControl.value) {
|
||||
matchingControl.setErrors({ confirmedValidator: true });
|
||||
} else {
|
||||
matchingControl.setErrors(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './jump-add.dialog';
|
||||
export * from './jump-delete.dialog';
|
||||
export * from './jump-edit.dialog';
|
||||
@@ -0,0 +1,90 @@
|
||||
<h2 mat-dialog-title>Ajouter un saut</h2>
|
||||
<mat-divider class="mt-2 mb-1 border-light-subtle"></mat-divider>
|
||||
<mat-dialog-content>
|
||||
<div fxLayout="row wrap">
|
||||
<!-- column -->
|
||||
<div fxFlex.gt-lg="25" fxFlex.gt-md="50" fxFlex.gt-xs="100" fxFlex="100" class="px-2">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Numéro</mat-label>
|
||||
<input matInput [(ngModel)]="jump.numero">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Date</mat-label>
|
||||
<input matInput [(ngModel)]="jump.date">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Lieu</mat-label>
|
||||
<input matInput [(ngModel)]="jump.lieu">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Aeronef</mat-label>
|
||||
<input matInput [(ngModel)]="jump.aeronef">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Hauteur</mat-label>
|
||||
<input matInput [(ngModel)]="jump.hauteur">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Voile</mat-label>
|
||||
<input matInput [(ngModel)]="jump.voile">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Taille</mat-label>
|
||||
<input matInput [(ngModel)]="jump.taille">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Categorie</mat-label>
|
||||
<input matInput [(ngModel)]="jump.categorie">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Module</mat-label>
|
||||
<input matInput [(ngModel)]="jump.module">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<!-- column -->
|
||||
<div fxFlex.gt-lg="25" fxFlex.gt-md="50" fxFlex.gt-xs="100" fxFlex="100" class="px-2">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Participants</mat-label>
|
||||
<input matInput [(ngModel)]="jump.participants">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Programme</mat-label>
|
||||
<input matInput [(ngModel)]="jump.programme">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Oaci</mat-label>
|
||||
<input matInput [(ngModel)]="jump.oaci">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Imat</mat-label>
|
||||
<input matInput [(ngModel)]="jump.imat">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Accessoires</mat-label>
|
||||
<input matInput [(ngModel)]="jump.accessoires">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Zone</mat-label>
|
||||
<input matInput [(ngModel)]="jump.zone">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Dossier</mat-label>
|
||||
<input matInput [(ngModel)]="jump.dossier">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Video</mat-label>
|
||||
<input matInput [(ngModel)]="jump.video">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<!-- column -->
|
||||
<div fxFlex.gt-lg="25" fxFlex.gt-md="50" fxFlex.gt-xs="100" fxFlex="100" class="px-2">
|
||||
</div>
|
||||
<!-- column -->
|
||||
<div fxFlex.gt-lg="25" fxFlex.gt-md="50" fxFlex.gt-xs="100" fxFlex="100" class="px-2">
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="onCloseDialog()">Annuler</button>
|
||||
<button mat-button color="primary" [mat-dialog-close]="jump" cdkFocusInitial>Enregistrer</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
|
||||
import { Jump } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-add-dialog',
|
||||
templateUrl: 'jump-add.dialog.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
FormsModule,
|
||||
MatDialogModule, MatFormFieldModule, MatInputModule, MatDividerModule,
|
||||
MatSelectModule, MatOptionModule, MatButtonModule
|
||||
]
|
||||
})
|
||||
export class JumpAddDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<JumpAddDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public jump: Jump
|
||||
) { }
|
||||
|
||||
onCloseDialog(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<h2 mat-dialog-title>Supprimer le saut n°{{jump.numero}}</h2>
|
||||
<mat-divider class="mt-2 mb-1 border-light-subtle"></mat-divider>
|
||||
<mat-dialog-content>
|
||||
<p class="mt-2 mb-3 text-accent">Veuillez confirmer la suppression de ce saut. Attention, cette action est irréversible.</p>
|
||||
<h3>Données du saut :</h3>
|
||||
<div class="mb-3 clearfix">
|
||||
<dl class="dl-horizontal">
|
||||
<dt class="text-lowercase text-bold">numero:</dt>
|
||||
<dd>{{jump.numero}}</dd>
|
||||
<dt class="text-lowercase text-bold">date:</dt>
|
||||
<dd>{{jump.date}}</dd>
|
||||
<dt class="text-lowercase text-bold">lieu:</dt>
|
||||
<dd>{{jump.lieu}}</dd>
|
||||
<dt class="text-lowercase text-bold">oaci:</dt>
|
||||
<dd>{{jump.oaci}}</dd>
|
||||
<dt class="text-lowercase text-bold">aeronef:</dt>
|
||||
<dd>{{jump.aeronef}}</dd>
|
||||
<dt class="text-lowercase text-bold">imat:</dt>
|
||||
<dd>{{jump.imat}}</dd>
|
||||
<dt class="text-lowercase text-bold">hauteur:</dt>
|
||||
<dd>{{jump.hauteur}}</dd>
|
||||
<dt class="text-lowercase text-bold">voile:</dt>
|
||||
<dd>{{jump.voile}}</dd>
|
||||
<dt class="text-lowercase text-bold">taille:</dt>
|
||||
<dd>{{jump.taille}}</dd>
|
||||
<dt class="text-lowercase text-bold">categorie:</dt>
|
||||
<dd>{{jump.categorie}}</dd>
|
||||
<dt class="text-lowercase text-bold">module:</dt>
|
||||
<dd>{{jump.module}}</dd>
|
||||
<dt class="text-lowercase text-bold">participants:</dt>
|
||||
<dd>{{jump.participants}}</dd>
|
||||
<dt class="text-lowercase text-bold">programme:</dt>
|
||||
<dd>{{jump.programme}}</dd>
|
||||
<dt class="text-lowercase text-bold">accessoires:</dt>
|
||||
<dd>{{jump.accessoires}}</dd>
|
||||
<dt class="text-lowercase text-bold">zone:</dt>
|
||||
<dd>{{jump.zone}}</dd>
|
||||
<dt class="text-lowercase text-bold">dossier:</dt>
|
||||
<dd>{{jump.dossier}}</dd>
|
||||
<dt class="text-lowercase text-bold">video:</dt>
|
||||
<dd>{{jump.video}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<hr />
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="onCloseDialog()">Annuler</button>
|
||||
<button mat-button color="accent" [mat-dialog-close]="jump" cdkFocusInitial>Supprimer</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
|
||||
import { Jump } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-delete-dialog',
|
||||
templateUrl: 'jump-delete.dialog.html',
|
||||
standalone: true,
|
||||
imports: [MatDialogModule, MatButtonModule, MatDividerModule]
|
||||
})
|
||||
export class JumpDeleteDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<JumpDeleteDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public jump: Jump
|
||||
) { }
|
||||
|
||||
onCloseDialog(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<h2 mat-dialog-title>Editer un saut</h2>
|
||||
<mat-divider class="mt-2 mb-1 border-light-subtle"></mat-divider>
|
||||
<mat-dialog-content>
|
||||
<div fxLayout="row wrap">
|
||||
<!-- column -->
|
||||
<div fxFlex.gt-lg="50" fxFlex.gt-md="50" fxFlex.gt-xs="100" fxFlex="100" class="px-2">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Numéro</mat-label>
|
||||
<input matInput [(ngModel)]="jump.numero">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Date</mat-label>
|
||||
<input matInput [(ngModel)]="jump.date">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Lieu</mat-label>
|
||||
<input matInput [(ngModel)]="jump.lieu">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Aeronef</mat-label>
|
||||
<input matInput [(ngModel)]="jump.aeronef">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Hauteur</mat-label>
|
||||
<input matInput [(ngModel)]="jump.hauteur">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Voile</mat-label>
|
||||
<input matInput [(ngModel)]="jump.voile">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Taille</mat-label>
|
||||
<input matInput [(ngModel)]="jump.taille">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Categorie</mat-label>
|
||||
<input matInput [(ngModel)]="jump.categorie">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Module</mat-label>
|
||||
<input matInput [(ngModel)]="jump.module">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<!-- column -->
|
||||
<div fxFlex.gt-lg="50" fxFlex.gt-md="50" fxFlex.gt-xs="100" fxFlex="100" class="px-2">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Participants</mat-label>
|
||||
<input matInput [(ngModel)]="jump.participants">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Programme</mat-label>
|
||||
<input matInput [(ngModel)]="jump.programme">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Oaci</mat-label>
|
||||
<input matInput [(ngModel)]="jump.oaci">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Imat</mat-label>
|
||||
<input matInput [(ngModel)]="jump.imat">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Accessoires</mat-label>
|
||||
<input matInput [(ngModel)]="jump.accessoires">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Zone</mat-label>
|
||||
<input matInput [(ngModel)]="jump.zone">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Dossier</mat-label>
|
||||
<input matInput [(ngModel)]="jump.dossier">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Video</mat-label>
|
||||
<input matInput [(ngModel)]="jump.video">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="onCloseDialog()">Annuler</button>
|
||||
<button mat-button color="primary" [mat-dialog-close]="jump" cdkFocusInitial>Enregistrer</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
|
||||
import { Jump } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-edit-dialog',
|
||||
templateUrl: 'jump-edit.dialog.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
FormsModule,
|
||||
MatDialogModule, MatFormFieldModule, MatInputModule, MatDividerModule,
|
||||
MatSelectModule, MatOptionModule, MatIconModule, MatButtonModule
|
||||
]
|
||||
})
|
||||
export class JumpEditDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<JumpEditDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public jump: Jump
|
||||
) { }
|
||||
|
||||
onCloseDialog(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './jump-list.component';
|
||||
export * from './jump-meta.component';
|
||||
export * from './jump-preview.component';
|
||||
export * from './jump-table.component';
|
||||
@@ -0,0 +1,18 @@
|
||||
<div class="clearfix mt-4 mb-1 border-bottom">
|
||||
<h1 class="float-md-start me-3">{{ title }} <span class="fs-4 text-accent" [hidden]="!jumpsCount">({{ jumpsCount }})</span></h1>
|
||||
<mat-spinner class="float-md-start" [diameter]="32" [hidden]="!loading" color="accent"></mat-spinner>
|
||||
<button [hidden]="!showAdd" mat-raised-button color="accent" [routerLink]="['/editor']" class="float-md-end">
|
||||
<mat-icon aria-label="Ajouter" fontIcon="add_notes"></mat-icon> Ajouter
|
||||
</button>
|
||||
</div>
|
||||
<span [hidden]="!jumps.length" class="small mt-2 me-2 fst-italic">Prix actualisés toutes les {{ (refresh / 1000) }} secondes.</span>
|
||||
<span [hidden]="jumps.length || loading">Aucun produit à afficher pour le moment</span>
|
||||
<span [hidden]="!loading">Chargement des produits en cours</span>
|
||||
<huapp-jump-preview *ngFor="let jump of jumps" [jump]="jump" [isUser]="isUser" [showMeta]="showMeta"></huapp-jump-preview>
|
||||
<nav [hidden]="loading || pages.length <= 1" class="clearfix mt-3">
|
||||
<ul class="pagination float-end">
|
||||
<li class="page-item" [ngClass]="{'active': pageNumber === currentPage}" *ngFor="let pageNumber of pages">
|
||||
<button class="page-link" (click)="setPageTo(pageNumber)">{{ pageNumber }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -0,0 +1,93 @@
|
||||
import { Component, OnInit, OnDestroy, Input, computed, Signal, WritableSignal } from '@angular/core';
|
||||
import { NgFor, NgClass, DatePipe } from '@angular/common';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { Errors, Jump, JumpList, JumpListConfig } from 'src/app/core/models';
|
||||
import { JumpsService } from 'src/app/core/services';
|
||||
import { JumpPreviewComponent } from './jump-preview.component';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-list',
|
||||
templateUrl: './jump-list.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgFor, NgClass, DatePipe,
|
||||
MatProgressSpinnerModule, MatButtonModule, RouterLink, MatIconModule,
|
||||
JumpPreviewComponent
|
||||
]
|
||||
})
|
||||
export class JumpListComponent implements OnInit, OnDestroy {
|
||||
private _jumps: Subscription = new Subscription();
|
||||
errors: Errors = { errors: {} };
|
||||
query!: JumpListConfig;
|
||||
jumps: Array<Jump> = [];
|
||||
jumpsCount = 0;
|
||||
loading = false;
|
||||
currentPage = 1;
|
||||
pages: Array<number> = [1];
|
||||
lastUpdate: Date = new Date();
|
||||
|
||||
constructor(
|
||||
private jumpsService: JumpsService
|
||||
) { }
|
||||
|
||||
@Input() limit = 0;
|
||||
@Input() refresh = 30000;
|
||||
@Input() title = '';
|
||||
@Input() showAdd = false;
|
||||
@Input() isUser = false;
|
||||
@Input() showMeta = false;
|
||||
@Input()
|
||||
set config(config: JumpListConfig) {
|
||||
if (config) {
|
||||
this.query = config;
|
||||
this.currentPage = 1;
|
||||
this.loading = true;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.runQuery();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._jumps.unsubscribe();
|
||||
}
|
||||
|
||||
setPageTo(pageNumber: number) {
|
||||
this.currentPage = pageNumber;
|
||||
this.runQuery();
|
||||
}
|
||||
|
||||
runQuery() {
|
||||
if (this.limit) {
|
||||
this.query.filters.limit = this.limit;
|
||||
this.query.filters.offset = (this.limit * (this.currentPage - 1));
|
||||
}
|
||||
|
||||
const jumps$: Observable<JumpList> = this.jumpsService.query(this.query);
|
||||
this._jumps = jumps$.subscribe({
|
||||
next: (data: JumpList) => {
|
||||
this.loading = false;
|
||||
this.jumps = data.jumps;
|
||||
this.jumpsCount = data.jumpsCount;
|
||||
if (this.limit) {
|
||||
this.pages = this.getTotalPages(this.jumpsCount, this.limit);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getTotalPages(count: number, limit: number) {
|
||||
// Used from http://www.jstips.co/en/create-range-0...n-easily-using-one-line/
|
||||
return Array.from(new Array(Math.ceil(count / limit)), (val, index) => index + 1);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user