Ajout des sources

This commit is contained in:
Julien Gautier
2023-09-12 21:46:56 +02:00
parent fa2288b8ed
commit 747948a422
235 changed files with 45064 additions and 0 deletions
+16
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}
+20
View File
@@ -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"
}
]
}
+42
View File
@@ -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"
}
}
}
}
]
}
+27
View File
@@ -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
View File
@@ -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": []
}
}
}
}
}
}
+23003
View File
File diff suppressed because it is too large Load Diff
+44
View File
@@ -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"
}
}
+102
View File
@@ -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;
?>
+40
View File
@@ -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 { }
+9
View File
@@ -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>
-->
View File
+29
View File
@@ -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!');
});
});
+20
View File
@@ -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);
}
}
+30
View File
@@ -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();
});
});
+157
View File
@@ -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: {} };
}
}
+14
View File
@@ -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 { }
@@ -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>
@@ -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();
});
});
@@ -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();
});
}
}
@@ -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>
@@ -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();
});
});
@@ -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
}
};
});
}
}
@@ -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>
@@ -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();
});
});
@@ -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
}
};
});
}
}
@@ -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>
@@ -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();
});
});
@@ -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"]
}
}
@@ -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>
@@ -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();
});
});
@@ -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"]
}
}
@@ -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>
@@ -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();
});
});
@@ -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]
}
}
@@ -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>
@@ -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();
});
});
@@ -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();
});
});
+114
View File
@@ -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: {} };
}
}
+15
View File
@@ -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 { }
+6
View File
@@ -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 { }
+60
View File
@@ -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;
}
}
+3
View File
@@ -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