4 Commits

Author SHA1 Message Date
Rampeur 25d47bc3be Linting 2024-05-09 02:21:19 +02:00
Rampeur 276da97003 Mise à jour angular 2024-05-09 00:06:04 +02:00
Rampeur d1870adff6 Refactoring 2024-05-08 23:46:08 +02:00
Rampeur 7c3f0c9648 Implémentation SkydiverId API 2024-05-08 20:32:49 +02:00
123 changed files with 3113 additions and 2481 deletions
+543 -463
View File
File diff suppressed because it is too large Load Diff
+10 -9
View File
@@ -25,30 +25,31 @@
"@angular/common": "^17.3.5", "@angular/common": "^17.3.5",
"@angular/compiler": "^17.3.5", "@angular/compiler": "^17.3.5",
"@angular/core": "^17.3.5", "@angular/core": "^17.3.5",
"@angular/forms": "^17.3.5", "@angular/forms": "^17.3.8",
"@angular/localize": "^17.3.5", "@angular/google-maps": "^17.3.8",
"@angular/material": "^17.3.6", "@angular/localize": "^17.3.8",
"@angular/material": "^17.3.8",
"@angular/platform-browser": "^17.3.5", "@angular/platform-browser": "^17.3.5",
"@angular/platform-browser-dynamic": "^17.3.5", "@angular/platform-browser-dynamic": "^17.3.8",
"@angular/router": "^17.3.5", "@angular/router": "^17.3.8",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"chart.js": "^4.4.2", "chart.js": "^4.4.2",
"chartist": "^1.3.0", "chartist": "^1.3.0",
"ng-chartist": "^8.2.0", "ng-chartist": "^8.2.0",
"ng2-charts": "^6.0.0", "ng2-charts": "^6.0.1",
"ngx-skeleton-loader": "^9.0.0", "ngx-skeleton-loader": "^9.0.0",
"rxjs": "~7.8.0", "rxjs": "~7.8.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "^0.14.4" "zone.js": "^0.14.5"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^17.3.5", "@angular-devkit/build-angular": "^17.3.7",
"@angular-eslint/builder": "17.3.0", "@angular-eslint/builder": "17.3.0",
"@angular-eslint/eslint-plugin": "17.3.0", "@angular-eslint/eslint-plugin": "17.3.0",
"@angular-eslint/eslint-plugin-template": "17.3.0", "@angular-eslint/eslint-plugin-template": "17.3.0",
"@angular-eslint/schematics": "17.3.0", "@angular-eslint/schematics": "17.3.0",
"@angular-eslint/template-parser": "17.3.0", "@angular-eslint/template-parser": "17.3.0",
"@angular/cli": "^17.3.5", "@angular/cli": "^17.3.7",
"@angular/compiler-cli": "^17.3.5", "@angular/compiler-cli": "^17.3.5",
"@types/jasmine": "~5.1.0", "@types/jasmine": "~5.1.0",
"@typescript-eslint/eslint-plugin": "7.2.0", "@typescript-eslint/eslint-plugin": "7.2.0",
@@ -1,51 +0,0 @@
<div @flyInOut>
<div class="content">
<div class="d-flex">
<div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
</span>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2">
<button mat-icon-button [matMenuTriggerFor]="menuAeronef" aria-label="Menu Aeronef">
<mat-icon fontIcon="more_vert"></mat-icon>
</button>
<mat-menu #menuAeronef="matMenu">
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
<button mat-menu-item>
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
<span>{{ menuitem.name }}</span>
</button>
}
</mat-menu>
</div>
</div>
<mat-divider class="my-3"></mat-divider>
<mat-card>
<mat-card-header>
<mat-card-title>{{ subtitle }}</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-4">
@if (displayCharts) {
<div class="row">
<div class="col-xl-4 col-lg-5 col-md-6 col-sm-12">
<app-pie-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [colors]="seriesColor"></app-pie-chart>
</div>
<div class="col-xl-8 col-lg-7 col-md-6 col-sm-12">
<!--<app-bar-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [colors]="seriesColor" label="Nombre total de sauts" [legend]="false"></app-bar-chart>-->
<app-bars-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor" [legend]="false"></app-bars-chart>
</div>
</div>
}
<div class="d-flex">
<div class="flex-fill py-2">
<mat-divider class="mt-3 mb-0"></mat-divider>
<app-history-table [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [rows]="seriesRow" [colors]="seriesColorClass"></app-history-table>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
</div>
@@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AeronefComponent } from './aeronef.component';
describe('AeronefComponent', () => {
let component: AeronefComponent;
let fixture: ComponentFixture<AeronefComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AeronefComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AeronefComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,49 @@
<div @flyInOut>
<div class="d-flex">
<div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
</span>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2">
<button mat-icon-button [matMenuTriggerFor]="menuAeronef" aria-label="Menu Aeronef">
<mat-icon fontIcon="more_vert"></mat-icon>
</button>
<mat-menu #menuAeronef="matMenu">
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
<button mat-menu-item>
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
<span>{{ menuitem.name }}</span>
</button>
}
</mat-menu>
</div>
</div>
<mat-divider class="my-3"></mat-divider>
<mat-card>
<mat-card-header>
<mat-card-title>{{ subtitle }}</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-4">
@if (displayCharts) {
<div class="row">
<div class="col-xl-4 col-lg-5 col-md-6 col-sm-12">
<app-pie-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [colors]="seriesColor"></app-pie-chart>
</div>
<div class="col-xl-8 col-lg-7 col-md-6 col-sm-12">
<!--<app-bar-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [colors]="seriesColor" label="Nombre total de sauts" [legend]="false"></app-bar-chart>-->
<app-bars-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor" [legend]="false"></app-bars-chart>
</div>
</div>
}
<div class="d-flex">
<div class="flex-fill py-2">
<mat-divider class="mt-3 mb-0"></mat-divider>
<app-history-table [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [rows]="seriesRow" [colors]="seriesColorClass"></app-history-table>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
@@ -1,4 +1,4 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AeronefsComponent } from './aeronefs.component'; import { AeronefsComponent } from './aeronefs.component';
@@ -6,14 +6,12 @@ describe('AeronefsComponent', () => {
let component: AeronefsComponent; let component: AeronefsComponent;
let fixture: ComponentFixture<AeronefsComponent>; let fixture: ComponentFixture<AeronefsComponent>;
beforeEach(waitForAsync(() => { beforeEach(async () => {
TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [AeronefsComponent] imports: [AeronefsComponent]
}) })
.compileComponents(); .compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AeronefsComponent); fixture = TestBed.createComponent(AeronefsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
@@ -21,7 +21,7 @@ import { UtilitiesService } from 'src/app/core/services';
import { AeronefByImat, AeronefByYear } from 'src/app/core/models'; import { AeronefByImat, AeronefByYear } from 'src/app/core/models';
@Component({ @Component({
selector: 'app-aeronef', selector: 'app-aeronefs',
standalone: true, standalone: true,
imports: [ imports: [
DatePipe, DatePipe,
@@ -29,8 +29,8 @@ import { AeronefByImat, AeronefByYear } from 'src/app/core/models';
MatExpansionModule, MatGridListModule, MatIconModule, MatMenuModule, MatExpansionModule, MatGridListModule, MatIconModule, MatMenuModule,
BarsChartComponent, HistoryTableComponent, PieChartComponent BarsChartComponent, HistoryTableComponent, PieChartComponent
], ],
templateUrl: './aeronef.component.html', templateUrl: './aeronefs.component.html',
styleUrl: './aeronef.component.scss', styleUrl: './aeronefs.component.scss',
animations: [ animations: [
trigger('flyInOut', [ trigger('flyInOut', [
state('in', style({ transform: 'translateX(0)' })), state('in', style({ transform: 'translateX(0)' })),
@@ -45,7 +45,7 @@ import { AeronefByImat, AeronefByYear } from 'src/app/core/models';
]) ])
] ]
}) })
export class AeronefComponent implements OnInit, OnDestroy { export class AeronefsComponent implements OnInit, OnDestroy {
private _data: Subscription = new Subscription(); private _data: Subscription = new Subscription();
private _aeronefsByImat!: Array<AeronefByImat>; private _aeronefsByImat!: Array<AeronefByImat>;
private _aeronefsByYear!: Array<AeronefByYear>; private _aeronefsByYear!: Array<AeronefByYear>;
+1 -1
View File
@@ -1,4 +1,4 @@
<div class="container content auth-page"> <div class="container auth-page">
<div class="row"> <div class="row">
<div class="col-md-6 offset-md-3 col-xs-12"> <div class="col-md-6 offset-md-3 col-xs-12">
<h1 class="text-xs-center">{{ title }}</h1> <h1 class="text-xs-center">{{ title }}</h1>
@@ -1,5 +1,4 @@
<div @flyInOut> <div @flyInOut>
<div class="calculator-page content">
<div class="d-flex"> <div class="d-flex">
<div class="flex-fill"> <div class="flex-fill">
<h1 class="mb-1">{{title}}</h1> <h1 class="mb-1">{{title}}</h1>
@@ -56,7 +55,7 @@
<table class="table table-striped table-dark table-hover mat-elevation-z2"> <table class="table table-striped table-dark table-hover mat-elevation-z2">
<thead> <thead>
<tr class="warn"> <tr class="warn">
<th class="first"></th> <th class="text-nowrap pe-4"></th>
<th class="text-nowrap">Taille actuelle</th> <th class="text-nowrap">Taille actuelle</th>
<th class="text-nowrap">Taille min</th> <th class="text-nowrap">Taille min</th>
<th class="text-nowrap">Taille min -11%</th> <th class="text-nowrap">Taille min -11%</th>
@@ -64,34 +63,34 @@
</thead> </thead>
<tbody> <tbody>
<tr class="warn"> <tr class="warn">
<th class="first">Taille de voile en ft<sup>2</sup></th> <th class="text-nowrap pe-4">Taille de voile en ft<sup>2</sup></th>
<td class="font-monospace text-{{ info.state.color }}"> <td class="font-monospace text-{{ info.state.color }}">
{{ info.sizesFeet.current }} </td> {{ info.sizesFeet.current }} </td>
<td class="font-monospace text-success"> ● {{ info.sizesFeet.min }} </td> <td class="font-monospace text-success"> ● {{ info.sizesFeet.min }} </td>
<td class="font-monospace text-warning"> ● {{ info.sizesFeet.min11 }} </td> <td class="font-monospace text-warning"> ● {{ info.sizesFeet.min11 }} </td>
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Charge alaire</th> <th class="text-nowrap pe-4">Charge alaire</th>
<td class="font-monospace">{{ info.charges.current | number : '1.2-3' }}</td> <td class="font-monospace">{{ info.charges.current | number : '1.2-3' }}</td>
<td class="font-monospace">{{ info.charges.min | number : '1.2-3' }}</td> <td class="font-monospace">{{ info.charges.min | number : '1.2-3' }}</td>
<td class="font-monospace">{{ info.charges.min11 | number : '1.2-3' }}</td> <td class="font-monospace">{{ info.charges.min11 | number : '1.2-3' }}</td>
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Taille de voile en m<sup>2</sup></th> <th class="text-nowrap pe-4">Taille de voile en m<sup>2</sup></th>
<td class="font-monospace">{{ info.sizesMeter.current | number : '1.2-3' }}</td> <td class="font-monospace">{{ info.sizesMeter.current | number : '1.2-3' }}</td>
<td class="font-monospace">{{ info.sizesMeter.min | number : '1.2-3' }}</td> <td class="font-monospace">{{ info.sizesMeter.min | number : '1.2-3' }}</td>
<td class="font-monospace">{{ info.sizesMeter.min11 | number : '1.2-3' }}</td> <td class="font-monospace">{{ info.sizesMeter.min11 | number : '1.2-3' }}</td>
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Coefficient kilo/livres</th> <th class="text-nowrap pe-4">Coefficient kilo/livres</th>
<td colspan="3" class="font-monospace text-start">{{ coeffKgLbs }}</td> <td colspan="3" class="font-monospace text-start">{{ coeffKgLbs }}</td>
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Coefficient pied/mètre</th> <th class="text-nowrap pe-4">Coefficient pied/mètre</th>
<td colspan="3" class="font-monospace text-start">{{ coeffFtM }}</td> <td colspan="3" class="font-monospace text-start">{{ coeffFtM }}</td>
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Formule charge alaire</th> <th class="text-nowrap pe-4">Formule charge alaire</th>
<td colspan="3" class="font-monospace text-start"> <td colspan="3" class="font-monospace text-start">
( ( {{ inputs.weight }} + {{ inputs.gear }} ) * {{ coeffKgLbs }} ) / ( ( {{ inputs.weight }} + {{ inputs.gear }} ) * {{ coeffKgLbs }} ) /
{{ inputs.current }} = {{ info.charges.current | number : '1.5' }}<br /> {{ inputs.current }} = {{ info.charges.current | number : '1.5' }}<br />
@@ -129,7 +128,7 @@
<table class="table table-striped table-dark table-hover mat-elevation-z2 mb-1"> <table class="table table-striped table-dark table-hover mat-elevation-z2 mb-1">
<thead> <thead>
<tr class="warn"> <tr class="warn">
<th class="first"></th> <th class="text-nowrap pe-4"></th>
@for (header of sizesHeader; track header; let index = $index) { @for (header of sizesHeader; track header; let index = $index) {
<th class="pl-3 pr-3 {{ tableHeader[(index+1)].active }}">{{ header.name }}</th> <th class="pl-3 pr-3 {{ tableHeader[(index+1)].active }}">{{ header.name }}</th>
} }
@@ -137,26 +136,26 @@
</thead> </thead>
<tbody> <tbody>
<tr class="warn"> <tr class="warn">
<th class="first">Taille de voile en ft<sup>2</sup></th> <th class="firtext-nowrap pe-4st">Taille de voile en ft<sup>2</sup></th>
@for (value of sizesValuesFeet; track value; let index = $index) { @for (value of sizesValuesFeet; track value; let index = $index) {
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td> <td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td>
} }
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Abbattement 11%</th> <th class="text-nowrap pe-4">Abbattement 11%</th>
@for (value of sizesMinValuesFeet; track value; let index = $index) { @for (value of sizesMinValuesFeet; track value; let index = $index) {
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td> <td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td>
} }
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Charge alaire</th> <th class="text-nowrap pe-4">Charge alaire</th>
@for (charge of sizesCharges; track charge; let index = $index) { @for (charge of sizesCharges; track charge; let index = $index) {
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}"> <td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">
{{ charge | number : '1.2-3' }}</td> {{ charge | number : '1.2-3' }}</td>
} }
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Taille de voile en m<sup>2</sup></th> <th class="text-nowrap pe-4">Taille de voile en m<sup>2</sup></th>
@for (value of sizesValuesMeter; track value; let index = $index) { @for (value of sizesValuesMeter; track value; let index = $index) {
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}"> <td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">
{{ value | number : '1.2-2' }}</td> {{ value | number : '1.2-2' }}</td>
@@ -175,7 +174,7 @@
class="table table-striped table-dark table-hover mat-elevation-z2"> class="table table-striped table-dark table-hover mat-elevation-z2">
<!-- weight column --> <!-- weight column -->
<ng-container matColumnDef="weight"> <ng-container matColumnDef="weight">
<th mat-header-cell *matHeaderCellDef mat-sort-header class="first {{tableHeader[0].active}}"> <th mat-header-cell *matHeaderCellDef mat-sort-header class="text-nowrap pe-4 {{tableHeader[0].active}}">
{{tableHeader[0].name}} </th> {{tableHeader[0].name}} </th>
<th mat-cell *matCellDef="let element;" class="text-end ps-3 pe-4 fs-6" role="button" <th mat-cell *matCellDef="let element;" class="text-end ps-3 pe-4 fs-6" role="button"
(click)="setCurrentWeight(element.weight)"> (click)="setCurrentWeight(element.weight)">
@@ -255,5 +254,4 @@
</div> </div>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</div>
</div> </div>
@@ -0,0 +1,56 @@
<div @flyInOut>
<div class="d-flex">
<div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
</span>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2">
<button mat-icon-button [matMenuTriggerFor]="menuCanopyModel" aria-label="Menu Canopy Model">
<mat-icon fontIcon="more_vert"></mat-icon>
</button>
<mat-menu #menuCanopyModel="matMenu">
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
<button mat-menu-item>
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
<span>{{ menuitem.name }}</span>
</button>
}
</mat-menu>
</div>
</div>
<mat-divider class="my-3"></mat-divider>
<mat-card>
<mat-card-header>
<mat-card-title>{{ subtitle }}</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-4">
@if (displayCharts) {
<div class="row">
<div class="col-xl-3 col-lg-6 col-md-6 col-sm-12">
<app-pie-chart [headers]="seriesModelHeader" [names]="seriesModelName" [values]="seriesModelValue" [colors]="seriesColor"></app-pie-chart>
</div>
<div class="col-xl-3 col-lg-6 col-md-6 col-sm-12">
<app-pie-chart [headers]="seriesSizeHeader" [names]="seriesSizeName" [values]="seriesSizeValue" [colors]="seriesColor"></app-pie-chart>
</div>
<div class="col-xl-6 col-lg-12 col-md-12">
<!--<app-pie-chart [headers]="seriesSizeHeader" [names]="seriesSizeName" [values]="seriesSizeValue" [colors]="seriesColor"></app-pie-chart>-->
<app-bars-chart [headers]="seriesSizeHeader" [names]="seriesSizeName" [values]="seriesSizeRow" [colors]="seriesColor" [legend]="false"></app-bars-chart>
</div>
</div>
}
<div class="row">
<div class="col-xl-6 col-lg-12">
<mat-divider class="mt-3 mb-0"></mat-divider>
<app-history-table [headers]="seriesModelHeader" [names]="seriesModelName" [values]="seriesModelValue" [rows]="seriesModelRow" [colors]="seriesColorClass"></app-history-table>
</div>
<div class="col-xl-6 col-lg-12">
<mat-divider class="mt-3 mb-0"></mat-divider>
<app-history-table [headers]="seriesSizeHeader" [names]="seriesSizeName" [values]="seriesSizeValue" [rows]="seriesSizeRow" [colors]="seriesColorClass"></app-history-table>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
@@ -1,18 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CanopyComponent } from './canopy.component'; import { CanopiesComponent } from './canopies.component';
describe('CanopyComponent', () => { describe('CanopiesComponent', () => {
let component: CanopyComponent; let component: CanopiesComponent;
let fixture: ComponentFixture<CanopyComponent>; let fixture: ComponentFixture<CanopiesComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [CanopyComponent] imports: [CanopiesComponent]
}) })
.compileComponents(); .compileComponents();
fixture = TestBed.createComponent(CanopyComponent); fixture = TestBed.createComponent(CanopiesComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -20,7 +20,7 @@ import { UtilitiesService } from 'src/app/core/services';
import { CanopyBySize, CanopyByYear, CanopyModelBySize, CanopyModelByYear } from 'src/app/core/models'; import { CanopyBySize, CanopyByYear, CanopyModelBySize, CanopyModelByYear } from 'src/app/core/models';
@Component({ @Component({
selector: 'app-canopy', selector: 'app-canopies',
standalone: true, standalone: true,
imports: [ imports: [
DatePipe, DatePipe,
@@ -28,8 +28,8 @@ import { CanopyBySize, CanopyByYear, CanopyModelBySize, CanopyModelByYear } from
MatGridListModule, MatIconModule, MatMenuModule, MatGridListModule, MatIconModule, MatMenuModule,
HistoryTableComponent, BarsChartComponent, PieChartComponent HistoryTableComponent, BarsChartComponent, PieChartComponent
], ],
templateUrl: './canopy.component.html', templateUrl: './canopies.component.html',
styleUrl: './canopy.component.scss', styleUrl: './canopies.component.scss',
animations: [ animations: [
trigger('flyInOut', [ trigger('flyInOut', [
state('in', style({ transform: 'translateX(0)' })), state('in', style({ transform: 'translateX(0)' })),
@@ -44,7 +44,7 @@ import { CanopyBySize, CanopyByYear, CanopyModelBySize, CanopyModelByYear } from
]) ])
] ]
}) })
export class CanopyComponent implements OnInit, OnDestroy { export class CanopiesComponent implements OnInit, OnDestroy {
private _data: Subscription = new Subscription(); private _data: Subscription = new Subscription();
private _canopiesModelBySize!: Array<CanopyModelBySize>; private _canopiesModelBySize!: Array<CanopyModelBySize>;
private _canopiesModelByYear!: Array<CanopyModelByYear>; private _canopiesModelByYear!: Array<CanopyModelByYear>;
@@ -1,58 +0,0 @@
<div @flyInOut>
<div class="content">
<div class="d-flex">
<div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
</span>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2">
<button mat-icon-button [matMenuTriggerFor]="menuCanopyModel" aria-label="Menu Canopy Model">
<mat-icon fontIcon="more_vert"></mat-icon>
</button>
<mat-menu #menuCanopyModel="matMenu">
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
<button mat-menu-item>
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
<span>{{ menuitem.name }}</span>
</button>
}
</mat-menu>
</div>
</div>
<mat-divider class="my-3"></mat-divider>
<mat-card>
<mat-card-header>
<mat-card-title>{{ subtitle }}</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-4">
@if (displayCharts) {
<div class="row">
<div class="col-xl-3 col-lg-6 col-md-6 col-sm-12">
<app-pie-chart [headers]="seriesModelHeader" [names]="seriesModelName" [values]="seriesModelValue" [colors]="seriesColor"></app-pie-chart>
</div>
<div class="col-xl-3 col-lg-6 col-md-6 col-sm-12">
<app-pie-chart [headers]="seriesSizeHeader" [names]="seriesSizeName" [values]="seriesSizeValue" [colors]="seriesColor"></app-pie-chart>
</div>
<div class="col-xl-6 col-lg-12 col-md-12">
<!--<app-pie-chart [headers]="seriesSizeHeader" [names]="seriesSizeName" [values]="seriesSizeValue" [colors]="seriesColor"></app-pie-chart>-->
<app-bars-chart [headers]="seriesSizeHeader" [names]="seriesSizeName" [values]="seriesSizeRow" [colors]="seriesColor" [legend]="false"></app-bars-chart>
</div>
</div>
}
<div class="row">
<div class="col-xl-6 col-lg-12">
<mat-divider class="mt-3 mb-0"></mat-divider>
<app-history-table [headers]="seriesModelHeader" [names]="seriesModelName" [values]="seriesModelValue" [rows]="seriesModelRow" [colors]="seriesColorClass"></app-history-table>
</div>
<div class="col-xl-6 col-lg-12">
<mat-divider class="mt-3 mb-0"></mat-divider>
<app-history-table [headers]="seriesSizeHeader" [names]="seriesSizeName" [values]="seriesSizeValue" [rows]="seriesSizeRow" [colors]="seriesColorClass"></app-history-table>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
</div>
@@ -1,10 +1,9 @@
<div class="content"> <h1 class="mb-1">{{title}}</h1>
<h1 class="mb-1">{{title}}</h1> <span [hidden]="!lastJump.numero" class="me-2 fst-italic0 fs-6">
<span [hidden]="!lastJump.numero" class="me-2 fst-italic0 fs-6">
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span> Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
</span> </span>
<mat-divider class="my-3"></mat-divider> <mat-divider class="my-3"></mat-divider>
<ng-container *appShowAuthed="true"> <ng-container *appShowAuthed="true">
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
<app-jumps-by-month></app-jumps-by-month> <app-jumps-by-month></app-jumps-by-month>
@@ -12,15 +11,15 @@
</div> </div>
<div class="row mt-sm-2 mt-lg-3"> <div class="row mt-sm-2 mt-lg-3">
<div class="col-md-4 col-sm-12"> <div class="col-md-4 col-sm-12">
<app-drop-zones></app-drop-zones> <app-dropzones-pie></app-dropzones-pie>
</div> </div>
<div class="col-md-8 col-sm-12"> <div class="col-md-8 col-sm-12">
<app-drop-zones-bar></app-drop-zones-bar> <app-dropzones-bar></app-dropzones-bar>
</div> </div>
</div> </div>
<div class="row mt-sm-2 mt-lg-3"> <div class="row mt-sm-2 mt-lg-3">
<div class="col-md-4 col-sm-12"> <div class="col-md-4 col-sm-12">
<app-aeronefs></app-aeronefs> <app-aeronefs-pie></app-aeronefs-pie>
</div> </div>
<div class="col-md-8 col-sm-12"> <div class="col-md-8 col-sm-12">
<app-aeronefs-bar></app-aeronefs-bar> <app-aeronefs-bar></app-aeronefs-bar>
@@ -28,11 +27,10 @@
</div> </div>
<div class="row mt-sm-2 mt-lg-3"> <div class="row mt-sm-2 mt-lg-3">
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12">
<app-canopy-models></app-canopy-models> <app-canopies-models></app-canopies-models>
</div> </div>
<div class="col-md-6 col-sm-12"> <div class="col-md-6 col-sm-12">
<app-canopy-sizes></app-canopy-sizes> <app-canopies-sizes></app-canopies-sizes>
</div> </div>
</div> </div>
</ng-container> </ng-container>
</div>
@@ -12,9 +12,9 @@ import { Aeronef, AeronefByImat, Canopy, CanopyModelBySize, DropZone, DropZoneBy
import { JumpsService, UserService } from 'src/app/core/services'; import { JumpsService, UserService } from 'src/app/core/services';
import { ShowAuthedDirective } from 'src/app/components/shared/show-authed.directive'; import { ShowAuthedDirective } from 'src/app/components/shared/show-authed.directive';
import { import {
AeronefsComponent, AeronefsBarComponent, AeronefsPieComponent, AeronefsBarComponent,
CanopyModelsComponent, CanopySizesComponent, CanopiesModelsComponent, CanopiesSizesComponent,
DropZonesComponent, DropZonesBarComponent, DropzonesPieComponent, DropzonesBarComponent,
JumpsByMonthComponent JumpsByMonthComponent
} from 'src/app/components/shared/dashboard-components'; } from 'src/app/components/shared/dashboard-components';
@@ -22,9 +22,9 @@ import {
standalone: true, standalone: true,
imports: [ imports: [
DatePipe, ShowAuthedDirective, MatDividerModule, DatePipe, ShowAuthedDirective, MatDividerModule,
AeronefsComponent, AeronefsBarComponent, AeronefsPieComponent, AeronefsBarComponent,
CanopyModelsComponent, CanopySizesComponent, CanopiesModelsComponent, CanopiesSizesComponent,
DropZonesComponent, DropZonesBarComponent, DropzonesPieComponent, DropzonesBarComponent,
JumpsByMonthComponent JumpsByMonthComponent
], ],
selector: 'app-dashboard', selector: 'app-dashboard',
@@ -1,57 +0,0 @@
<div @flyInOut>
<div class="content">
<div class="d-flex">
<div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
</span>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2">
<button mat-icon-button [matMenuTriggerFor]="menuDropzone" aria-label="Menu Dropzone">
<mat-icon fontIcon="more_vert"></mat-icon>
</button>
<mat-menu #menuDropzone="matMenu">
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
<button mat-menu-item>
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
<span>{{ menuitem.name }}</span>
</button>
}
</mat-menu>
</div>
</div>
<mat-divider class="my-3"></mat-divider>
<mat-card>
<mat-card-header>
<mat-card-title>{{ subtitle }}</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-4">
@if (displayCharts) {
<div class="row">
<div class="col-xl-4 col-lg-5 col-md-6 col-sm-12">
<app-pie-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [colors]="seriesColor"></app-pie-chart>
</div>
<div class="col-xl-8 col-lg-7 col-md-6 col-sm-12">
<app-bars-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor" [legend]="false"></app-bars-chart>
<!--
@if (displayCharts) {
<div class="barchart position-relative w-100 my-1">
<x-chartist [configuration]="chartBarConfig"></x-chartist>
</div>
}
-->
</div>
</div>
}
<div class="d-flex">
<div class="flex-fill py-2">
<mat-divider class="mt-3 mb-0"></mat-divider>
<app-history-table [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [rows]="seriesRow" [colors]="seriesColorClass"></app-history-table>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
</div>
@@ -0,0 +1,55 @@
<div @flyInOut>
<div class="d-flex">
<div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
</span>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2">
<button mat-icon-button [matMenuTriggerFor]="menuDropzone" aria-label="Menu Dropzone">
<mat-icon fontIcon="more_vert"></mat-icon>
</button>
<mat-menu #menuDropzone="matMenu">
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
<button mat-menu-item>
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
<span>{{ menuitem.name }}</span>
</button>
}
</mat-menu>
</div>
</div>
<mat-divider class="my-3"></mat-divider>
<mat-card>
<mat-card-header>
<mat-card-title>{{ subtitle }}</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-4">
@if (displayCharts) {
<div class="row">
<div class="col-xl-4 col-lg-5 col-md-6 col-sm-12">
<app-pie-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [colors]="seriesColor"></app-pie-chart>
</div>
<div class="col-xl-8 col-lg-7 col-md-6 col-sm-12">
<app-bars-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor" [legend]="false"></app-bars-chart>
<!--
@if (displayCharts) {
<div class="barchart position-relative w-100 my-1">
<x-chartist [configuration]="chartBarConfig"></x-chartist>
</div>
}
-->
</div>
</div>
}
<div class="d-flex">
<div class="flex-fill py-2">
<mat-divider class="mt-3 mb-0"></mat-divider>
<app-history-table [headers]="seriesHeader" [names]="seriesName" [values]="seriesValue" [rows]="seriesRow" [colors]="seriesColorClass"></app-history-table>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
@@ -1,18 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DropzoneComponent } from './dropzone.component'; import { DropzonesComponent } from './dropzones.component';
describe('DropzoneComponent', () => { describe('DropzonesComponent', () => {
let component: DropzoneComponent; let component: DropzonesComponent;
let fixture: ComponentFixture<DropzoneComponent>; let fixture: ComponentFixture<DropzonesComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [DropzoneComponent] imports: [DropzonesComponent]
}) })
.compileComponents(); .compileComponents();
fixture = TestBed.createComponent(DropzoneComponent); fixture = TestBed.createComponent(DropzonesComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -21,7 +21,7 @@ import { DropZoneByOaci, DropZoneByYear, DropZonesPageData, Jump } from 'src/app
import { UtilitiesService } from 'src/app/core/services'; import { UtilitiesService } from 'src/app/core/services';
@Component({ @Component({
selector: 'app-dropzone', selector: 'app-dropzones',
standalone: true, standalone: true,
imports: [ imports: [
DatePipe, DatePipe,
@@ -30,8 +30,8 @@ import { UtilitiesService } from 'src/app/core/services';
//ChartistModule, //ChartistModule,
HistoryTableComponent, BarsChartComponent, PieChartComponent HistoryTableComponent, BarsChartComponent, PieChartComponent
], ],
templateUrl: './dropzone.component.html', templateUrl: './dropzones.component.html',
styleUrl: './dropzone.component.scss', styleUrl: './dropzones.component.scss',
animations: [ animations: [
trigger('flyInOut', [ trigger('flyInOut', [
state('in', style({ transform: 'translateX(0)' })), state('in', style({ transform: 'translateX(0)' })),
@@ -46,7 +46,7 @@ import { UtilitiesService } from 'src/app/core/services';
]) ])
] ]
}) })
export class DropzoneComponent implements OnInit, OnDestroy { export class DropzonesComponent implements OnInit, OnDestroy {
private _data: Subscription = new Subscription(); private _data: Subscription = new Subscription();
private _lastjump: Subscription = new Subscription(); private _lastjump: Subscription = new Subscription();
private _dropzonesByOaci!: Array<DropZoneByOaci>; private _dropzonesByOaci!: Array<DropZoneByOaci>;
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
/* Home */
//@use 'variable' as var;
/*
#FF41F8
#F0060B
#F0070C
#CC26D5
#7702FF
#35A2EB
#B7DDD0
#B0D9CB
#D9CAAE
#9788C7
*/
@@ -41,6 +41,8 @@ export class HomeComponent implements OnInit {
public errors: Errors = { errors: {} }; public errors: Errors = { errors: {} };
public lastJump: Jump = {} as Jump; public lastJump: Jump = {} as Jump;
public destroyRef = inject(DestroyRef); public destroyRef = inject(DestroyRef);
public now = new Date();
public isJumpDay = false;
constructor( constructor(
private _jumpsService: JumpsService private _jumpsService: JumpsService
@@ -50,6 +52,12 @@ export class HomeComponent implements OnInit {
this._lastjump$ = this._jumpsService.getLastJump().pipe(takeUntilDestroyed(this.destroyRef)); this._lastjump$ = this._jumpsService.getLastJump().pipe(takeUntilDestroyed(this.destroyRef));
this._lastjump = this._lastjump$.subscribe((jump) => { this._lastjump = this._lastjump$.subscribe((jump) => {
this.lastJump = jump; this.lastJump = jump;
this.isJumpDay = this._isToday(new Date(this.lastJump.date));
}); });
} }
private _isToday(someDate: Date) {
return someDate.getDate() == this.now.getDate() &&
someDate.getMonth() == this.now.getMonth() &&
someDate.getFullYear() == this.now.getFullYear()
}
} }
+4 -3
View File
@@ -1,11 +1,12 @@
export * from './auth/auth.component'; export * from './auth/auth.component';
export * from './aeronef/aeronef.component'; export * from './aeronefs/aeronefs.component';
export * from './calculator/calculator.component'; export * from './calculator/calculator.component';
export * from './canopy/canopy.component'; export * from './canopies/canopies.component';
export * from './dashboard/dashboard.component'; export * from './dashboard/dashboard.component';
export * from './dropzone/dropzone.component'; export * from './dropzones/dropzones.component';
export * from './home/home.component'; export * from './home/home.component';
export * from './jump/jump.component'; export * from './jump/jump.component';
export * from './jumps/jumps.component';
export * from './logbook/logbook.component'; export * from './logbook/logbook.component';
export * from './profile/profile.component'; export * from './profile/profile.component';
export * from './qcm/qcm.component'; export * from './qcm/qcm.component';
+256 -105
View File
@@ -1,132 +1,283 @@
<div @flyInOut> <div class="d-flex">
<div class="content">
<div class="d-flex">
<div class="flex-fill"> <div class="flex-fill">
<h1 class="mb-1">{{title}}</h1> <h1 class="mb-1">
<span [hidden]="!lastJump.numero" class="me-2 fs-6"> {{title}}
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span> <span class="fs-3 text-accent ms-2">{{ jump.categorie }} {{ jump.module }}</span>
<span class="fs-3 text-muted ms-2">{{ jump.voile }} {{ jump.taille }}</span>
</h1>
<span class="me-2 fs-6">
Le <span class="fs-5 mx-1 text-primary">{{ jump.date | date: 'dd/MM/yyyy' }}</span>
à <span class="fs-5 mx-1 text-primary">{{ jump.lieu }} {{ jump.oaci }}</span>
@if (jump.x2data) {
depuis <span class="fs-5 mx-1 text-primary">{{ jump.aeronef }} {{ jump.imat }}</span>
}
</span> </span>
</div> </div>
<span class="flex-spacer"></span> <span class="flex-spacer"></span>
<div class="align-self-center me-4">
<button mat-button [routerLink]="['/jump', prevSlug]" aria-label="Saut précédent" color="accent">
<mat-icon fontIcon="navigate_before"></mat-icon> {{ jump.numero - 1}}
</button>
<div class="vr mx-2 fs-3 align-middle"></div>
<span class="mx-2 align-middle">{{ jump.numero }}</span>
<div class="vr mx-2 fs-3 align-middle"></div>
<button mat-button [routerLink]="['/jump', nextSlug]" aria-label="Saut suivant" color="accent">
<mat-icon fontIcon="navigate_next" iconPositionEnd></mat-icon> {{ jump.numero +1}}
</button>
</div>
<div class="align-self-center me-2"> <div class="align-self-center me-2">
<button mat-icon-button [matMenuTriggerFor]="menuAeronef" aria-label="Menu Aeronef"> <button mat-icon-button [matMenuTriggerFor]="menuJump" aria-label="Menu saut">
<mat-icon fontIcon="more_vert"></mat-icon> <mat-icon fontIcon="more_vert"></mat-icon>
</button> </button>
<mat-menu #menuAeronef="matMenu"> <mat-menu #menuJump="matMenu">
@for (menuitem of menuItems.getMenuPanel(); track menuitem) { @if (jump.x2data) {
<button mat-menu-item> <button mat-menu-item>
<mat-icon [fontIcon]="menuitem.icon"></mat-icon> <mat-icon fontIcon="language" color="accent"></mat-icon>
<span>{{ menuitem.name }}</span> <span class="text-accent fw-medium">Obtenir le KML</span>
</button>
<mat-divider></mat-divider>
}
@for (menuitem of menuItems.getMenuJump(); track menuitem) {
<button mat-menu-item>
<mat-icon [fontIcon]="menuitem.icon" [color]="menuitem.color"></mat-icon>
<span class="text-{{menuitem.color}} fw-medium">{{ menuitem.name }}</span>
</button> </button>
} }
</mat-menu> </mat-menu>
</div> </div>
</div> </div>
<mat-divider class="my-3"></mat-divider> <mat-divider class="my-3"></mat-divider>
<mat-card> <mat-card>
<mat-card-header> <mat-card-content>
<mat-card-title>{{ subtitle }}</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-2">
@if(displayCharts) {
<div class="row"> <div class="row">
<div class="col-xxl-6 col-xs-12"> <div class="col-lg-6 col-md-12">
<app-linearea-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor"></app-linearea-chart> <dl class="dl-horizontal">
<dt class="text-light-info fw-normal">Aeronef:</dt>
<dd>{{ jump.aeronef }} {{ jump.imat }}</dd>
<dt class="text-light-info fw-normal">Voile:</dt>
<dd>{{ jump.voile }} {{ jump.taille }} ft<sup>2</sup></dd>
@if (jump.programme) {
<dt class="text-light-info fw-normal">Programme:</dt>
<dd>{{ jump.programme }}</dd>
}
</dl>
</div> </div>
<div class="col-xxl-6 col-xs-12"> <div class="col-lg-6 col-md-12">
<app-bars-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor" [legend]="true"></app-bars-chart> <dl class="dl-horizontal">
@if (!jump.x2data) {
<dt class="text-light-info fw-normal">Hauteur:</dt>
<dd>{{ jump.hauteur }} m</dd>
<dt class="text-light-info fw-normal">Déploiement:</dt>
<dd>{{ jump.deploiement }} m</dd>
}
@for (file of jump.files; track file) {
<dt class="text-light-info fw-normal">Fichier {{ file.type }}</dt>
<dd>{{ file.name }}</dd>
}
</dl>
</div> </div>
</div> </div>
</mat-card-content>
</mat-card>
@if (jump.x2data) {
<mat-card class="mt-3">
<mat-card-content>
<div class="row my-3">
<div class="col-sm-4 col-xs-12 text-center">
<h6>Hauteur</h6>
@if (jump.x2data.altitude.exit > 4200) {
<mat-icon class="align-middle fs-5 me-2" fontIcon="mood" color="primary"></mat-icon>
}
<span class="text-primary fw-medium fs-5">{{ jump.x2data.altitude.exit }} m</span>
</div>
<div class="col-sm-4 col-xs-12 text-center">
<h6 class="d-flex justify-content-center"><mat-icon fontIcon="timer" class="me-1 fs-5"></mat-icon> Durée de la chute</h6>
<span class="text-primary fw-medium fs-4">{{ formatDuration(jump.x2data.duration.freeFall) }}</span>
</div>
<div class="col-sm-4 col-xs-12 text-center">
<h6>Déploiement</h6>
@if (jump.x2data.altitude.deployment < 650) {
<mat-icon class="align-middle fs-5 me-2" fontIcon="warning" color="raspberry"></mat-icon>
<span class="text-raspberry fw-medium fs-5">{{ jump.x2data.altitude.deployment }} m</span>
}
@else if (jump.x2data.altitude.deployment < 750) {
<mat-icon class="align-middle fs-5 me-2" fontIcon="local_florist" color="warn"></mat-icon>
<span class="text-warn fw-medium fs-5">{{ jump.x2data.altitude.deployment }} m</span>
}
@else if (jump.x2data.altitude.deployment > 1000) {
<mat-icon class="align-middle fs-5 me-2" fontIcon="mood" color="primary"></mat-icon>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.altitude.deployment }} m</span>
}
</div>
</div>
<div class="row mt-4">
<div class="{{ jump.accessoires ? 'col-sm-6 ' : '' }}col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-4"><mat-icon fontIcon="people" class="me-2"></mat-icon> Participants ({{ jump.sautants.length + 1 }})</h5>
@for (sautant of jump.sautants; track sautant) {
<span class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
{{ sautant }}
</span>
}
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
{{ jump.author.username }}
</span>
</div>
@if (jump.accessoires) {
<div class="col-sm-6 col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-4"><mat-icon fontIcon="auto_awesome" class="me-2"></mat-icon> Accessoires</h5>
<span class="badge rounded-pill bg-navy-light mb-2 me-1 pe-3 py-1 fw-normal">
<mat-icon class="align-middle me-1" fontIcon="auto_awesome"></mat-icon>
{{ jump.accessoires }}
</span>
</div>
} }
<div class="row"> </div>
<div class="col-xs-12"> </mat-card-content>
<mat-divider class="mt-3 mb-0"></mat-divider> </mat-card>
<table class="table table-striped table-dark table-hover"> <mat-card class="mt-3">
<thead> <mat-card-content>
<tr> <div class="row my-3">
<th></th> <div class="col-xs-12 text-center">
<th *ngFor='let name of seriesHeader; let i=index' class="text-end"> {{ name }} </th> <h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="speed" class="me-1"></mat-icon> Vitesse en chute</h5>
<th class="text-end">Total</th> </div>
</tr> <div class="col-sm-6 hidden-xs text-center">
</thead> <h6>Verticale</h6>
<tbody> </div>
<tr *ngFor='let values of seriesRow; let i=index'> <div class="col-sm-6 hidden-xs text-center">
<th class="{{seriesColorClass[i]}} text-end"> ● {{ seriesName[i] }} </th> <h6>Horizontale</h6>
<td *ngFor='let value of values; let i=index' class="font-monospace text-end"> </div>
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span> <div class="col-sm-3 col-xs-12 text-center">
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template> <h6>Vitesse maximale</h6>
</td> <span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.vertical.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
<th class="{{seriesColorClass[i]}} font-monospace text-end">{{ seriesRowTotal[i] }}</th> </div>
</tr> <div class="col-sm-3 col-xs-12 text-center">
</tbody> <h6>Vitesse moyenne</h6>
<tfoot> <span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.vertical.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
<tr> </div>
<th class="text-end">Total</th> <div class="col-sm-3 col-xs-12 text-center">
<th *ngFor='let value of seriesColTotal; let i=index' class="font-monospace text-end"> <h6>Vitesse maximale</h6>
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span> <span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.horizontal.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template> </div>
</th> <div class="col-sm-3 col-xs-12 text-center">
<th class="font-monospace text-end">{{ grandTotal }}</th> <h6>Vitesse moyenne</h6>
</tr> <span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.horizontal.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
<tr>
<th class="text-end">Moyenne</th>
<th *ngFor='let value of seriesRowAvg; let i=index' class="font-monospace text-end">
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
</th>
<th class="font-monospace text-end">{{ grandAvg | number : '1.0-1' }}</th>
</tr>
<tr>
<th class="text-end">Moyenne <small>3 dernières années</small></th>
<th *ngFor='let value of seriesRowAvgLastYears; let i=index' class="font-monospace text-end">
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
</th>
<th class="font-monospace text-end">{{ grandAvgLastYears | number : '1.0-1' }}</th>
</tr>
</tfoot>
</table>
</div> </div>
</div> </div>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
<mat-card class="mt-3"> <mat-card class="mt-3">
<mat-card-header> <mat-card-content>
<mat-card-title>Types de sauts</mat-card-title> <div class="row my-3">
</mat-card-header> <div class="col-xs-12 text-center">
<mat-card-content class="mt-2"> <h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="speed" class="me-1"></mat-icon> Vitesse sous voile</h5>
@if(displayCharts) { </div>
<div class="row"> <div class="col-sm-6 hidden-xs text-center">
<div class="col-xs-12"> <h6>Verticale</h6>
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRowCumulated" [colors]="seriesTypesColor" [legend]="true"></app-line-chart> </div>
<div class="col-sm-6 hidden-xs text-center">
<h6>Horizontale</h6>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Vitesse maximale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.vertical.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Vitesse moyenne</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.vertical.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Vitesse maximale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.horizontal.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Vitesse moyenne</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.horizontal.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div> </div>
</div> </div>
<mat-divider class="mt-2 mb-3"></mat-divider>
<div class="row">
@for (item of getCategories(); track $index) {
<div class="col-lg-2 col-sm-4 col-xs-6">
<app-circle-chart [headers]="seriesHeader" [names]="seriesName" [values]="[item.count, (lastJump.numero-item.count)]" [color]="$index" [label]="item.categorie"></app-circle-chart>
</div>
}
@for (item of getModules(); track $index) {
<div class="col-lg-2 col-sm-4 col-xs-6">
<app-circle-chart [headers]="seriesHeader" [names]="seriesName" [values]="[item.count, (lastJump.numero-item.count)]" [color]="getModuleColor($index)" [label]="item.module"></app-circle-chart>
</div>
}
</div>
<mat-divider class="my-3"></mat-divider>
<!--
<div class="row">
<div class="col-xxl-6 col-xs-12">
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRow" [colors]="seriesTypesColor" [legend]="true"></app-line-chart>
</div>
<div class="col-xxl-6 col-xs-12">
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRowCumulated" [colors]="seriesTypesColor" [legend]="true"></app-line-chart>
</div>
</div>
-->
}
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
<mat-card class="mt-3">
<mat-card-content>
<div class="row my-3">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="square_foot" class="me-1"></mat-icon> Finesse</h5>
</div> </div>
</div> <div class="col-sm-6 hidden-xs text-center">
<h6>En chute</h6>
</div>
<div class="col-sm-6 hidden-xs text-center">
<h6>Sous voile</h6>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Finesse Max</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.glideRatio.freeFall.max | number : '1.2-2' }}</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Finesse Moyenne</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.glideRatio.freeFall.avg | number : '1.2-2' }}</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Finesse Max</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.glideRatio.underCanopy.max | number : '1.2-2' }}</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Finesse Moyenne</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.glideRatio.underCanopy.avg | number : '1.2-2' }}</span>
</div>
</div>
</mat-card-content>
</mat-card>
<mat-card class="mt-3">
<mat-card-content>
<div class="row my-3">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="straighten" class="me-1"></mat-icon> Distance</h5>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Distance au largage</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.distance.exitFromLandingZone| number : '1.0-0' }} m</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Durée sous voile</h6>
<span class="text-primary fw-medium fs-5">{{ formatDuration(jump.x2data.duration.underCanopy) }}</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Distance totale</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.distance.totalFlying | number : '1.0-0' }} m</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Distance horizontale</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.distance.totalHorizontal | number : '1.0-0' }} m</span>
</div>
</div>
</mat-card-content>
</mat-card>
}
@else {
<mat-card class="mt-3">
<mat-card-content>
<div class="row my-3">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-4"><mat-icon fontIcon="people" class="me-1"></mat-icon> Participants ({{ jump.sautants.length + 1 }})</h5>
@for (sautant of jump.sautants; track sautant) {
<span class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
{{ sautant }}
</span>
}
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
{{ jump.author.username }}
</span>
</div>
</div>
</mat-card-content>
</mat-card>
<mat-card class="mt-3">
<mat-card-content>
<div class="d-flex justify-content-center my-3 fs-5"><mat-icon fontIcon="satellite_alt" class="me-2"></mat-icon> Aucune donnée GPS</div>
</mat-card-content>
</mat-card>
}
@@ -6,10 +6,12 @@ describe('JumpComponent', () => {
let component: JumpComponent; let component: JumpComponent;
let fixture: ComponentFixture<JumpComponent>; let fixture: ComponentFixture<JumpComponent>;
beforeEach(() => { beforeEach(async () => {
TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [JumpComponent] imports: [JumpComponent]
}); })
.compileComponents();
fixture = TestBed.createComponent(JumpComponent); fixture = TestBed.createComponent(JumpComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
+31 -208
View File
@@ -1,248 +1,71 @@
import { trigger, state, style, animate, transition } from '@angular/animations';
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core'; import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute } from '@angular/router'; import { DatePipe, DecimalPipe } from '@angular/common';
import { CommonModule } from '@angular/common'; import { ActivatedRoute, RouterLink } from '@angular/router';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider'; import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { MenuItems } from 'src/app/components/shared'; import { ListErrorsComponent, MenuItems } from 'src/app/components/shared';
import { BarsChartComponent, CircleChartComponent, LineChartComponent, LineAreaChartComponent } from 'src/app/components/shared/helpers-chart'; import { Errors, Jump, JumpPageData } from 'src/app/core/models';
import { UtilitiesService } from 'src/app/core/services';
import { Jump, JumpByCategorie, JumpByDate, JumpByDateByModule, JumpByModule, JumpsPageData, JumpYears } from 'src/app/core/models';
@Component({ @Component({
selector: 'app-jump', selector: 'app-jump',
standalone: true, standalone: true,
imports: [ imports: [
CommonModule, DatePipe, DecimalPipe, RouterLink,
MatButtonModule, MatCardModule, MatDividerModule, MatButtonModule, MatCardModule,
MatExpansionModule, MatIconModule, MatMenuModule, MatDividerModule, MatIconModule, MatMenuModule,
BarsChartComponent, CircleChartComponent, LineChartComponent, LineAreaChartComponent ListErrorsComponent
], ],
templateUrl: './jump.component.html', templateUrl: './jump.component.html',
styleUrl: './jump.component.scss', styleUrl: './jump.component.scss'
animations: [
trigger('flyInOut', [
state('in', style({ transform: 'translateX(0)' })),
transition('void => *', [
style({ transform: 'translateX(-100%)' }),
animate(200)
]),
transition('* => void', [
style({ transform: 'translateX(100%)' }),
animate(200)
])
])
]
}) })
export class JumpComponent implements OnInit, OnDestroy { export class JumpComponent implements OnInit, OnDestroy {
private _data: Subscription = new Subscription(); private _data: Subscription = new Subscription();
private _jumpsByCategorie: Array<JumpByCategorie> = []; public title: string = '';
private _jumpsByModule: Array<JumpByModule> = []; public errors: Errors = { errors: {} };
private _jumpsByDate: Array<JumpByDate> = [];
public lastJump: Jump = {} as Jump;
public title: string = 'Sauts';
public subtitle: string = 'Nombre total de sauts par mois';
public displayCharts = false;
public destroyRef = inject(DestroyRef); public destroyRef = inject(DestroyRef);
public min = 0; public jump: Jump = {} as Jump;
public max = 0; public prevSlug: string = '';
public grandAvg = 0; public nextSlug: string = '';
public grandAvgLastYears = 0;
public grandTotal = 0;
public seriesHeader: string[] = [];
public seriesName: string[] = [];
public seriesRow: Array<Array<number>> = [];
public seriesColor: {
backgroundColor: string[],
borderColor: string[]
} = {
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
};
public seriesTypesHeader: string[] = [];
public seriesTypesName: string[] = [];
public seriesTypesRow: Array<Array<number>> = [];
public seriesTypesRowCumulated: Array<Array<number>> = [];
public seriesTypesColor: {
backgroundColor: string[],
borderColor: string[]
} = {
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'pastels'),
borderColor: this._utilitiesService.getSeriesColors(1, 'pastels')
};
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
public seriesRowTotal: number[] = [];
public seriesRowAvg: number[] = [];
public seriesRowAvgLastYears: number[] = [];
public seriesColTotal: number[] = [];
public seriesColTotalClosed: number[] = [];
public seriesColTotalLastYears: number[] = [];
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private _utilitiesService: UtilitiesService,
public menuItems: MenuItems public menuItems: MenuItems
) { } ) { }
ngOnInit() { ngOnInit() {
const data$: Observable<{ jumpsPageData: JumpsPageData }> = this.route.data as Observable<{ jumpsPageData: JumpsPageData }>; const data$: Observable<{ pageData: JumpPageData }> = this.route.data as Observable<{ pageData: JumpPageData }>;
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { jumpsPageData: JumpsPageData }) => { this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { pageData: JumpPageData }) => {
const pageData: JumpsPageData = data.jumpsPageData; this.jump = data.pageData.jump;
this.lastJump = pageData.lastjump; this.prevSlug = data.pageData.prevJump?.slug;
const currentYear: number = new Date().getFullYear(); this.nextSlug = data.pageData.nextJump?.slug;
const types: Array<string> = ['FF', 'Solo', 'VR', 'Wingsuit']; this.title = `Saut n° ${this.jump.numero} `;
const verticals: Array<string> = ['Chutas', 'Head Up', 'Head Down'];
const others: Array<string> = ['Track', 'Trace'];
const modules: Array<string> = [...verticals, ...others];
this.min = pageData.jumpsByYears[0].year;
this.max = pageData.jumpsByYears[(pageData.jumpsByYears.length-1)].year;
this.seriesHeader = this._utilitiesService.getMonthsList();
const values: Array<number> = this.seriesHeader.map(() => 0);
this.seriesColTotal = [...values];
this.seriesColTotalClosed = [...values];
this.seriesColTotalLastYears = [...values];
this.seriesName = pageData.jumpsByYears.map((row: JumpYears) => {
this.seriesRow.push([...values]);
return row.year.toString();
});
this._jumpsByDate = pageData.jumpsByDate.map((row: JumpByDate) => {
this.seriesRow[(row.year-this.min)][(row.month-1)] = row.count;
this.seriesColTotal[(row.month-1)] += row.count;
if (row.year < currentYear) {
this.seriesColTotalClosed[(row.month-1)] += row.count;
}
if (row.year >= (currentYear-3) && row.year < currentYear) {
this.seriesColTotalLastYears[(row.month-1)] += row.count;
}
return row;
});
this.seriesRow.forEach((row: number[]) => {
const total = row.reduce((partialSum, a) => partialSum + a, 0);
this.seriesRowTotal.push(total);
});
this.seriesColTotalClosed.forEach((row: number) => {
const value: number = (row/(this.seriesName.length-2)); // -2 pour ne pas compter la première année
this.seriesRowAvg.push(parseInt(value.toFixed(0)));
});
this.seriesColTotalLastYears.forEach((row: number) => {
const value: number = (row/3); // Les 3 dernières années
this.seriesRowAvgLastYears.push(parseInt(value.toFixed(0)));
});
this.grandAvg = this.seriesRowAvg.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
this.grandAvgLastYears = this.seriesRowAvgLastYears.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
this.grandTotal = this.seriesColTotal.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
pageData.jumpsByCategory.forEach((row: JumpByCategorie) => {
if (types.indexOf(row.categorie) !== -1) {
this._jumpsByCategorie.push(row);
}
});
//const focus: Array<string> = ['FF', 'Solo']; // ['FF']
let verticalCount:number = 0;
let otherCount:number = 0;
pageData.jumpsByModule.forEach((row: JumpByModule) => {
//if (focus.indexOf(row.categorie) !== -1 && modules.indexOf(row.module) !== -1) {
if (modules.indexOf(row.module) !== -1) {
if (verticals.indexOf(row.module) !== -1) {
verticalCount += row.count;
} else {
otherCount += row.count;
}
}
});
if (verticalCount > 0) {
const row:JumpByModule = {
categorie: 'FF',
module: 'Vertical',
count: verticalCount
}
this._jumpsByModule.push(row);
}
if (otherCount > 0) {
const row:JumpByModule = {
categorie: 'FF',
module: 'Track/Trace',
count: otherCount
}
this._jumpsByModule.push(row);
}
for (let year = 0; year < pageData.jumpsByYears.length; year++) {
for (let index = 0; index < 12; index++) {
const month: string = ((index+1) < 10) ? `0${(index+1)}` : `${(index+1)}`;
this.seriesTypesHeader.push(`${month}-${pageData.jumpsByYears[year].year}`);
}
}
const emptyValues: Array<number> = this.seriesTypesHeader.map(() => 0);
this.seriesTypesName = types;
this.seriesTypesName.forEach(() => {
this.seriesTypesRow.push([...emptyValues]);
});
pageData.jumpsByDateByModule.forEach((row: JumpByDateByModule) => {
const indexOf: number = this.seriesTypesName.indexOf(row.categorie);
if (indexOf !== -1) {
this.seriesTypesRow[indexOf][(((row.year-this.min)*12)+row.month-1)] += row.count;
}
});
this.seriesTypesRowCumulated = this.seriesTypesRow.map((row: Array<number>) => {
let accumulated: number = 0;
return row.map((value: number) => {
accumulated += value;
return accumulated;
});
});
this.seriesTypesHeader = this.seriesTypesHeader.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
this.seriesTypesRow = this.seriesTypesRow.map((row: Array<number>) => {
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
});
this.seriesTypesRowCumulated = this.seriesTypesRowCumulated.map((row: Array<number>) => {
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
}); });
/* /*
console.log('this.min', this.min); const data$: Observable<Jump> = this.route.data as Observable<Jump>;
console.log('this.max', this.max); this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: Jump) => {
console.log('this.lastJump', this.lastJump); this.jump = data;
console.log('this.seriesHeader', this.seriesHeader); console.log('jump : ', this.jump);
console.log('this.seriesName', this.seriesName);
console.log('this.seriesRow', this.seriesRow);
console.log('this.seriesRowTotal', this.seriesRowTotal);
console.log('this.seriesRowAvg', this.seriesRowAvg);
console.log('this.seriesRowAvgLastYears', this.seriesRowAvgLastYears);
console.log('this._jumpsByCategorie', this._jumpsByCategorie);
console.log('this._jumpsByModule', this._jumpsByModule);
console.log('this.seriesTypesHeader', this.seriesTypesHeader);
console.log('this.seriesTypesName', this.seriesTypesName);
console.log('this.seriesTypesRow', this.seriesTypesRow);
console.log('this.seriesTypesRowCumulated', this.seriesTypesRowCumulated);
*/
this.displayCharts = true;
}); });
*/
} }
ngOnDestroy() { ngOnDestroy() {
this._data.unsubscribe(); this._data.unsubscribe();
} }
public getCategories(): Array<JumpByCategorie> { formatDuration(duration: number): string {
return this._jumpsByCategorie; const start = new Date(0);
start.setSeconds(duration);
return start.toISOString().substring(14, 19);
} }
public getModules(): Array<JumpByModule> { private _resetErrors(): void {
return this._jumpsByModule; this.errors = { errors: {} };
}
public getModuleColor(index: number): number {
return (this._jumpsByCategorie.length + index);
} }
} }
@@ -0,0 +1,130 @@
<div @flyInOut>
<div class="d-flex">
<div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
</span>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2">
<button mat-icon-button [matMenuTriggerFor]="menuJumps" aria-label="Menu sauts">
<mat-icon fontIcon="more_vert"></mat-icon>
</button>
<mat-menu #menuJumps="matMenu">
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
<button mat-menu-item>
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
<span>{{ menuitem.name }}</span>
</button>
}
</mat-menu>
</div>
</div>
<mat-divider class="my-3"></mat-divider>
<mat-card>
<mat-card-header>
<mat-card-title>Types de sauts</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-2">
@if(displayCharts) {
<div class="row">
<div class="col-xs-12">
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRowCumulated" [colors]="seriesTypesColor" [legend]="true" [hideZero]="true"></app-line-chart>
</div>
</div>
<mat-divider class="mt-2 mb-3"></mat-divider>
<div class="row">
@for (item of getCategories(); track $index) {
<div class="col-lg-2 col-sm-4 col-xs-6">
<app-circle-chart [headers]="seriesHeader" [names]="seriesName" [values]="[item.count, (lastJump.numero-item.count)]" [color]="$index" [label]="item.categorie"></app-circle-chart>
</div>
}
@for (item of getModules(); track $index) {
<div class="col-lg-2 col-sm-4 col-xs-6">
<app-circle-chart [headers]="seriesHeader" [names]="seriesName" [values]="[item.count, (lastJump.numero-item.count)]" [color]="getModuleColor($index)" [label]="item.module"></app-circle-chart>
</div>
}
</div>
<mat-divider class="my-3"></mat-divider>
<!--
<div class="row">
<div class="col-xxl-6 col-xs-12">
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRow" [colors]="seriesTypesColor" [legend]="true"></app-line-chart>
</div>
<div class="col-xxl-6 col-xs-12">
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRowCumulated" [colors]="seriesTypesColor" [legend]="true"></app-line-chart>
</div>
</div>
-->
}
</mat-card-content>
</mat-card>
<mat-card class="mt-3">
<mat-card-header>
<mat-card-title>{{ subtitle }}</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-2">
@if(displayCharts) {
<div class="row">
<div class="col-xxl-6 col-xs-12">
<app-linearea-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor"></app-linearea-chart>
</div>
<div class="col-xxl-6 col-xs-12">
<app-bars-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor" [legend]="true"></app-bars-chart>
</div>
</div>
}
<div class="row">
<div class="col-xs-12">
<mat-divider class="mt-3 mb-0"></mat-divider>
<table class="table table-striped table-dark table-hover">
<thead>
<tr>
<th></th>
<th *ngFor='let name of seriesHeader; let i=index' class="text-end"> {{ name }} </th>
<th class="text-end">Total</th>
</tr>
</thead>
<tbody>
<tr *ngFor='let values of seriesRow; let i=index'>
<th class="{{seriesColorClass[i]}} text-end"> ● {{ seriesName[i] }} </th>
<td *ngFor='let value of values; let i=index' class="font-monospace text-end">
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template>
</td>
<th class="{{seriesColorClass[i]}} font-monospace text-end">{{ seriesRowTotal[i] }}</th>
</tr>
</tbody>
<tfoot>
<tr>
<th class="text-end">Total</th>
<th *ngFor='let value of seriesColTotal; let i=index' class="font-monospace text-end">
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template>
</th>
<th class="font-monospace text-end">{{ grandTotal }}</th>
</tr>
<tr>
<th class="text-end">Moyenne</th>
<th *ngFor='let value of seriesRowAvg; let i=index' class="font-monospace text-end">
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
</th>
<th class="font-monospace text-end">{{ grandAvg | number : '1.0-1' }}</th>
</tr>
<tr>
<th class="text-end">Moyenne <small>3 dernières années</small></th>
<th *ngFor='let value of seriesRowAvgLastYears; let i=index' class="font-monospace text-end">
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
</th>
<th class="font-monospace text-end">{{ grandAvgLastYears | number : '1.0-1' }}</th>
</tr>
</tfoot>
</table>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { JumpsComponent } from './jumps.component';
describe('JumpsComponent', () => {
let component: JumpsComponent;
let fixture: ComponentFixture<JumpsComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [JumpsComponent]
});
fixture = TestBed.createComponent(JumpsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
+249
View File
@@ -0,0 +1,249 @@
import { trigger, state, style, animate, transition } from '@angular/animations';
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute } from '@angular/router';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { Observable, Subscription } from 'rxjs';
import { MenuItems } from 'src/app/components/shared';
import { BarsChartComponent, CircleChartComponent, LineChartComponent, LineAreaChartComponent } from 'src/app/components/shared/helpers-chart';
import { UtilitiesService } from 'src/app/core/services';
import { Jump, JumpByCategorie, JumpByDate, JumpByDateByModule, JumpByModule, JumpsPageData, JumpYears } from 'src/app/core/models';
@Component({
selector: 'app-jumps',
standalone: true,
imports: [
CommonModule,
MatButtonModule, MatCardModule, MatDividerModule,
MatExpansionModule, MatIconModule, MatMenuModule,
BarsChartComponent, CircleChartComponent, LineChartComponent, LineAreaChartComponent
],
templateUrl: './jumps.component.html',
styleUrl: './jumps.component.scss',
animations: [
trigger('flyInOut', [
state('in', style({ transform: 'translateX(0)' })),
transition('void => *', [
style({ transform: 'translateX(-100%)' }),
animate(200)
]),
transition('* => void', [
style({ transform: 'translateX(100%)' }),
animate(200)
])
])
]
})
export class JumpsComponent implements OnInit, OnDestroy {
private _data: Subscription = new Subscription();
private _jumpsByCategorie: Array<JumpByCategorie> = [];
private _jumpsByModule: Array<JumpByModule> = [];
private _jumpsByDate: Array<JumpByDate> = [];
public lastJump: Jump = {} as Jump;
public title: string = 'Sauts';
public subtitle: string = 'Nombre total de sauts par mois';
public displayCharts = false;
public destroyRef = inject(DestroyRef);
public min = 0;
public max = 0;
public grandAvg = 0;
public grandAvgLastYears = 0;
public grandTotal = 0;
public seriesHeader: string[] = [];
public seriesName: string[] = [];
public seriesRow: Array<Array<number>> = [];
public seriesColor: {
backgroundColor: string[],
borderColor: string[]
} = {
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
};
public seriesTypesHeader: string[] = [];
public seriesTypesName: string[] = [];
public seriesTypesRow: Array<Array<number>> = [];
public seriesTypesRowCumulated: Array<Array<number>> = [];
public seriesTypesColor: {
backgroundColor: string[],
borderColor: string[]
} = {
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'pastels'),
borderColor: this._utilitiesService.getSeriesColors(1, 'pastels')
};
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
public seriesRowTotal: number[] = [];
public seriesRowAvg: number[] = [];
public seriesRowAvgLastYears: number[] = [];
public seriesColTotal: number[] = [];
public seriesColTotalClosed: number[] = [];
public seriesColTotalLastYears: number[] = [];
constructor(
private route: ActivatedRoute,
private _utilitiesService: UtilitiesService,
public menuItems: MenuItems
) { }
ngOnInit() {
const data$: Observable<{ pageData: JumpsPageData }> = this.route.data as Observable<{ pageData: JumpsPageData }>;
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { pageData: JumpsPageData }) => {
const pageData: JumpsPageData = data.pageData;
this.lastJump = pageData.lastjump;
const currentYear: number = new Date().getFullYear();
const types: Array<string> = ['FF', 'Solo', 'VR', 'Wingsuit'];
const verticals: Array<string> = ['Chutas', 'Head Up', 'Head Down'];
const others: Array<string> = ['Track', 'Trace'];
const modules: Array<string> = [...verticals, ...others];
const minoration = 2; // -2 pour ne pas compter la première année
this.min = pageData.jumpsByYears[0].year;
this.max = pageData.jumpsByYears[(pageData.jumpsByYears.length-1)].year;
this.seriesHeader = this._utilitiesService.getMonthsList();
const values: Array<number> = this.seriesHeader.map(() => 0);
this.seriesColTotal = [...values];
this.seriesColTotalClosed = [...values];
this.seriesColTotalLastYears = [...values];
this.seriesName = pageData.jumpsByYears.map((row: JumpYears) => {
this.seriesRow.push([...values]);
return row.year.toString();
});
this._jumpsByDate = pageData.jumpsByDate.map((row: JumpByDate) => {
this.seriesRow[(row.year-this.min)][(row.month-1)] = row.count;
this.seriesColTotal[(row.month-1)] += row.count;
if (row.year < currentYear) {
this.seriesColTotalClosed[(row.month-1)] += row.count;
}
if (row.year >= (currentYear-3) && row.year < currentYear) {
this.seriesColTotalLastYears[(row.month-1)] += row.count;
}
return row;
});
this.seriesRow.forEach((row: number[]) => {
const total = row.reduce((partialSum, a) => partialSum + a, 0);
this.seriesRowTotal.push(total);
});
this.seriesColTotalClosed.forEach((row: number) => {
const value: number = (row/(this.seriesName.length-minoration));
this.seriesRowAvg.push(parseInt(value.toFixed(0)));
});
this.seriesColTotalLastYears.forEach((row: number) => {
const value: number = (row/3); // Les 3 dernières années
this.seriesRowAvgLastYears.push(parseInt(value.toFixed(0)));
});
this.grandAvg = this.seriesRowAvg.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
this.grandAvgLastYears = this.seriesRowAvgLastYears.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
this.grandTotal = this.seriesColTotal.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
pageData.jumpsByCategory.forEach((row: JumpByCategorie) => {
if (types.indexOf(row.categorie) !== -1) {
this._jumpsByCategorie.push(row);
}
});
//const focus: Array<string> = ['FF', 'Solo']; // ['FF']
let verticalCount:number = 0;
let otherCount:number = 0;
pageData.jumpsByModule.forEach((row: JumpByModule) => {
//if (focus.indexOf(row.categorie) !== -1 && modules.indexOf(row.module) !== -1) {
if (modules.indexOf(row.module) !== -1) {
if (verticals.indexOf(row.module) !== -1) {
verticalCount += row.count;
} else {
otherCount += row.count;
}
}
});
if (verticalCount > 0) {
const row:JumpByModule = {
categorie: 'FF',
module: 'Vertical',
count: verticalCount
}
this._jumpsByModule.push(row);
}
if (otherCount > 0) {
const row:JumpByModule = {
categorie: 'FF',
module: 'Track/Trace',
count: otherCount
}
this._jumpsByModule.push(row);
}
for (let year = 0; year < pageData.jumpsByYears.length; year++) {
for (let index = 0; index < 12; index++) {
const month: string = ((index+1) < 10) ? `0${(index+1)}` : `${(index+1)}`;
this.seriesTypesHeader.push(`${month}-${pageData.jumpsByYears[year].year}`);
}
}
const emptyValues: Array<number> = this.seriesTypesHeader.map(() => 0);
this.seriesTypesName = types;
this.seriesTypesName.forEach(() => {
this.seriesTypesRow.push([...emptyValues]);
});
pageData.jumpsByDateByModule.forEach((row: JumpByDateByModule) => {
const indexOf: number = this.seriesTypesName.indexOf(row.categorie);
if (indexOf !== -1) {
this.seriesTypesRow[indexOf][(((row.year-this.min)*12)+row.month-1)] += row.count;
}
});
this.seriesTypesRowCumulated = this.seriesTypesRow.map((row: Array<number>) => {
let accumulated: number = 0;
return row.map((value: number) => {
accumulated += value;
return accumulated;
});
});
this.seriesTypesHeader = this.seriesTypesHeader.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
this.seriesTypesRow = this.seriesTypesRow.map((row: Array<number>) => {
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
});
this.seriesTypesRowCumulated = this.seriesTypesRowCumulated.map((row: Array<number>) => {
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
});
/*
console.log('this.min', this.min);
console.log('this.max', this.max);
console.log('this.lastJump', this.lastJump);
console.log('this.seriesHeader', this.seriesHeader);
console.log('this.seriesName', this.seriesName);
console.log('this.seriesRow', this.seriesRow);
console.log('this.seriesRowTotal', this.seriesRowTotal);
console.log('this.seriesRowAvg', this.seriesRowAvg);
console.log('this.seriesRowAvgLastYears', this.seriesRowAvgLastYears);
console.log('this._jumpsByCategorie', this._jumpsByCategorie);
console.log('this._jumpsByModule', this._jumpsByModule);
console.log('this.seriesTypesHeader', this.seriesTypesHeader);
console.log('this.seriesTypesName', this.seriesTypesName);
console.log('this.seriesTypesRow', this.seriesTypesRow);
console.log('this.seriesTypesRowCumulated', this.seriesTypesRowCumulated);
*/
this.displayCharts = true;
});
}
ngOnDestroy() {
this._data.unsubscribe();
}
public getCategories(): Array<JumpByCategorie> {
return this._jumpsByCategorie;
}
public getModules(): Array<JumpByModule> {
return this._jumpsByModule;
}
public getModuleColor(index: number): number {
return (this._jumpsByCategorie.length + index);
}
}
@@ -245,7 +245,7 @@
} }
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark"> <span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon> <mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon>
ju_solide {{ lastJump.author.username }}
</span> </span>
</div> </div>
</div> </div>
@@ -180,7 +180,7 @@
} }
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark"> <span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon> <mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon>
ju_solide {{ jump.author.username }}
</span> </span>
</div> </div>
</div> </div>
@@ -1,29 +1,69 @@
<h2 mat-dialog-title class="bg-cyan border-bottom border-light-subtle d-flex mb-3 pt-3 pb-2"> <h2 mat-dialog-title class="bg-navy-light border-bottom border-light-subtle d-flex mb-3 pt-3 pb-2">
Saut n°{{jump.numero}}<span class="flex-spacer"></span> Saut n° {{jump.numero}}<span class="flex-spacer"></span>
<mat-icon fontIcon="close" class="pointer" (click)="closeDialog()"></mat-icon> <mat-icon fontIcon="close" class="pointer" (click)="closeDialog()"></mat-icon>
</h2> </h2>
<mat-dialog-content> <mat-dialog-content>
<div class="container"> <div class="container">
<h5 class="border-bottom text-light-info border-light-subtle pb-2 mb-3">Le {{ jump.date | date: 'dd/MM/yyyy' }} à {{ jump.lieu }} :</h5> <div class="d-flex border-bottom border-light-subtle text-light-info pb-2 mb-3">
<div class="row mb-3"> <div class="flex-fill">
<h5>Le {{ jump.date | date: 'dd/MM/yyyy' }} à {{ jump.lieu }} {{ jump.zone ? jump.zone : jump.oaci }} :</h5>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2">
<span class="text-accent fs-5">{{ jump.categorie }}</span>
@if (jump.module) {
<span class="fs-5 mx-2">/</span>
<span class="text-muted fs-5">{{ jump.module }}</span>
}
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-12"> <div class="col-lg-6 col-md-12">
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt class="text-cyan fw-normal">Lieu:</dt> <dt class="text-light-info fw-normal">Aeronef:</dt>
<dd>{{jump.lieu}}</dd> <dd>{{ jump.aeronef }} {{ jump.imat }}</dd>
<dt class="text-cyan fw-normal">Oaci:</dt> <dt class="text-light-info fw-normal">Voile:</dt>
<dd>{{jump.oaci}}</dd> <dd>{{ jump.voile }} {{ jump.taille }} ft<sup>2</sup></dd>
<dt class="text-cyan fw-normal">Aeronef:</dt> @if (jump.programme) {
<dd>{{jump.aeronef}}</dd> <dt class="text-light-info fw-normal">Programme:</dt>
<dt class="text-cyan fw-normal">Imat:</dt> <dd>{{ jump.programme }}</dd>
<dd>{{jump.imat}}</dd> }
<dt class="text-cyan fw-normal">Voile:</dt> </dl>
<dd>{{jump.voile}}</dd> </div>
<dt class="text-cyan fw-normal">Taille:</dt> <div class="col-lg-6 col-md-12">
<dd>{{jump.taille}} ft<sup>2</sup></dd> <dl class="dl-horizontal">
<dt class="text-cyan fw-normal">Participants:</dt> @if (!jump.x2data) {
<dd>{{jump.participants}}</dd> <dt class="text-light-info fw-normal">Hauteur:</dt>
<dt class="text-cyan fw-normal">Sautants:</dt> <dd>{{ jump.hauteur }} m</dd>
<dd> <dt class="text-light-info fw-normal">Déploiement:</dt>
<dd>{{ jump.deploiement }} m</dd>
}
@for (file of jump.files; track file) {
<dt class="text-light-info fw-normal">Fichier {{ file.type }}</dt>
<dd>{{ file.name }}</dd>
}
</dl>
</div>
</div>
<mat-divider></mat-divider>
@if (jump.x2data) {
<div class="row my-3">
<div class="col-sm-4 col-xs-12 text-center">
<h6>Hauteur</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.altitude.exit }} m</span>
</div>
<div class="col-sm-4 col-xs-12 text-center">
<h6 class="d-flex justify-content-center"><mat-icon fontIcon="timer" class="me-1 fs-5"></mat-icon> Durée de la chute</h6>
<span class="text-primary fw-medium fs-5">{{formatDuration(jump.x2data.duration.freeFall)}}</span>
</div>
<div class="col-sm-4 col-xs-12 text-center">
<h6>Déploiement</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.altitude.deployment }} m</span>
</div>
</div>
<div class="row my-4">
<div class="{{ jump.accessoires ? 'col-sm-6 ' : '' }}col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-4"><mat-icon fontIcon="people" class="me-1"></mat-icon> Participants ({{ jump.sautants.length + 1 }})</h5>
@for (sautant of jump.sautants; track sautant) { @for (sautant of jump.sautants; track sautant) {
<span class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark"> <span class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon> <mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
@@ -32,35 +72,140 @@
} }
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark"> <span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon> <mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
ju_solide {{ jump.author.username }}
</span> </span>
</dd>
</dl>
</div> </div>
<div class="col-lg-6 col-md-12"> @if (jump.accessoires) {
<dl class="dl-horizontal"> <div class="col-sm-6 col-xs-12 text-center">
<dt class="text-cyan fw-normal">Hauteur:</dt> <h5 class="d-flex justify-content-center mb-4"><mat-icon fontIcon="auto_awesome" class="me-1"></mat-icon> Accessoires</h5>
<dd>{{jump.hauteur}} m</dd> <span class="badge rounded-pill bg-navy-light mb-2 me-1 pe-3 py-1 fw-normal">
<dt class="text-cyan fw-normal">Deploiement:</dt> <mat-icon class="align-middle me-1" fontIcon="auto_awesome"></mat-icon>
<dd>{{jump.deploiement}} m</dd> {{ jump.accessoires }}
<dt class="text-cyan fw-normal">Categorie:</dt> </span>
<dd>{{jump.categorie}}</dd> </div>
<dt class="text-cyan fw-normal">Module:</dt> }
<dd>{{jump.module}}</dd> </div>
<dt class="text-cyan fw-normal">Accessoires:</dt>
<dd>{{jump.accessoires}}</dd> <div class="row my-3">
<dt class="text-cyan fw-normal">Zone:</dt> <div class="col-xs-12 text-center">
<dd>{{jump.zone}}</dd> <h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="speed" class="me-1"></mat-icon> Vitesse en chute</h5>
<dt class="text-cyan fw-normal">Dossier media:</dt> </div>
<dd>{{jump.dossier}}</dd> <div class="col-sm-3 col-xs-12 text-center">
<dt class="text-cyan fw-normal">Video:</dt> <h6>Max Verticale</h6>
<dd>{{jump.video}}</dd> <span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.vertical.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
<dt class="text-cyan fw-normal">Programme:</dt> </div>
<dd>{{jump.programme}}</dd> <div class="col-sm-3 col-xs-12 text-center">
</dl> <h6>Max Horizontale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.horizontal.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Moyenne Verticale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.vertical.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Moyenne Horizontale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.horizontal.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div> </div>
</div> </div>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<div class="row mt-4 mb-3">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="speed" class="me-1"></mat-icon> Vitesse sous voile</h5>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Max Verticale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.vertical.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Max Horizontale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.horizontal.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Moyenne Verticale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.vertical.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Moyenne Horizontale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.horizontal.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
</div>
<mat-divider></mat-divider>
<div class="row mt-3">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="square_foot" class="me-1"></mat-icon> Finesse</h5>
</div>
<div class="col-sm-6 col-xs-12 text-center">
<h6>Finesse en chute</h6>
</div>
<div class="col-sm-6 col-xs-12 text-center">
<h6>Finesse sous voile</h6>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-3 col-xs-12 text-center">
<h6>Finesse Max</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.glideRatio.freeFall.max | number : '1.2-2' }}</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Finesse Moyenne</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.glideRatio.freeFall.avg | number : '1.2-2' }}</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Finesse Max</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.glideRatio.underCanopy.max | number : '1.2-2' }}</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Finesse Moyenne</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.glideRatio.underCanopy.avg | number : '1.2-2' }}</span>
</div>
</div>
<mat-divider></mat-divider>
<div class="row my-3">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="straighten" class="me-1"></mat-icon> Distance</h5>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Distance au largage</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.distance.exitFromLandingZone| number : '1.0-0' }} m</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6 class="d-flex justify-content-center"><mat-icon fontIcon="timer" class="me-1 fs-5"></mat-icon> Durée sous voile</h6>
<span class="text-primary fw-medium fs-5">{{formatDuration(jump.x2data.duration.underCanopy)}}</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Distance totale</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.distance.totalFlying | number : '1.0-0' }} m</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Distance horizontale</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.distance.totalHorizontal | number : '1.0-0' }} m</span>
</div>
</div>
<mat-divider></mat-divider>
}
@else {
<div class="row my-4">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-4"><mat-icon fontIcon="people" class="me-1"></mat-icon> Participants ({{ jump.sautants.length + 1 }})</h5>
@for (sautant of jump.sautants; track sautant) {
<span class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
{{ sautant }}
</span>
}
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
{{ jump.author.username }}
</span>
</div>
</div>
<mat-divider></mat-divider>
<div class="d-flex justify-content-center my-3 fs-5"><mat-icon fontIcon="satellite_alt" class="me-2"></mat-icon> Aucune donnée GPS</div>
<mat-divider></mat-divider>
}
</div> </div>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions> <mat-dialog-actions>
@@ -1,5 +1,5 @@
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { DatePipe } from '@angular/common'; import { DatePipe, DecimalPipe } from '@angular/common';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider'; import { MatDividerModule } from '@angular/material/divider';
@@ -12,11 +12,9 @@ import { Jump } from 'src/app/core/models';
templateUrl: 'jump-view.dialog.html', templateUrl: 'jump-view.dialog.html',
standalone: true, standalone: true,
imports: [ imports: [
DatePipe, DatePipe, DecimalPipe,
MatButtonModule, MatButtonModule, MatDialogModule,
MatDialogModule, MatDividerModule, MatIconModule
MatDividerModule,
MatIconModule
] ]
}) })
export class JumpViewDialogComponent { export class JumpViewDialogComponent {
@@ -30,4 +28,10 @@ export class JumpViewDialogComponent {
// close the dialog without result // close the dialog without result
this.dialogRef.close(); this.dialogRef.close();
} }
formatDuration(duration: number): string {
const start = new Date(0);
start.setSeconds(duration);
return start.toISOString().substring(14, 19);
}
} }
@@ -1,5 +1,4 @@
<div @flyInOut> <div @flyInOut>
<div class="content">
<div class="d-flex"> <div class="d-flex">
<div class="flex-fill"> <div class="flex-fill">
<h1 class="mb-1">{{title}}</h1> <h1 class="mb-1">{{title}}</h1>
@@ -103,5 +102,4 @@
</app-jump-table> </app-jump-table>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</div>
</div> </div>
@@ -12,14 +12,20 @@
</div> </div>
</div> </div>
</div> </div>
<div class="profile-page content"> <div class="profile-page">
<h1>{{title}}</h1> <div class="d-flex">
<mat-divider></mat-divider> <div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end"></div>
</div>
<mat-divider class="my-3"></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors> <app-list-errors [errors]="errors"></app-list-errors>
<mat-card class="mt-4 bg-light text-black scaled"> <mat-card class="mt-4 bg-light text-black scaled">
<mat-card-content> <mat-card-content>
<div class="text-center"> <div class="text-center">
<a href="index.html" class="d-block p-4"><img height="72" width="90" src="assets/images/vector/upt_logo.png" alt="Logo UPT"></a> <a href="/" class="d-block p-4"><img height="72" width="90" src="assets/images/vector/upt_logo.png" alt="Logo UPT"></a>
<mat-divider class="border-primary w-50 mx-auto"></mat-divider> <mat-divider class="border-primary w-50 mx-auto"></mat-divider>
<h1 class="mt-3 mb-4"><span class="pt-1 pb-2 px-2">Vector 3 - <small class="text-muted">V306 Micron</small></span></h1> <h1 class="mt-3 mb-4"><span class="pt-1 pb-2 px-2">Vector 3 - <small class="text-muted">V306 Micron</small></span></h1>
<mat-divider class="border-primary w-50 mx-auto"></mat-divider> <mat-divider class="border-primary w-50 mx-auto"></mat-divider>
@@ -4,6 +4,7 @@
.banner { .banner {
background-color: var.$sidebar-footer; background-color: var.$sidebar-footer;
padding: 1.6rem 0 1.3rem 0; padding: 1.6rem 0 1.3rem 0;
margin: -1.5rem -1.5rem 1.5rem -1.5rem;
.user-img { .user-img {
width: var.$user-img-size; width: var.$user-img-size;
height: var.$user-img-size; height: var.$user-img-size;
+31 -10
View File
@@ -1,5 +1,4 @@
<div class="content"> <div class="d-flex">
<div class="d-flex">
<div class="flex-fill"> <div class="flex-fill">
<h1 class="mb-1"> <h1 class="mb-1">
{{ title }} {{ title }}
@@ -21,12 +20,22 @@
} }
</mat-menu> </mat-menu>
</div> </div>
</div>
<mat-divider class="my-3"></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors>
@if (questions.controls.length) {
<mat-card [hidden]="!questions.controls.length">
<div class="mx-3 my-5">
<mat-progress-bar [mode]="loading ? 'query' : 'determinate'" [value]="loading ? 0 : (totalAnswer/questionsCount*100)" color="accent"></mat-progress-bar>
<mat-progress-bar class="mt-0" [mode]="loading ? 'query' : 'determinate'" [value]="loading ? 0 : ((timeProgress/timeMax)*100)" [color]="timeProgressColor"></mat-progress-bar>
<ol class="squareItems clearfix">
@for (answer of answers; track $index) {
<li [class]="answer">{{ ($index + 1) }}</li>
}
</ol>
</div> </div>
<mat-divider class="my-3"></mat-divider> <mat-card-content>
<app-list-errors [errors]="errors"></app-list-errors> <form [formGroup]="qcmForm" (ngSubmit)="onSubmit()">
<mat-card [hidden]="!questions.length">
<mat-card-content class="mt-4">
<form [formGroup]="qcmForm">
@for (category of qcm.categories; track category; let index = $index) { @for (category of qcm.categories; track category; let index = $index) {
<mat-accordion> <mat-accordion>
<mat-expansion-panel class="mb-3" [expanded]="expandedIndex === index" (opened)="setExpandedIndex(index)"> <mat-expansion-panel class="mb-3" [expanded]="expandedIndex === index" (opened)="setExpandedIndex(index)">
@@ -43,9 +52,9 @@
@for (question of category.questions; track question; let index = $index) { @for (question of category.questions; track question; let index = $index) {
<div class="mb-3" [formGroupName]="index"> <div class="mb-3" [formGroupName]="index">
<label class="fs-5 fw-medium mb-2" for="radio-group-{{ category.num }}-{{ question.num }}">{{ question.num }} - {{ question.libelle }}</label> <label class="fs-5 fw-medium mb-2" for="radio-group-{{ category.num }}-{{ question.num }}">{{ question.num }} - {{ question.libelle }}</label>
<mat-radio-group id="radio-group-{{ category.num }}-{{ question.num }}" class="d-flex flex-column" (change)="onChange()"> <mat-radio-group formControlName="choice" id="radio-group-{{ category.num }}-{{ question.num }}" class="d-flex flex-column" (change)="onChange(category, question, $event.value)">
@for (choice of question.choices; track choice) { @for (choice of question.choices; track choice) {
<mat-radio-button class="example-radio-button" [value]="choice.index" [color]="choice.correct ? 'primary' : 'warn'">{{ choice.libelle }}</mat-radio-button> <mat-radio-button [value]="choice.index" [color]="choice.correct ? 'primary' : 'warn'">{{ choice.libelle }}</mat-radio-button>
} }
</mat-radio-group> </mat-radio-group>
</div> </div>
@@ -56,5 +65,17 @@
} }
</form> </form>
</mat-card-content> </mat-card-content>
<!--
<form [formGroup]="qcmForm">
<div class="mb-3 ms-4" formArrayName="questions">
@for (question of questions.controls; track question; let index = $index) {
<div class="mb-3" [formGroupName]="index">
<label class="fs-5 fw-medium mb-2" for="radio-group-{{ index }}">{{ index }} - Libellé</label>
<input formControlName="choice" type="text"/>
</div>
}
</div>
</form>
-->
</mat-card> </mat-card>
</div> }
+37
View File
@@ -0,0 +1,37 @@
/* QCM */
@use 'variable' as var;
ol.squareItems {
padding: 0;
margin: 1rem 0;
li {
color: var.$primary;
background-color: var.$sidenav-content; //var.$dark;
background-image: none;
border: 1px solid var.$dark; //var.$extra-muted;
cursor: pointer;
float: left;
list-style-type: none;
margin: 0.17em;
padding: 0.34em 0;
text-align: center;
width: 33px;
height: 33px;
&.active {
border-color: var.$info;
}
&.valid {
color: var.$sidenav-content;
border-color: var.$success;
background-color: var.$primary;
}
&.defer {
border-color: var.$info-dark;
background-color: var.$navy-light;
}
&.error {
border-color: var.$purple-light;
background-color: var.$secondary;
}
}
}
+80 -19
View File
@@ -1,6 +1,6 @@
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core'; import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormControl, FormGroup, FormBuilder, FormArray, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { /*FormControl,*/ FormGroup, FormBuilder, FormArray, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
@@ -8,12 +8,15 @@ import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion'; import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatRadioModule } from '@angular/material/radio'; import { MatRadioModule } from '@angular/material/radio';
import { Observable, Subscription } from 'rxjs'; import { MatSelectModule } from '@angular/material/select';
import { interval, Observable, Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { MenuItems } from 'src/app/components/shared'; import { MenuItems } from 'src/app/components/shared';
import { ListErrorsComponent, } from 'src/app/components/shared'; import { ListErrorsComponent } from 'src/app/components/shared';
import { Errors, Qcm, User } from 'src/app/core/models'; import { Errors, Qcm, QcmCategory, QcmQuestion, QcmQuestionState, User } from 'src/app/core/models';
import { QcmService, UserService } from 'src/app/core/services'; import { QcmService, UserService } from 'src/app/core/services';
import data from 'src/qcm-bpa.json'; import data from 'src/qcm-bpa.json';
@@ -23,7 +26,8 @@ import data from 'src/qcm-bpa.json';
imports: [ imports: [
FormsModule, ReactiveFormsModule, FormsModule, ReactiveFormsModule,
MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule, MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule,
MatIconModule, MatMenuModule, MatRadioModule, MatIconModule, MatMenuModule, MatProgressBarModule, MatRadioModule,
MatSelectModule,
ListErrorsComponent ListErrorsComponent
], ],
templateUrl: './qcm.component.html', templateUrl: './qcm.component.html',
@@ -32,6 +36,8 @@ import data from 'src/qcm-bpa.json';
export class QcmComponent implements OnInit, OnDestroy { export class QcmComponent implements OnInit, OnDestroy {
private _currentUser: Subscription = new Subscription(); private _currentUser: Subscription = new Subscription();
private _data: Subscription = new Subscription(); private _data: Subscription = new Subscription();
private _timer: Subscription = new Subscription();
private _refresh = 1000;
public qcm: Qcm = {} as Qcm; public qcm: Qcm = {} as Qcm;
public currentUser: User = {} as User; public currentUser: User = {} as User;
public canModify = false; public canModify = false;
@@ -41,7 +47,13 @@ export class QcmComponent implements OnInit, OnDestroy {
public destroyRef = inject(DestroyRef); public destroyRef = inject(DestroyRef);
public expandedIndex = 0; public expandedIndex = 0;
public questionsCount = 0; public questionsCount = 0;
public answers: Array<QcmQuestionState> = [];
public qcmForm!: FormGroup; public qcmForm!: FormGroup;
public loading = true;
public totalAnswer = 0;
public timeProgress = 0;
public timeProgressColor = 'primary';
public timeMax = (60*45);
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@@ -56,15 +68,16 @@ export class QcmComponent implements OnInit, OnDestroy {
} }
get questions(): FormArray { get questions(): FormArray {
return <FormArray>this.qcmForm.controls['questions']; return this.qcmForm.controls['questions'] as FormArray;
} }
ngOnInit() { ngOnInit() {
//this.importQuestions(); //this._importQuestions();
//this.importChoices(); //this._importChoices();
// Retreive the prefetched QCM // Retreive the prefetched QCM
const data$: Observable<{ qcm: Qcm }> = this.route.data as Observable<{ qcm: Qcm }>; const data$: Observable<{ qcm: Qcm }> = this.route.data as Observable<{ qcm: Qcm }>;
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { qcm: Qcm }) => { this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { qcm: Qcm }) => {
this.questionsCount = 0;
this.qcm = data.qcm; this.qcm = data.qcm;
if (this.qcm.name === 'bpa') { if (this.qcm.name === 'bpa') {
this.title = 'QCM du BPA'; this.title = 'QCM du BPA';
@@ -77,13 +90,7 @@ export class QcmComponent implements OnInit, OnDestroy {
this.qcm.categories.forEach(category => { this.qcm.categories.forEach(category => {
this.questionsCount += category.questions.length; this.questionsCount += category.questions.length;
category.questions.forEach(question => { category.questions.forEach(question => {
this.questions.push( this.addQuestion(question);
this.fb.group({
id: new FormControl({ value: question.num, disabled: false }),
choice: ['']
//choice: new FormControl({ value: '', disabled: false })
})
);
}); });
}); });
}); });
@@ -93,6 +100,7 @@ export class QcmComponent implements OnInit, OnDestroy {
this.currentUser = userData; this.currentUser = userData;
this.canModify = userData.role === 'Admin'; this.canModify = userData.role === 'Admin';
}); });
this.loading = false;
} }
ngOnDestroy() { ngOnDestroy() {
@@ -100,15 +108,64 @@ export class QcmComponent implements OnInit, OnDestroy {
this._currentUser.unsubscribe(); this._currentUser.unsubscribe();
} }
private _starTimer(): void {
this._timer = interval(1000)
.pipe(take(this.timeMax))
.subscribe(() => {
this.timeProgress++;
if (this.timeProgress > (this.timeMax/100*70) && this.timeProgress <= (this.timeMax/100*90)) {
this.timeProgressColor = 'warning';
} else if (this.timeProgress > (this.timeMax/100*90) && this.timeProgress <= (this.timeMax/100*98)) {
this.timeProgressColor = 'danger';
} else if (this.timeProgress > (this.timeMax/100*98)) {
this.timeProgressColor = 'purple';
}
if (this.timeProgress >= this.timeMax) {
this.timeProgressColor = 'accent';
this._timer.unsubscribe();
}
});
}
addQuestion(question: QcmQuestion) {
const item = this.fb.group({
id: [question.num],
choice: ['']
});
this.answers.push(QcmQuestionState.UNANSWERED);
this.questions.push(item);
}
setExpandedIndex(index: number) { setExpandedIndex(index: number) {
this.expandedIndex = index; this.expandedIndex = index;
} }
onChange() { onChange(category: QcmCategory, question: QcmQuestion, value: unknown) {
//this.isDisabled = true; //console.log('onChange', category.num, question.num, value);
if (this.timeProgress === 0) {
this._starTimer();
}
this.qcm.categories[(category.num-1)].questions[(question.num-1)].choices.forEach(choice => {
if (choice.index === value) {
if (choice.correct) {
this.answers[(question.num-1)] = QcmQuestionState.VALID;
console.log(`Réponse à la question ${question.num} de la catégorie ${category.num} : correcte`);
} else {
this.answers[(question.num-1)] = QcmQuestionState.ERROR;
console.log(`Réponse à la question ${question.num} de la catégorie ${category.num} : incorrecte`);
}
}
});
this.totalAnswer++;
this.questions.controls[(question.num-1)].disable();
//console.log(this.questions.controls[(question.num-1)].disabled);
} }
importChoices() { onSubmit() {
console.log('onSubmit');
}
private _importChoices() {
try { try {
data.categories.forEach((category) => { data.categories.forEach((category) => {
category.questions.forEach(question => { category.questions.forEach(question => {
@@ -132,7 +189,7 @@ export class QcmComponent implements OnInit, OnDestroy {
} }
} }
importQuestions() { private _importQuestions() {
try { try {
data.categories.forEach((category) => { data.categories.forEach((category) => {
//Object.assign(this.jump, category); //Object.assign(this.jump, category);
@@ -154,4 +211,8 @@ export class QcmComponent implements OnInit, OnDestroy {
console.error(error); console.error(error);
} }
} }
private _resetErrors(): void {
this.errors = { errors: {} };
}
} }
@@ -12,7 +12,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="credentials-page content"> <div class="credentials-page">
<h1>{{title}}</h1> <h1>{{title}}</h1>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors> <app-list-errors [errors]="errors"></app-list-errors>
@@ -4,6 +4,7 @@
.banner { .banner {
background-color: var.$sidebar-footer; background-color: var.$sidebar-footer;
padding: 1.6rem 0 1.3rem 0; padding: 1.6rem 0 1.3rem 0;
margin: -1.5rem -1.5rem 1.5rem -1.5rem;
.user-img { .user-img {
width: var.$user-img-size; width: var.$user-img-size;
height: var.$user-img-size; height: var.$user-img-size;
@@ -12,7 +12,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="settings-page content"> <div class="settings-page">
<h1>{{title}}</h1> <h1>{{title}}</h1>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors> <app-list-errors [errors]="errors"></app-list-errors>
@@ -4,6 +4,7 @@
.banner { .banner {
background-color: var.$sidebar-footer; background-color: var.$sidebar-footer;
padding: 1.6rem 0 1.3rem 0; padding: 1.6rem 0 1.3rem 0;
margin: -1.5rem -1.5rem 1.5rem -1.5rem;
/* /*
background-color: mat.get-color-from-palette(var.$md-custom-warn, 700); background-color: mat.get-color-from-palette(var.$md-custom-warn, 700);
margin-bottom: 1rem; margin-bottom: 1rem;
@@ -1,9 +1,4 @@
import { import { Directive, AfterContentChecked } from '@angular/core';
Directive,
OnInit,
AfterViewInit,
AfterContentChecked
} from '@angular/core';
import { Router, NavigationEnd } from '@angular/router'; import { Router, NavigationEnd } from '@angular/router';
import { AccordionLinkDirective } from './accordionlink.directive'; import { AccordionLinkDirective } from './accordionlink.directive';
@@ -51,7 +46,7 @@ export class AccordionDirective implements AfterContentChecked {
ngAfterContentChecked(): void { ngAfterContentChecked(): void {
this.router.events this.router.events
.pipe(filter(event => event instanceof NavigationEnd)) .pipe(filter(event => event instanceof NavigationEnd))
.subscribe(e => this.checkOpenLinks()); .subscribe(() => this.checkOpenLinks());
} }
constructor(private router: Router) { constructor(private router: Router) {
@@ -14,7 +14,7 @@ export class AccordionAnchorDirective {
} }
@HostListener('click', ['$event']) @HostListener('click', ['$event'])
onClick(e: any) { onClick() {
this.navlink.toggle(); this.navlink.toggle();
} }
} }
@@ -15,7 +15,7 @@ import { AccordionDirective } from './accordion.directive';
}) })
export class AccordionLinkDirective implements OnInit, OnDestroy { export class AccordionLinkDirective implements OnInit, OnDestroy {
@Input() @Input()
public group: any; public group!: string;
@HostBinding('class.selected') @HostBinding('class.selected')
@Input() @Input()
@@ -37,15 +37,15 @@ export class AccordionLinkDirective implements OnInit, OnDestroy {
this.nav = nav; this.nav = nav;
} }
ngOnInit(): any { ngOnInit() {
this.nav.addLink(this); this.nav.addLink(this);
} }
ngOnDestroy(): any { ngOnDestroy(){
this.nav.removeGroup(this); this.nav.removeGroup(this);
} }
toggle(): any { toggle() {
this.selected = !this.selected; this.selected = !this.selected;
} }
} }
@@ -0,0 +1,25 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { AeronefsPieComponent } from './aeronefs-pie.component';
describe('AeronefsPieComponent', () => {
let component: AeronefsPieComponent;
let fixture: ComponentFixture<AeronefsPieComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [AeronefsPieComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AeronefsPieComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -16,16 +16,16 @@ import { UtilitiesService, AeronefsService } from 'src/app/core/services';
import { AeronefByImat, AeronefByYear } from 'src/app/core/models'; import { AeronefByImat, AeronefByYear } from 'src/app/core/models';
@Component({ @Component({
selector: 'app-aeronefs', selector: 'app-aeronefs-pie',
standalone: true, standalone: true,
imports: [ imports: [
MatBadgeModule, MatButtonModule, MatCardModule, MatDividerModule, MatBadgeModule, MatButtonModule, MatCardModule, MatDividerModule,
MatExpansionModule, MatIconModule, MatMenuModule, MatExpansionModule, MatIconModule, MatMenuModule,
HistoryTableComponent, PieChartComponent HistoryTableComponent, PieChartComponent
], ],
templateUrl: './aeronefs.component.html' templateUrl: './aeronefs-pie.component.html'
}) })
export class AeronefsComponent implements OnInit, OnDestroy { export class AeronefsPieComponent implements OnInit, OnDestroy {
private _aeronefByImat: Subscription = new Subscription(); private _aeronefByImat: Subscription = new Subscription();
private _aeronefByYear: Subscription = new Subscription(); private _aeronefByYear: Subscription = new Subscription();
private _aeronefsByImat!: Array<AeronefByImat>; private _aeronefsByImat!: Array<AeronefByImat>;
@@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DropZonesComponent } from './drop-zones.component'; import { CanopiesModelsComponent } from './canopies-models.component';
describe('DropZonesComponent', () => { describe('CanopiesModelsComponent', () => {
let component: DropZonesComponent; let component: CanopiesModelsComponent;
let fixture: ComponentFixture<DropZonesComponent>; let fixture: ComponentFixture<CanopiesModelsComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [DropZonesComponent] imports: [CanopiesModelsComponent]
}) })
.compileComponents(); .compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DropZonesComponent); fixture = TestBed.createComponent(CanopiesModelsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -15,16 +15,16 @@ import { UtilitiesService, CanopiesService } from 'src/app/core/services';
import { CanopyModelBySize, CanopyModelByYear } from 'src/app/core/models'; import { CanopyModelBySize, CanopyModelByYear } from 'src/app/core/models';
@Component({ @Component({
selector: 'app-canopy-models', selector: 'app-canopies-models',
standalone: true, standalone: true,
imports: [ imports: [
MatButtonModule, MatCardModule, MatDividerModule, MatButtonModule, MatCardModule, MatDividerModule,
MatExpansionModule, MatIconModule, MatMenuModule, MatExpansionModule, MatIconModule, MatMenuModule,
PieChartComponent, HistoryTableComponent PieChartComponent, HistoryTableComponent
], ],
templateUrl: './canopy-models.component.html' templateUrl: './canopies-models.component.html'
}) })
export class CanopyModelsComponent implements OnInit, OnDestroy { export class CanopiesModelsComponent implements OnInit, OnDestroy {
private _canopyBySize: Subscription = new Subscription(); private _canopyBySize: Subscription = new Subscription();
private _canopyByYear: Subscription = new Subscription(); private _canopyByYear: Subscription = new Subscription();
private _canopiesBySize!: Array<CanopyModelBySize>; private _canopiesBySize!: Array<CanopyModelBySize>;
@@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DropZonesBarComponent } from './drop-zones-bar.component'; import { CanopiesSizesComponent } from './canopies-sizes.component';
describe('DropZonesBarComponent', () => { describe('CanopiesSizesComponent', () => {
let component: DropZonesBarComponent; let component: CanopiesSizesComponent;
let fixture: ComponentFixture<DropZonesBarComponent>; let fixture: ComponentFixture<CanopiesSizesComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [DropZonesBarComponent] imports: [CanopiesSizesComponent]
}) })
.compileComponents(); .compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DropZonesBarComponent); fixture = TestBed.createComponent(CanopiesSizesComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -16,16 +16,16 @@ import { UtilitiesService, CanopiesService } from 'src/app/core/services';
import { CanopyBySize, CanopyByYear } from 'src/app/core/models'; import { CanopyBySize, CanopyByYear } from 'src/app/core/models';
@Component({ @Component({
selector: 'app-canopy-sizes', selector: 'app-canopies-sizes',
standalone: true, standalone: true,
imports: [ imports: [
MatButtonModule, MatCardModule, MatDividerModule, MatButtonModule, MatCardModule, MatDividerModule,
MatExpansionModule, MatIconModule, MatMenuModule, MatExpansionModule, MatIconModule, MatMenuModule,
PieChartComponent, HistoryTableComponent PieChartComponent, HistoryTableComponent
], ],
templateUrl: './canopy-sizes.component.html' templateUrl: './canopies-sizes.component.html'
}) })
export class CanopySizesComponent implements OnInit, OnDestroy { export class CanopiesSizesComponent implements OnInit, OnDestroy {
private _canopyBySize: Subscription = new Subscription(); private _canopyBySize: Subscription = new Subscription();
private _canopyByYear: Subscription = new Subscription(); private _canopyByYear: Subscription = new Subscription();
private _canopiesBySize!: Array<CanopyBySize>; private _canopiesBySize!: Array<CanopyBySize>;
@@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CanopyModelsComponent } from './canopy-models.component'; import { DropzonesBarComponent } from './dropzones-bar.component';
describe('CanopyModelsComponent', () => { describe('DropzonesBarComponent', () => {
let component: CanopyModelsComponent; let component: DropzonesBarComponent;
let fixture: ComponentFixture<CanopyModelsComponent>; let fixture: ComponentFixture<DropzonesBarComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [CanopyModelsComponent] imports: [DropzonesBarComponent]
}) })
.compileComponents(); .compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(CanopyModelsComponent); fixture = TestBed.createComponent(DropzonesBarComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -17,7 +17,7 @@ import { UtilitiesService, DropZonesService } from 'src/app/core/services';
import { DropZoneByOaci, DropZoneByYear } from 'src/app/core/models'; import { DropZoneByOaci, DropZoneByYear } from 'src/app/core/models';
@Component({ @Component({
selector: 'app-drop-zones-bar', selector: 'app-dropzones-bar',
standalone: true, standalone: true,
imports: [ imports: [
MatButtonModule, MatCardModule, MatDividerModule, MatButtonModule, MatCardModule, MatDividerModule,
@@ -25,9 +25,9 @@ import { DropZoneByOaci, DropZoneByYear } from 'src/app/core/models';
BaseChartDirective, ChartistModule, BaseChartDirective, ChartistModule,
HistoryTableComponent HistoryTableComponent
], ],
templateUrl: './drop-zones-bar.component.html' templateUrl: './dropzones-bar.component.html'
}) })
export class DropZonesBarComponent implements OnInit, OnDestroy { export class DropzonesBarComponent implements OnInit, OnDestroy {
private _dropzoneByOaci: Subscription = new Subscription(); private _dropzoneByOaci: Subscription = new Subscription();
private _dropzoneByYear: Subscription = new Subscription(); private _dropzoneByYear: Subscription = new Subscription();
private _dropzonesByOaci!: Array<DropZoneByOaci>; private _dropzonesByOaci!: Array<DropZoneByOaci>;
@@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CanopySizesComponent } from './canopy-sizes.component'; import { DropzonesPieComponent } from './dropzones-pie.component';
describe('CanopySizesComponent', () => { describe('DropzonesPieComponent', () => {
let component: CanopySizesComponent; let component: DropzonesPieComponent;
let fixture: ComponentFixture<CanopySizesComponent>; let fixture: ComponentFixture<DropzonesPieComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [CanopySizesComponent] imports: [DropzonesPieComponent]
}) })
.compileComponents(); .compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(CanopySizesComponent); fixture = TestBed.createComponent(DropzonesPieComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });
@@ -16,16 +16,16 @@ import { UtilitiesService, DropZonesService } from 'src/app/core/services';
import { DropZoneByOaci, DropZoneByYear } from 'src/app/core/models'; import { DropZoneByOaci, DropZoneByYear } from 'src/app/core/models';
@Component({ @Component({
selector: 'app-drop-zones', selector: 'app-dropzones-pie',
standalone: true, standalone: true,
imports: [ imports: [
MatButtonModule, MatCardModule, MatDividerModule, MatButtonModule, MatCardModule, MatDividerModule,
MatExpansionModule, MatIconModule, MatMenuModule, MatExpansionModule, MatIconModule, MatMenuModule,
PieChartComponent, HistoryTableComponent PieChartComponent, HistoryTableComponent
], ],
templateUrl: './drop-zones.component.html' templateUrl: './dropzones-pie.component.html'
}) })
export class DropZonesComponent implements OnInit, OnDestroy { export class DropzonesPieComponent implements OnInit, OnDestroy {
private _dropzoneByOaci: Subscription = new Subscription(); private _dropzoneByOaci: Subscription = new Subscription();
private _dropzoneByYear: Subscription = new Subscription(); private _dropzoneByYear: Subscription = new Subscription();
private _dropzonesByOaci!: Array<DropZoneByOaci>; private _dropzonesByOaci!: Array<DropZoneByOaci>;
@@ -1,7 +1,7 @@
export * from './aeronefs/aeronefs.component'; export * from './aeronefs-pie/aeronefs-pie.component';
export * from './aeronefs-bar/aeronefs-bar.component'; export * from './aeronefs-bar/aeronefs-bar.component';
export * from './canopy-models/canopy-models.component'; export * from './canopies-models/canopies-models.component';
export * from './canopy-sizes/canopy-sizes.component'; export * from './canopies-sizes/canopies-sizes.component';
export * from './drop-zones/drop-zones.component'; export * from './dropzones-pie/dropzones-pie.component';
export * from './drop-zones-bar/drop-zones-bar.component'; export * from './dropzones-bar/dropzones-bar.component';
export * from './jumps-by-month/jumps-by-month.component'; export * from './jumps-by-month/jumps-by-month.component';
@@ -74,6 +74,7 @@ export class JumpsByMonthComponent implements OnInit, OnDestroy {
} }
private _loadJumpByDate() { private _loadJumpByDate() {
const minoration = 2; // -2 pour ne pas compter la première année
this.seriesHeader = this._utilitiesService.getMonthsList(); this.seriesHeader = this._utilitiesService.getMonthsList();
const values: Array<number> = this.seriesHeader.map(() => 0); const values: Array<number> = this.seriesHeader.map(() => 0);
this.seriesColTotal = [...values]; this.seriesColTotal = [...values];
@@ -111,7 +112,7 @@ export class JumpsByMonthComponent implements OnInit, OnDestroy {
this.seriesRowTotal.push(total); this.seriesRowTotal.push(total);
}); });
this.seriesColTotalClosed.forEach((row: number) => { this.seriesColTotalClosed.forEach((row: number) => {
const value: number = (row/(this.seriesName.length-2)); // -2 pour ne pas compter la première année const value: number = (row/(this.seriesName.length-minoration));
this.seriesRowAvg.push(parseInt(value.toFixed(0))); this.seriesRowAvg.push(parseInt(value.toFixed(0)));
}); });
this.seriesColTotalLastYears.forEach((row: number) => { this.seriesColTotalLastYears.forEach((row: number) => {
@@ -3,7 +3,7 @@
<canvas baseChart class="w-100 h-auto" <canvas baseChart class="w-100 h-auto"
[data]="chartConfig.barChartData" [data]="chartConfig.barChartData"
[options]="chartConfig.barChartOptions" [options]="chartConfig.barChartOptions"
[plugins]="chartConfig.barChartPlugins" [plugins]="chartConfig.barChartPlugins!"
[legend]="chartConfig.barChartLegend" [legend]="chartConfig.barChartLegend"
[type]="'bar'"> [type]="'bar'">
</canvas> </canvas>
@@ -39,7 +39,7 @@ export class BarChartComponent implements OnChanges {
} }
private _loadBarChart(): void { private _loadBarChart(): void {
const data: ChartDataset<'bar'> = { const dataset: ChartDataset<'bar'> = {
data: [...this.values], data: [...this.values],
label: this.label, label: this.label,
backgroundColor: this.colors.backgroundColor, backgroundColor: this.colors.backgroundColor,
@@ -47,7 +47,7 @@ export class BarChartComponent implements OnChanges {
borderWidth: 1 borderWidth: 1
}; };
this.chartConfig.barChartData.labels = [...this.names]; this.chartConfig.barChartData.labels = [...this.names];
this.chartConfig.barChartData.datasets!.push(data); this.chartConfig.barChartData.datasets!.push(dataset);
/*this.chartConfig.barChartOptions!.scales = { /*this.chartConfig.barChartOptions!.scales = {
x: {display: false}, x: {display: false},
y: {display: true} y: {display: true}
@@ -3,7 +3,7 @@
<canvas baseChart <canvas baseChart
[data]="chartConfig.barChartData" [data]="chartConfig.barChartData"
[options]="chartConfig.barChartOptions" [options]="chartConfig.barChartOptions"
[plugins]="chartConfig.barChartPlugins" [plugins]="chartConfig.barChartPlugins!"
[legend]="chartConfig.barChartLegend" [legend]="chartConfig.barChartLegend"
[type]="'bar'"> [type]="'bar'">
</canvas> </canvas>
@@ -39,7 +39,7 @@ export class BarHorizontalChartComponent implements OnChanges {
} }
private _loadBarChart(): void { private _loadBarChart(): void {
const data: ChartDataset<'bar'> = { const dataset: ChartDataset<'bar'> = {
data: [...this.values], data: [...this.values],
label: this.label, label: this.label,
backgroundColor: this.colors.backgroundColor, backgroundColor: this.colors.backgroundColor,
@@ -47,7 +47,7 @@ export class BarHorizontalChartComponent implements OnChanges {
borderWidth: 1 borderWidth: 1
}; };
this.chartConfig.barChartData.labels = [...this.names]; this.chartConfig.barChartData.labels = [...this.names];
this.chartConfig.barChartData.datasets!.push(data); this.chartConfig.barChartData.datasets!.push(dataset);
this.chartConfig.barChartLegend = this.legend; this.chartConfig.barChartLegend = this.legend;
this.displayCharts = true; this.displayCharts = true;
} }
@@ -3,7 +3,7 @@
<canvas baseChart class="w-100 h-auto" <canvas baseChart class="w-100 h-auto"
[data]="chartConfig.barChartData" [data]="chartConfig.barChartData"
[options]="chartConfig.barChartOptions" [options]="chartConfig.barChartOptions"
[plugins]="chartConfig.barChartPlugins" [plugins]="chartConfig.barChartPlugins!"
[legend]="chartConfig.barChartLegend" [legend]="chartConfig.barChartLegend"
[type]="'bar'"> [type]="'bar'">
</canvas> </canvas>
@@ -34,7 +34,7 @@ export class BarsChartComponent implements OnChanges, AfterContentChecked {
@Input() legend: boolean = false; @Input() legend: boolean = false;
ngOnChanges() { ngOnChanges() {
//this._loadBarChart(); this._loadBarChart();
} }
ngAfterContentChecked() { ngAfterContentChecked() {
@@ -63,7 +63,7 @@ export class BarsChartComponent implements OnChanges, AfterContentChecked {
} }
}; };
this.names.forEach((label: string, index: number) => { this.names.forEach((label: string, index: number) => {
const data: ChartDataset<'bar'> = { const dataset: ChartDataset<'bar'> = {
data: [...this.values[index]], data: [...this.values[index]],
label: label, label: label,
backgroundColor: this.colors.backgroundColor[index], backgroundColor: this.colors.backgroundColor[index],
@@ -71,7 +71,7 @@ export class BarsChartComponent implements OnChanges, AfterContentChecked {
borderWidth: 1, borderWidth: 1,
//stack: 'Stack 0' //stack: 'Stack 0'
}; };
this.chartConfig.barChartData.datasets!.push(data); this.chartConfig.barChartData.datasets!.push(dataset);
}); });
this.chartConfig.barChartLegend = this.legend; this.chartConfig.barChartLegend = this.legend;
this.displayCharts = true; this.displayCharts = true;
@@ -50,7 +50,7 @@ export class CircleChartComponent implements OnChanges {
} }
/*private _loadCircleChart(): void { /*private _loadCircleChart(): void {
const data: ChartDataset<'circle'> = { const dataset: ChartDataset<'circle'> = {
data: [...this.values], data: [...this.values],
label: 'Nombre total de sauts', label: 'Nombre total de sauts',
backgroundColor: this._utilitiesService.getSeriesColors(0.8), backgroundColor: this._utilitiesService.getSeriesColors(0.8),
@@ -58,7 +58,7 @@ export class CircleChartComponent implements OnChanges {
borderWidth: 1 borderWidth: 1
}; };
this.chartConfig.circleChartLabels = [...this.names]; this.chartConfig.circleChartLabels = [...this.names];
this.chartConfig.circleChartDatasets!.push(data); this.chartConfig.circleChartDatasets!.push(dataset);
this.displayCharts = true; this.displayCharts = true;
}*/ }*/
} }
@@ -1,5 +1,5 @@
import { Component, Input, OnChanges } from '@angular/core'; import { Component, Input, OnChanges } from '@angular/core';
import { ChartDataset } from 'chart.js'; import { ChartDataset, Point } from 'chart.js';
import { BaseChartDirective } from 'ng2-charts'; import { BaseChartDirective } from 'ng2-charts';
import { UtilitiesService } from 'src/app/core/services'; import { UtilitiesService } from 'src/app/core/services';
@@ -32,6 +32,7 @@ export class LineChartComponent implements OnChanges {
borderColor: this._utilitiesService.getSeriesColors(1) borderColor: this._utilitiesService.getSeriesColors(1)
}; };
@Input() legend: boolean = false; @Input() legend: boolean = false;
@Input() hideZero: boolean = false;
ngOnChanges() { ngOnChanges() {
this._loadLineChart(); this._loadLineChart();
@@ -40,8 +41,14 @@ export class LineChartComponent implements OnChanges {
private _loadLineChart(): void { private _loadLineChart(): void {
this.chartConfig.lineChartData.labels = [...this.headers]; this.chartConfig.lineChartData.labels = [...this.headers];
this.names.forEach((label: string, index: number) => { this.names.forEach((label: string, index: number) => {
const data: ChartDataset<'line'> = { let data: Array<(number | Point | null)> = [];
data: [...this.values[index]], if (this.hideZero) {
data = [...this.values[index].map(value => value === 0 ? null : value)];
} else {
data = [...this.values[index]];
}
const dataset: ChartDataset<'line'> = {
data: data,
label: label, label: label,
backgroundColor: this.colors.backgroundColor[index], backgroundColor: this.colors.backgroundColor[index],
borderColor: this.colors.borderColor[index], borderColor: this.colors.borderColor[index],
@@ -49,7 +56,7 @@ export class LineChartComponent implements OnChanges {
cubicInterpolationMode: 'monotone', cubicInterpolationMode: 'monotone',
tension: 0 tension: 0
}; };
this.chartConfig.lineChartData.datasets!.push(data); this.chartConfig.lineChartData.datasets!.push(dataset);
}); });
this.chartConfig.lineChartLegend = this.legend; this.chartConfig.lineChartLegend = this.legend;
this.displayCharts = true; this.displayCharts = true;
@@ -44,7 +44,7 @@ export class LineAreaChartComponent implements OnChanges {
if (index === 0) { if (index === 0) {
fill = true; fill = true;
} }
const data: ChartDataset<'line'> = { const dataset: ChartDataset<'line'> = {
data: [...this.values[index]], data: [...this.values[index]],
label: label, label: label,
backgroundColor: this.colors.backgroundColor[index], backgroundColor: this.colors.backgroundColor[index],
@@ -55,7 +55,7 @@ export class LineAreaChartComponent implements OnChanges {
cubicInterpolationMode: 'monotone', cubicInterpolationMode: 'monotone',
tension: 0 tension: 0
}; };
this.chartConfig.lineChartData.datasets!.push(data); this.chartConfig.lineChartData.datasets!.push(dataset);
}); });
this.chartConfig.lineChartLegend = this.legend; this.chartConfig.lineChartLegend = this.legend;
this.displayCharts = true; this.displayCharts = true;
@@ -49,7 +49,7 @@ export class PieChartComponent implements OnChanges {
} }
/*private _loadDoughnutChart(): void { /*private _loadDoughnutChart(): void {
const data: ChartDataset<'doughnut'> = { const dataset: ChartDataset<'doughnut'> = {
data: [...this.values], data: [...this.values],
label: 'Nombre total de sauts', label: 'Nombre total de sauts',
backgroundColor: this._utilitiesService.getSeriesColors(0.8), backgroundColor: this._utilitiesService.getSeriesColors(0.8),
@@ -57,7 +57,7 @@ export class PieChartComponent implements OnChanges {
borderWidth: 1 borderWidth: 1
}; };
this.chartConfig.doughnutChartLabels = [...this.names]; this.chartConfig.doughnutChartLabels = [...this.names];
this.chartConfig.doughnutChartDatasets!.push(data); this.chartConfig.doughnutChartDatasets!.push(dataset);
this.displayCharts = true; this.displayCharts = true;
}*/ }*/
} }
@@ -1,4 +1,4 @@
import { Component, Input, OnChanges, AfterContentChecked } from '@angular/core'; import { Component, Input, AfterContentChecked } from '@angular/core';
import { NgIf, NgFor } from '@angular/common'; import { NgIf, NgFor } from '@angular/common';
@Component({ @Component({
@@ -10,7 +10,7 @@ import { NgIf, NgFor } from '@angular/common';
templateUrl: './history-table.component.html', templateUrl: './history-table.component.html',
styleUrls: [] styleUrls: []
}) })
export class HistoryTableComponent implements OnChanges, AfterContentChecked { export class HistoryTableComponent implements AfterContentChecked {
public seriesColTotal: number[] = []; public seriesColTotal: number[] = [];
public grandTotal: number = 0; public grandTotal: number = 0;
@@ -22,10 +22,10 @@ export class HistoryTableComponent implements OnChanges, AfterContentChecked {
@Input() rows: Array<Array<number>> = []; @Input() rows: Array<Array<number>> = [];
@Input() colors: string[] = []; @Input() colors: string[] = [];
ngOnChanges() { /*ngOnChanges() {
//this._loadHistoryTable(); //this._loadHistoryTable();
//console.log('onChanges'); //console.log('onChanges');
} }*/
ngAfterContentChecked() { ngAfterContentChecked() {
if (this.rows !== undefined && this.rows.length) { if (this.rows !== undefined && this.rows.length) {
@@ -1,11 +1,10 @@
import { Component, OnInit, OnDestroy, Input, computed, Signal, WritableSignal } from '@angular/core'; import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import { NgClass, DatePipe } from '@angular/common'; import { NgClass, DatePipe } from '@angular/common';
import { RouterLink } from '@angular/router'; import { RouterLink } from '@angular/router';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { Errors, Jump, JumpList, JumpListConfig } from 'src/app/core/models'; import { Errors, Jump, JumpList, JumpListConfig } from 'src/app/core/models';
import { JumpsService } from 'src/app/core/services'; import { JumpsService } from 'src/app/core/services';
@@ -39,7 +39,7 @@ export class JumpPreviewComponent implements OnDestroy {
deleteJump() { deleteJump() {
this.isDeleting = true; this.isDeleting = true;
const jump$: Observable<any> = this.jumpsService.destroy(this.jump.slug); const jump$: Observable<boolean> = this.jumpsService.destroy(this.jump.slug);
this._jump = jump$.subscribe({ this._jump = jump$.subscribe({
next: () => { next: () => {
this.router.navigateByUrl('/'); this.router.navigateByUrl('/');
@@ -12,7 +12,7 @@
</button> </button>
<mat-spinner [diameter]="32" [hidden]="!loading" color="accent" class="align-middle ms-2"></mat-spinner> <mat-spinner [diameter]="32" [hidden]="!loading" color="accent" class="align-middle ms-2"></mat-spinner>
<span [hidden]="jumpsCount || loading" class="ms-2 fst-italic">Aucun saut à afficher pour le moment.</span> <span [hidden]="jumpsCount || loading" class="ms-2 fst-italic">Aucun saut à afficher pour le moment.</span>
<span [hidden]="!loading" class="ms-2 fst-italic">Actualisation des sauts en cours</span> <span [hidden]="!loading" class="ms-2 fst-italic">Chargement des sauts en cours</span>
</form> </form>
</div> </div>
<div class="col-md-6 col-xs-12 px-0"> <div class="col-md-6 col-xs-12 px-0">
@@ -224,7 +224,7 @@
</ng-container> </ng-container>
</td> </td>
</ng-container> --> </ng-container> -->
<!-- Video Column --> <!-- Video Column
<ng-container matColumnDef="video"> <ng-container matColumnDef="video">
<th class="pl-3" mat-header-cell *matHeaderCellDef> Video </th> <th class="pl-3" mat-header-cell *matHeaderCellDef> Video </th>
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element"> <td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
@@ -236,6 +236,41 @@
</ngx-skeleton-loader> </ngx-skeleton-loader>
} }
</td> </td>
</ng-container> -->
<!-- Files Column -->
<ng-container matColumnDef="files">
<th class="pl-3" mat-header-cell *matHeaderCellDef> Fichiers </th>
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
@if (element.files) {
@for (file of element.files; track $index) {
@switch (file.type) {
@case ('video') {
<button mat-button (click)="openViewDialog(element)" color="navy-light">
<mat-icon fontIcon="videocam me-2"></mat-icon> {{ file.type }}
</button>
}
@case ('image') {
<button mat-button (click)="openViewDialog(element)" color="navy-light">
<mat-icon fontIcon="image me-2"></mat-icon> {{ file.type }}
</button>
}
@case ('kml') {
<button mat-button (click)="openViewDialog(element)" color="navy-light">
<mat-icon fontIcon="language me-2"></mat-icon> {{ file.type }}
</button>
}
@default {
<button mat-button (click)="openViewDialog(element)" color="navy-light">
<mat-icon fontIcon="description me-2"></mat-icon> {{ file.type }}
</button>
}
}
}
}
@if (!element.numero) {
<ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader>
}
</td>
</ng-container> </ng-container>
<!-- Actions Column --> <!-- Actions Column -->
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
@@ -247,13 +282,17 @@
<mat-icon>more_vert</mat-icon> <mat-icon>more_vert</mat-icon>
</button> </button>
<mat-menu [hidden]="!showAdd" #actionMenu="matMenu"> <mat-menu [hidden]="!showAdd" #actionMenu="matMenu">
<button mat-menu-item [routerLink]="['/jump', element.slug]">
<mat-icon aria-label="Voir" fontIcon="visibility" color="info" class="mini"></mat-icon>
<span class="text-info">Détails</span>
</button>
<button mat-menu-item (click)="openEditDialog(element)"> <button mat-menu-item (click)="openEditDialog(element)">
<mat-icon aria-label="Modifier" fontIcon="edit" color="primary" class="mini"></mat-icon> <mat-icon aria-label="Modifier" fontIcon="edit" color="primary" class="mini"></mat-icon>
<span class="text-primary">Modifier</span> <span class="text-primary">Modifier</span>
</button> </button>
<button mat-menu-item (click)="openDeleteDialog(element)"> <button mat-menu-item (click)="openDeleteDialog(element)">
<mat-icon aria-label="Supprimer" fontIcon="delete" color="danger" class="mini"></mat-icon> <mat-icon aria-label="Supprimer" fontIcon="delete" color="raspberry" class="mini"></mat-icon>
<span class="text-danger">Supprimer</span> <span class="text-raspberry">Supprimer</span>
</button> </button>
</mat-menu> </mat-menu>
} }
@@ -273,57 +312,3 @@
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true" class="accent"></tr> <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true" class="accent"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" class="text-middle accent"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;" class="text-middle accent"></tr>
</table> </table>
<!--
<table mat-table [dataSource]="jumps">
<ng-container [matColumnDef]="col.key" *ngFor="let col of columnsSchema">
<th mat-header-cell *matHeaderCellDef [ngSwitch]="col.key">
<span *ngSwitchCase="'isSelected'">
<mat-checkbox (change)="selectAll($event)" [checked]="isAllSelected()" [indeterminate]="!isAllSelected() && isAnySelected()"></mat-checkbox>
</span>
<span *ngSwitchDefault>{{ col.label }}</span>
</th>
<td mat-cell *matCellDef="let element">
<div [ngSwitch]="col.type" *ngIf="!element.isEdit">
<ng-container *ngSwitchCase="'isSelected'">
<mat-checkbox (change)="element.isSelected = $event.checked" [checked]="element.isSelected">
</mat-checkbox>
</ng-container>
<div class="btn-edit" *ngSwitchCase="'isEdit'">
<button mat-button (click)="element.isEdit = !element.isEdit">
Modifier
</button>
<button mat-button class="button-remove" (click)="openDeleteDialog(element)">
Supprimer
</button>
</div>
<span *ngSwitchCase="'date'">
{{ element[col.key] | date: 'mediumDate' }}
</span>
<span *ngSwitchDefault>
{{ element[col.key] }}
</span>
</div>
<div [ngSwitch]="col.type" *ngIf="element.isEdit">
<div *ngSwitchCase="'isSelected'"></div>
<div class="btn-edit" *ngSwitchCase="'isEdit'">
<button mat-icon-button color="primary" [hidden]="!showAdd" (click)="openEditDialog(element)" [disabled]="disableSubmit(element.id)">
<mat-icon aria-label="Modifier" fontIcon="edit"></mat-icon>
</button>
</div>
<mat-form-field class="form-input" *ngSwitchCase="'date'" appearance="fill">
<mat-label>Choose a date</mat-label>
<input matInput [matDatepicker]="picker" [(ngModel)]="element[col.key]" />
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
<mat-form-field class="form-input" *ngSwitchDefault>
<input matInput [required]="col.required!" [pattern]="col.pattern!" [type]="col.type"
[(ngModel)]="element[col.key]" (change)="inputHandler($event, element.id, col.key)" />
</mat-form-field>
</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
-->
@@ -21,7 +21,7 @@ import { take } from 'rxjs/operators';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { FrenchPaginator } from 'src/app/components/shared/french-paginator.component'; import { FrenchPaginator } from 'src/app/components/shared/french-paginator.component';
import { ColumnDefinition, Errors, Jump, JumpList, JumpListConfig, jumpColumns } from 'src/app/core/models'; import { ColumnDefinition, Errors, Jump, JumpByDay, JumpFile, JumpFileType, JumpList, JumpListConfig, jumpColumns } from 'src/app/core/models';
import { JumpsService, JumpTableService } from 'src/app/core/services'; import { JumpsService, JumpTableService } from 'src/app/core/services';
import { JumpAddDialogComponent, JumpDeleteDialogComponent, JumpEditDialogComponent, JumpViewDialogComponent } from 'src/app/components/logbook/dialogs' import { JumpAddDialogComponent, JumpDeleteDialogComponent, JumpEditDialogComponent, JumpViewDialogComponent } from 'src/app/components/logbook/dialogs'
@@ -41,6 +41,7 @@ import { JumpAddDialogComponent, JumpDeleteDialogComponent, JumpEditDialogCompon
}) })
export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChecked { export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChecked {
private _jumps: Subscription = new Subscription(); private _jumps: Subscription = new Subscription();
private _subscriptions: Array<Subscription> = [];
//private _jump: Subscription = new Subscription(); //private _jump: Subscription = new Subscription();
private _query: JumpListConfig = { type: 'all', filters: {} }; private _query: JumpListConfig = { type: 'all', filters: {} };
//private _lastjump: Jump = {} as Jump; //private _lastjump: Jump = {} as Jump;
@@ -53,13 +54,13 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
'numero', 'date', 'lieu', 'oaci', 'aeronef', 'numero', 'date', 'lieu', 'oaci', 'aeronef',
'imat', 'hauteur', 'voile', 'taille', 'categorie', 'imat', 'hauteur', 'voile', 'taille', 'categorie',
'module', 'participants', 'programme', 'accessoires', 'module', 'participants', 'programme', 'accessoires',
'zone', 'video', 'actions' 'zone', 'files', 'actions'
];*/ ];*/
public displayedColumns: string[] = [ public displayedColumns: string[] = [
'numero', 'date', 'lieu', 'aeronef', 'numero', 'date', 'lieu', 'aeronef',
'hauteur', 'voile', 'categorie', 'hauteur', 'voile', 'categorie',
'participants', 'zone', 'accessoires', 'participants', 'zone', 'accessoires',
'video', 'actions' 'files', 'actions'
]; ];
//public displayedColumns: string[] = jumpColumns.map((col) => col.key) //public displayedColumns: string[] = jumpColumns.map((col) => col.key)
public columnsSchema: Array<ColumnDefinition> = jumpColumns; public columnsSchema: Array<ColumnDefinition> = jumpColumns;
@@ -115,7 +116,9 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
ngOnDestroy() { ngOnDestroy() {
this._jumps.unsubscribe(); this._jumps.unsubscribe();
//this._jump.unsubscribe(); this._subscriptions.forEach(subscription => {
subscription.unsubscribe();
});
} }
ngAfterContentChecked() { ngAfterContentChecked() {
@@ -155,6 +158,9 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
const jumps$: Observable<JumpList> = this._jumpsService.query(this._query); const jumps$: Observable<JumpList> = this._jumpsService.query(this._query);
this._jumps = jumps$.subscribe({ this._jumps = jumps$.subscribe({
next: (data: JumpList) => { next: (data: JumpList) => {
//this.updateJumps(data.jumps);
//this.linkSkydiverIdJumps();
//this.getKmlJumpFiles(data.jumps);
this.loading = false; this.loading = false;
this.jumps = new MatTableDataSource<Jump>(data.jumps); this.jumps = new MatTableDataSource<Jump>(data.jumps);
this.jumpsCount = data.jumpsCount; this.jumpsCount = data.jumpsCount;
@@ -170,12 +176,150 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
}); });
} }
linkSkydiverIdJumps() {
const config: JumpListConfig = { type: 'all', filters: {} } as JumpListConfig;
config.filters.limit = 200;
config.filters.offset = 0;
config.filters.dateRangeStart = '2024-01-01 00:00:00';
//const year = new Date().getFullYear();
//config.filters.dateRangeEnd = `${year}-12-31 23:59:59`;
config.filters.dateRangeEnd = '2024-12-31 23:59:59';
//console.log(config.filters);
const subscription: Subscription = this._jumpsService.getAllByDay(config)
.pipe(take(1))
.subscribe({
next: (days) => {
this._resetErrors();
//console.log('getAllByDay : ', days);
days.forEach(day => {
this.loadSkydiverIdJumps(day, config);
});
},
error: (err) => {
this.errors = err;
}
});
this._subscriptions.push(subscription);
}
loadSkydiverIdJumps(day: JumpByDay, config: JumpListConfig) {
config.filters.dateRangeStart = `${day.jumps[0].date!.substring(0, 10)} 00:00:00`;
config.filters.dateRangeEnd = `${day.jumps[0].date!.substring(0, 10)} 23:59:59`;
const subscription: Subscription = this._jumpsService.getAllFromSkydiverIdApi(config)
.pipe(take(1))
.subscribe({
next: (data) => {
if (data.items.length) {
const diff = day.count - data.items.length;
if (diff < 0) {
console.error(`${diff} ${diff > 1 ? 'données' : 'donnée'} altimètre de trop le ${day.jumps[0].date!.substring(0, 10)}`);
} else {
if (diff > 0) {
console.error(`${diff}/${day.count} ${diff > 1 ? 'données altimètre manquantes' : 'donnée altimètre manquante'} le ${day.jumps[0].date!.substring(0, 10)}`, day.jumps, data.items);
}
console.log(`${day.count} ${day.count > 1 ? 'sauts' : 'saut'} le ${data.items[0].date!.substring(0, 10)}`, day.jumps.sort((a, b) => b.numero! - a.numero!), data.items);
data.items.map((jump, index) => {
const file: JumpFile = {
name: jump.name,
path: `/Volumes/Storage/Skydive/X2_Logs/${day.jumps[index].date!.substring(0, 4)}/`,
type: JumpFileType.CSV
} as JumpFile;
this._subscriptions.push(
this._jumpsService.saveX2Data(day.jumps[index].slug!, file, jump)
.pipe(take(1))
.subscribe({
next: (jump) => {
this._resetErrors();
console.log(`Le fichier ${file.name} a été ajouté au saut n°${jump.numero} !`, 'OK');
},
error: (err) => {
this.errors = err;
}
})
);
return jump;
});
}
//console.log(`${day.count} ${day.count > 1 ? 'sauts' : 'saut'} le ${day.jumps[0].date!.substring(0, 10)}`);
//console.log(`${data.items.length} ${data.items.length > 1 ? 'sauts' : 'saut'} le ${data.items[0].date!.substring(0, 10)}`, data);
} else {
console.info('Aucune données altimètre pour le ', day.jumps[0].date!.substring(0, 10));
}
},
error: (err) => {
console.error('getAllFromSkydiverIdApi error');
this.errors = err;
}
});
this._subscriptions.push(subscription);
}
updateJumps(jumps: Array<Jump>) {
jumps.map(jump => {
jump.files = [];
if (jump.dossier !== "" && jump.video !== "") {
const file: JumpFile = {
name: jump.video!,
path: jump.dossier!,
type: JumpFileType.VIDEO
} as JumpFile;
this._subscriptions.push(
this._jumpsService.saveFile(jump.slug, file)
.pipe(take(1))
.subscribe({
next: (file) => {
this._resetErrors();
jump.files.push(file);
console.log(`Le fichier ${file.name} a été ajouté au saut n°${jump.numero} !`, 'OK');
},
error: (err) => {
this.errors = err;
}
})
);
}
return jump;
});
}
getKmlJumpFiles(jumps: Array<Jump>) {
jumps.map(jump => {
if (jump.x2data !== null) {
//console.log(`Le fichier ${jump.x2data.name} existe pour le saut n°${jump.numero} !`, `https://skydiver.id/api/jump/${jump.x2data.id}/kml`);
const file: JumpFile = {
name: jump.x2data.name.substring(0, -4) + '.kml',
path: `/X2_Kmls/${jump.date.substring(0, 4)}/`,
type: JumpFileType.KML
} as JumpFile;
this._subscriptions.push(
this._jumpsService.saveKml(jump.slug, file)
.pipe(take(1))
.subscribe({
next: (file) => {
this._resetErrors();
console.log(`Le fichier ${file.path}${file.name} a été ajouté au saut n°${jump.numero} !`, 'OK');
},
error: (err) => {
this.errors = err;
}
})
);
/*
*/
}
return jump;
});
}
deleteJump(slug: string) { deleteJump(slug: string) {
this.isDeleting = true; this.isDeleting = true;
this._jumpsService.destroy(slug).pipe(take(1)) this._subscriptions.push(
this._jumpsService.destroy(slug)
.pipe(take(1))
.subscribe(() => { .subscribe(() => {
this.router.navigateByUrl('/'); this.router.navigateByUrl('/');
}); })
);
} }
openDeleteDialog(jump: Jump): void { openDeleteDialog(jump: Jump): void {
@@ -183,12 +327,15 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
width: '60vw', width: '60vw',
data: jump data: jump
}); });
dialogRef.afterClosed().pipe(take(1)) this._subscriptions.push(
dialogRef.afterClosed()
.pipe(take(1))
.subscribe(result => { .subscribe(result => {
if (result != undefined) { if (result != undefined) {
this._onDelete(result.slug); this._onDelete(result.slug);
} }
}); })
);
} }
openEditDialog(jump: Jump): void { openEditDialog(jump: Jump): void {
@@ -196,12 +343,15 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
width: '70vw', width: '70vw',
data: jump data: jump
}); });
dialogRef.afterClosed().pipe(take(1)) this._subscriptions.push(
dialogRef.afterClosed()
.pipe(take(1))
.subscribe(result => { .subscribe(result => {
if (result != undefined) { if (result != undefined) {
this._onEdit(result); this._onEdit(result);
} }
}); })
);
} }
openViewDialog(jump: Jump): void { openViewDialog(jump: Jump): void {
@@ -230,6 +380,7 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
private _onDelete(slug: string): void { private _onDelete(slug: string): void {
try { try {
this.isDeleting = true; this.isDeleting = true;
this._subscriptions.push(
this._jumpsService.destroy(slug) this._jumpsService.destroy(slug)
.pipe(take(1)) .pipe(take(1))
.subscribe({ .subscribe({
@@ -243,7 +394,8 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
error: (err) => { error: (err) => {
this.errors = err; this.errors = err;
} }
}); })
);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@@ -252,6 +404,7 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
private _onEdit(jump: Jump): void { private _onEdit(jump: Jump): void {
try { try {
this.isSubmitting = true; this.isSubmitting = true;
this._subscriptions.push(
this._jumpsService.update(jump) this._jumpsService.update(jump)
.pipe(take(1)) .pipe(take(1))
.subscribe({ .subscribe({
@@ -266,7 +419,8 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
this.errors = err; this.errors = err;
this.isSubmitting = false; this.isSubmitting = false;
} }
}); })
);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@@ -1,6 +1,6 @@
<mat-toolbar class="topbar bg-primary"> <mat-toolbar class="topbar bg-primary">
<div class="navbar-header"> <div class="navbar-header">
<a class="navbar-brand" href="index.html"> <a class="navbar-brand" href="/">
<span class="logo"> <span class="logo">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="190px" height="33px" viewBox="0 0 190 33" enable-background="new 0 0 190 33" xml:space="preserve"> <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="190px" height="33px" viewBox="0 0 190 33" enable-background="new 0 0 190 33" xml:space="preserve">
<path id="Jumper_2" fill="#FFFFFF" <path id="Jumper_2" fill="#FFFFFF"
@@ -281,8 +281,8 @@
<app-layout-footer></app-layout-footer> <app-layout-footer></app-layout-footer>
</mat-sidenav> </mat-sidenav>
<mat-sidenav-content class="page-wrapper"> <mat-sidenav-content class="page-wrapper">
<div class="page-content"> <!--<div class="page-content">-->
<router-outlet><!--<app-spinner></app-spinner>--></router-outlet> <router-outlet><!--<app-spinner></app-spinner>--></router-outlet>
</div> <!--</div>-->
</mat-sidenav-content> </mat-sidenav-content>
</mat-sidenav-container> </mat-sidenav-container>
@@ -1,5 +1,4 @@
import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core'; import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { Router, RouterOutlet, RouterModule } from '@angular/router'; import { Router, RouterOutlet, RouterModule } from '@angular/router';
import { MediaMatcher } from '@angular/cdk/layout'; import { MediaMatcher } from '@angular/cdk/layout';
import { MatBadgeModule } from '@angular/material/badge'; import { MatBadgeModule } from '@angular/material/badge';
File diff suppressed because one or more lines are too long
+12 -4
View File
@@ -11,14 +11,19 @@ const MENUAUTH: Menu[] = [
const MENUITEMS: Menu[] = [ const MENUITEMS: Menu[] = [
{ state: 'dashboard', type: 'link', name: 'Dashboard', icon: 'dashboard', desc: '' }, { state: 'dashboard', type: 'link', name: 'Dashboard', icon: 'dashboard', desc: '' },
{ state: 'logbook', type: 'link', name: 'Carnet de saut', icon: 'assignment_turned_in', desc: '' }, { state: 'logbook', type: 'link', name: 'Carnet de saut', icon: 'assignment_turned_in', desc: '' },
{ state: 'aeronef', type: 'link', name: 'Aéronefs', icon: 'flight', desc: '' }, { state: 'aeronefs', type: 'link', name: 'Aéronefs', icon: 'flight', desc: '' },
{ state: 'canopy', type: 'link', name: 'Voiles', icon: 'paragliding', desc: '' }, { state: 'dropzones', type: 'link', name: 'Drop Zones', icon: 'flight_takeoff', desc: '' },
{ state: 'dropzone', type: 'link', name: 'Dropzones', icon: 'adjust', desc: '' }, { state: 'canopies', type: 'link', name: 'Voiles', icon: 'paragliding', desc: '' },
{ state: 'jump', type: 'link', name: 'Sauts', icon: 'cloud_download', desc: '' }, { state: 'jumps', type: 'link', name: 'Sauts', icon: 'filter_drama', desc: '' },
{ state: 'calculator', type: 'link', name: 'Taille de voile', icon: 'calculate', desc: '' }, { state: 'calculator', type: 'link', name: 'Taille de voile', icon: 'calculate', desc: '' },
{ state: 'qcm', type: 'menu', name: 'QCM Brevets', icon: 'rule_folder', desc: '' } { state: 'qcm', type: 'menu', name: 'QCM Brevets', icon: 'rule_folder', desc: '' }
]; ];
const MENUPJUMP: Menu[] = [
{ state: '#', type: 'link', name: 'Modifier', icon: 'edit', desc: '', color: 'primary' },
{ state: '#', type: 'link', name: 'Supprimer', icon: 'delete', desc: '', color: 'raspberry' }
];
const MENUQCM: Menu[] = [ const MENUQCM: Menu[] = [
{ state: 'bpa', type: 'link', name: 'BPA', icon: 'rule' }, { state: 'bpa', type: 'link', name: 'BPA', icon: 'rule' },
{ state: 'c', type: 'link', name: 'Brevet C', icon: 'rule' }, { state: 'c', type: 'link', name: 'Brevet C', icon: 'rule' },
@@ -63,4 +68,7 @@ export class MenuItems {
getMenuPanel(): Menu[] { getMenuPanel(): Menu[] {
return MENUPANEL; return MENUPANEL;
} }
getMenuJump(): Menu[] {
return MENUPJUMP;
}
} }
@@ -11,7 +11,8 @@ export class ShowAuthedDirective implements OnInit, OnDestroy {
private _auth: Subscription = new Subscription(); private _auth: Subscription = new Subscription();
constructor( constructor(
private templateRef: TemplateRef<any>, //private templateRef: TemplateRef<any>,
private templateRef: TemplateRef<never>,
private userService: UserService, private userService: UserService,
private viewContainer: ViewContainerRef private viewContainer: ViewContainerRef
) { } ) { }
+2 -2
View File
@@ -1,10 +1,10 @@
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router'; import { CanActivateFn, Router } from '@angular/router';
import { map, take } from 'rxjs/operators'; import { map, take } from 'rxjs/operators';
import { UserService } from 'src/app/core/services'; import { UserService } from 'src/app/core/services';
export const adminGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { export const adminGuard: CanActivateFn = () => {
const router = inject(Router); const router = inject(Router);
const userService = inject(UserService); const userService = inject(UserService);
return userService.currentUser.pipe(take(1)).pipe(map(data => { return userService.currentUser.pipe(take(1)).pipe(map(data => {
+2 -2
View File
@@ -1,10 +1,10 @@
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router'; import { CanActivateFn, Router } from '@angular/router';
import { map, take } from 'rxjs/operators'; import { map, take } from 'rxjs/operators';
import { UserService } from 'src/app/core/services'; import { UserService } from 'src/app/core/services';
export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { export const authGuard: CanActivateFn = () => {
const router = inject(Router); const router = inject(Router);
const userService = inject(UserService); const userService = inject(UserService);
return userService.isAuthenticated.pipe(take(1)).pipe(map(data => { return userService.isAuthenticated.pipe(take(1)).pipe(map(data => {
+2 -2
View File
@@ -1,11 +1,11 @@
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router'; import { CanActivateFn } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators'; import { map, take } from 'rxjs/operators';
import { UserService } from 'src/app/core/services'; import { UserService } from 'src/app/core/services';
export const noauthGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> => { export const noauthGuard: CanActivateFn = (): Observable<boolean> => {
/* /*
const router = inject(Router); const router = inject(Router);
const userService = inject(UserService); const userService = inject(UserService);
@@ -0,0 +1,6 @@
import { Application } from './application.model';
export interface ApplicationList {
applications: Array<Application>;
applicationsCount: number;
}
-13
View File
@@ -1,13 +0,0 @@
export interface CanopySize {
id: string;
key: any;
value: any;
doc?: CanopySizeDoc;
}
export interface CanopySizeDoc {
_id: string;
_rev: string;
type: string;
weight: number;
values: [];
}
+1 -1
View File
@@ -3,7 +3,7 @@ import { ChartConfiguration, ChartOptions } from 'chart.js';
export interface BarConfig { export interface BarConfig {
barChartData: ChartConfiguration<'bar'>['data'], barChartData: ChartConfiguration<'bar'>['data'],
barChartOptions: ChartConfiguration<'bar'>['options'], barChartOptions: ChartConfiguration<'bar'>['options'],
barChartPlugins: any[], barChartPlugins: ChartConfiguration<'bar'>['plugins'],
barChartLegend: boolean barChartLegend: boolean
} }

Some files were not shown because too many files have changed in this diff Show More