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/compiler": "^17.3.5",
"@angular/core": "^17.3.5",
"@angular/forms": "^17.3.5",
"@angular/localize": "^17.3.5",
"@angular/material": "^17.3.6",
"@angular/forms": "^17.3.8",
"@angular/google-maps": "^17.3.8",
"@angular/localize": "^17.3.8",
"@angular/material": "^17.3.8",
"@angular/platform-browser": "^17.3.5",
"@angular/platform-browser-dynamic": "^17.3.5",
"@angular/router": "^17.3.5",
"@angular/platform-browser-dynamic": "^17.3.8",
"@angular/router": "^17.3.8",
"bootstrap": "^5.3.3",
"chart.js": "^4.4.2",
"chartist": "^1.3.0",
"ng-chartist": "^8.2.0",
"ng2-charts": "^6.0.0",
"ng2-charts": "^6.0.1",
"ngx-skeleton-loader": "^9.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "^0.14.4"
"zone.js": "^0.14.5"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.3.5",
"@angular-devkit/build-angular": "^17.3.7",
"@angular-eslint/builder": "17.3.0",
"@angular-eslint/eslint-plugin": "17.3.0",
"@angular-eslint/eslint-plugin-template": "17.3.0",
"@angular-eslint/schematics": "17.3.0",
"@angular-eslint/template-parser": "17.3.0",
"@angular/cli": "^17.3.5",
"@angular/cli": "^17.3.7",
"@angular/compiler-cli": "^17.3.5",
"@types/jasmine": "~5.1.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';
@@ -6,14 +6,12 @@ describe('AeronefsComponent', () => {
let component: AeronefsComponent;
let fixture: ComponentFixture<AeronefsComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AeronefsComponent]
})
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AeronefsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
@@ -21,7 +21,7 @@ import { UtilitiesService } from 'src/app/core/services';
import { AeronefByImat, AeronefByYear } from 'src/app/core/models';
@Component({
selector: 'app-aeronef',
selector: 'app-aeronefs',
standalone: true,
imports: [
DatePipe,
@@ -29,8 +29,8 @@ import { AeronefByImat, AeronefByYear } from 'src/app/core/models';
MatExpansionModule, MatGridListModule, MatIconModule, MatMenuModule,
BarsChartComponent, HistoryTableComponent, PieChartComponent
],
templateUrl: './aeronef.component.html',
styleUrl: './aeronef.component.scss',
templateUrl: './aeronefs.component.html',
styleUrl: './aeronefs.component.scss',
animations: [
trigger('flyInOut', [
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 _aeronefsByImat!: Array<AeronefByImat>;
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="col-md-6 offset-md-3 col-xs-12">
<h1 class="text-xs-center">{{ title }}</h1>
@@ -1,5 +1,4 @@
<div @flyInOut>
<div class="calculator-page content">
<div class="d-flex">
<div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
@@ -56,7 +55,7 @@
<table class="table table-striped table-dark table-hover mat-elevation-z2">
<thead>
<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 min</th>
<th class="text-nowrap">Taille min -11%</th>
@@ -64,34 +63,34 @@
</thead>
<tbody>
<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 }}">
{{ info.sizesFeet.current }} </td>
<td class="font-monospace text-success"> ● {{ info.sizesFeet.min }} </td>
<td class="font-monospace text-warning"> ● {{ info.sizesFeet.min11 }} </td>
</tr>
<tr class="warn">
<th class="first">Charge alaire</th>
<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.min | number : '1.2-3' }}</td>
<td class="font-monospace">{{ info.charges.min11 | number : '1.2-3' }}</td>
</tr>
<tr class="warn">
<th class="first">Taille de voile en m<sup>2</sup></th>
<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.min | number : '1.2-3' }}</td>
<td class="font-monospace">{{ info.sizesMeter.min11 | number : '1.2-3' }}</td>
</tr>
<tr class="warn">
<th class="first">Coefficient kilo/livres</th>
<th class="text-nowrap pe-4">Coefficient kilo/livres</th>
<td colspan="3" class="font-monospace text-start">{{ coeffKgLbs }}</td>
</tr>
<tr class="warn">
<th class="first">Coefficient pied/mètre</th>
<th class="text-nowrap pe-4">Coefficient pied/mètre</th>
<td colspan="3" class="font-monospace text-start">{{ coeffFtM }}</td>
</tr>
<tr class="warn">
<th class="first">Formule charge alaire</th>
<th class="text-nowrap pe-4">Formule charge alaire</th>
<td colspan="3" class="font-monospace text-start">
( ( {{ inputs.weight }} + {{ inputs.gear }} ) * {{ coeffKgLbs }} ) /
{{ inputs.current }} = {{ info.charges.current | number : '1.5' }}<br />
@@ -129,7 +128,7 @@
<table class="table table-striped table-dark table-hover mat-elevation-z2 mb-1">
<thead>
<tr class="warn">
<th class="first"></th>
<th class="text-nowrap pe-4"></th>
@for (header of sizesHeader; track header; let index = $index) {
<th class="pl-3 pr-3 {{ tableHeader[(index+1)].active }}">{{ header.name }}</th>
}
@@ -137,26 +136,26 @@
</thead>
<tbody>
<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) {
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td>
}
</tr>
<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) {
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td>
}
</tr>
<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) {
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">
{{ charge | number : '1.2-3' }}</td>
}
</tr>
<tr class="warn">
<th class="first">Taille de voile en m<sup>2</sup></th>
<th class="text-nowrap pe-4">Taille de voile en m<sup>2</sup></th>
@for (value of sizesValuesMeter; track value; let index = $index) {
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">
{{ value | number : '1.2-2' }}</td>
@@ -175,7 +174,7 @@
class="table table-striped table-dark table-hover mat-elevation-z2">
<!-- weight column -->
<ng-container matColumnDef="weight">
<th mat-header-cell *matHeaderCellDef mat-sort-header class="first {{tableHeader[0].active}}">
<th mat-header-cell *matHeaderCellDef mat-sort-header class="text-nowrap pe-4 {{tableHeader[0].active}}">
{{tableHeader[0].name}} </th>
<th mat-cell *matCellDef="let element;" class="text-end ps-3 pe-4 fs-6" role="button"
(click)="setCurrentWeight(element.weight)">
@@ -255,5 +254,4 @@
</div>
</mat-card-content>
</mat-card>
</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 { CanopyComponent } from './canopy.component';
import { CanopiesComponent } from './canopies.component';
describe('CanopyComponent', () => {
let component: CanopyComponent;
let fixture: ComponentFixture<CanopyComponent>;
describe('CanopiesComponent', () => {
let component: CanopiesComponent;
let fixture: ComponentFixture<CanopiesComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CanopyComponent]
imports: [CanopiesComponent]
})
.compileComponents();
fixture = TestBed.createComponent(CanopyComponent);
fixture = TestBed.createComponent(CanopiesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -20,7 +20,7 @@ import { UtilitiesService } from 'src/app/core/services';
import { CanopyBySize, CanopyByYear, CanopyModelBySize, CanopyModelByYear } from 'src/app/core/models';
@Component({
selector: 'app-canopy',
selector: 'app-canopies',
standalone: true,
imports: [
DatePipe,
@@ -28,8 +28,8 @@ import { CanopyBySize, CanopyByYear, CanopyModelBySize, CanopyModelByYear } from
MatGridListModule, MatIconModule, MatMenuModule,
HistoryTableComponent, BarsChartComponent, PieChartComponent
],
templateUrl: './canopy.component.html',
styleUrl: './canopy.component.scss',
templateUrl: './canopies.component.html',
styleUrl: './canopies.component.scss',
animations: [
trigger('flyInOut', [
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 _canopiesModelBySize!: Array<CanopyModelBySize>;
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>
<span [hidden]="!lastJump.numero" class="me-2 fst-italic0 fs-6">
<h1 class="mb-1">{{title}}</h1>
<span [hidden]="!lastJump.numero" class="me-2 fst-italic0 fs-6">
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
</span>
<mat-divider class="my-3"></mat-divider>
<ng-container *appShowAuthed="true">
</span>
<mat-divider class="my-3"></mat-divider>
<ng-container *appShowAuthed="true">
<div class="row">
<div class="col-xs-12">
<app-jumps-by-month></app-jumps-by-month>
@@ -12,15 +11,15 @@
</div>
<div class="row mt-sm-2 mt-lg-3">
<div class="col-md-4 col-sm-12">
<app-drop-zones></app-drop-zones>
<app-dropzones-pie></app-dropzones-pie>
</div>
<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 class="row mt-sm-2 mt-lg-3">
<div class="col-md-4 col-sm-12">
<app-aeronefs></app-aeronefs>
<app-aeronefs-pie></app-aeronefs-pie>
</div>
<div class="col-md-8 col-sm-12">
<app-aeronefs-bar></app-aeronefs-bar>
@@ -28,11 +27,10 @@
</div>
<div class="row mt-sm-2 mt-lg-3">
<div class="col-md-6 col-sm-12">
<app-canopy-models></app-canopy-models>
<app-canopies-models></app-canopies-models>
</div>
<div class="col-md-6 col-sm-12">
<app-canopy-sizes></app-canopy-sizes>
<app-canopies-sizes></app-canopies-sizes>
</div>
</div>
</ng-container>
</div>
</ng-container>
@@ -12,9 +12,9 @@ import { Aeronef, AeronefByImat, Canopy, CanopyModelBySize, DropZone, DropZoneBy
import { JumpsService, UserService } from 'src/app/core/services';
import { ShowAuthedDirective } from 'src/app/components/shared/show-authed.directive';
import {
AeronefsComponent, AeronefsBarComponent,
CanopyModelsComponent, CanopySizesComponent,
DropZonesComponent, DropZonesBarComponent,
AeronefsPieComponent, AeronefsBarComponent,
CanopiesModelsComponent, CanopiesSizesComponent,
DropzonesPieComponent, DropzonesBarComponent,
JumpsByMonthComponent
} from 'src/app/components/shared/dashboard-components';
@@ -22,9 +22,9 @@ import {
standalone: true,
imports: [
DatePipe, ShowAuthedDirective, MatDividerModule,
AeronefsComponent, AeronefsBarComponent,
CanopyModelsComponent, CanopySizesComponent,
DropZonesComponent, DropZonesBarComponent,
AeronefsPieComponent, AeronefsBarComponent,
CanopiesModelsComponent, CanopiesSizesComponent,
DropzonesPieComponent, DropzonesBarComponent,
JumpsByMonthComponent
],
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 { DropzoneComponent } from './dropzone.component';
import { DropzonesComponent } from './dropzones.component';
describe('DropzoneComponent', () => {
let component: DropzoneComponent;
let fixture: ComponentFixture<DropzoneComponent>;
describe('DropzonesComponent', () => {
let component: DropzonesComponent;
let fixture: ComponentFixture<DropzonesComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DropzoneComponent]
imports: [DropzonesComponent]
})
.compileComponents();
fixture = TestBed.createComponent(DropzoneComponent);
fixture = TestBed.createComponent(DropzonesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -21,7 +21,7 @@ import { DropZoneByOaci, DropZoneByYear, DropZonesPageData, Jump } from 'src/app
import { UtilitiesService } from 'src/app/core/services';
@Component({
selector: 'app-dropzone',
selector: 'app-dropzones',
standalone: true,
imports: [
DatePipe,
@@ -30,8 +30,8 @@ import { UtilitiesService } from 'src/app/core/services';
//ChartistModule,
HistoryTableComponent, BarsChartComponent, PieChartComponent
],
templateUrl: './dropzone.component.html',
styleUrl: './dropzone.component.scss',
templateUrl: './dropzones.component.html',
styleUrl: './dropzones.component.scss',
animations: [
trigger('flyInOut', [
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 _lastjump: Subscription = new Subscription();
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 lastJump: Jump = {} as Jump;
public destroyRef = inject(DestroyRef);
public now = new Date();
public isJumpDay = false;
constructor(
private _jumpsService: JumpsService
@@ -50,6 +52,12 @@ export class HomeComponent implements OnInit {
this._lastjump$ = this._jumpsService.getLastJump().pipe(takeUntilDestroyed(this.destroyRef));
this._lastjump = this._lastjump$.subscribe((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 './aeronef/aeronef.component';
export * from './aeronefs/aeronefs.component';
export * from './calculator/calculator.component';
export * from './canopy/canopy.component';
export * from './canopies/canopies.component';
export * from './dashboard/dashboard.component';
export * from './dropzone/dropzone.component';
export * from './dropzones/dropzones.component';
export * from './home/home.component';
export * from './jump/jump.component';
export * from './jumps/jumps.component';
export * from './logbook/logbook.component';
export * from './profile/profile.component';
export * from './qcm/qcm.component';
+256 -105
View File
@@ -1,132 +1,283 @@
<div @flyInOut>
<div class="content">
<div class="d-flex">
<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>
<h1 class="mb-1">
{{title}}
<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>
</div>
<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">
<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>
</button>
<mat-menu #menuAeronef="matMenu">
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
<mat-menu #menuJump="matMenu">
@if (jump.x2data) {
<button mat-menu-item>
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
<span>{{ menuitem.name }}</span>
<mat-icon fontIcon="language" color="accent"></mat-icon>
<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>
}
</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-2">
@if(displayCharts) {
</div>
<mat-divider class="my-3"></mat-divider>
<mat-card>
<mat-card-content>
<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 class="col-lg-6 col-md-12">
<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 class="col-xxl-6 col-xs-12">
<app-bars-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor" [legend]="true"></app-bars-chart>
<div class="col-lg-6 col-md-12">
<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>
</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 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>
</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="speed" class="me-1"></mat-icon> Vitesse en chute</h5>
</div>
<div class="col-sm-6 hidden-xs text-center">
<h6>Verticale</h6>
</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.freeFall.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.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>Vitesse maximale</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>Vitesse moyenne</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>
</mat-card-content>
</mat-card>
<mat-card class="mt-3">
<mat-card-header>
<mat-card-title>Types de sauts</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-2">
@if(displayCharts) {
<div class="row">
<div class="col-xs-12">
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRowCumulated" [colors]="seriesTypesColor" [legend]="true"></app-line-chart>
<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="speed" class="me-1"></mat-icon> Vitesse sous voile</h5>
</div>
<div class="col-sm-6 hidden-xs text-center">
<h6>Verticale</h6>
</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>
<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-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 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 fixture: ComponentFixture<JumpComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [JumpComponent]
});
})
.compileComponents();
fixture = TestBed.createComponent(JumpComponent);
component = fixture.componentInstance;
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 { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute } from '@angular/router';
import { CommonModule } from '@angular/common';
import { DatePipe, DecimalPipe } from '@angular/common';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { 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';
import { ListErrorsComponent, MenuItems } from 'src/app/components/shared';
import { Errors, Jump, JumpPageData } from 'src/app/core/models';
@Component({
selector: 'app-jump',
standalone: true,
imports: [
CommonModule,
MatButtonModule, MatCardModule, MatDividerModule,
MatExpansionModule, MatIconModule, MatMenuModule,
BarsChartComponent, CircleChartComponent, LineChartComponent, LineAreaChartComponent
DatePipe, DecimalPipe, RouterLink,
MatButtonModule, MatCardModule,
MatDividerModule, MatIconModule, MatMenuModule,
ListErrorsComponent
],
templateUrl: './jump.component.html',
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)
])
])
]
styleUrl: './jump.component.scss'
})
export class JumpComponent 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 title: string = '';
public errors: Errors = { errors: {} };
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[] = [];
public jump: Jump = {} as Jump;
public prevSlug: string = '';
public nextSlug: string = '';
constructor(
private route: ActivatedRoute,
private _utilitiesService: UtilitiesService,
public menuItems: MenuItems
) { }
ngOnInit() {
const data$: Observable<{ jumpsPageData: JumpsPageData }> = this.route.data as Observable<{ jumpsPageData: JumpsPageData }>;
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { jumpsPageData: JumpsPageData }) => {
const pageData: JumpsPageData = data.jumpsPageData;
this.lastJump = pageData.lastjump;
const currentYear: number = new Date().getFullYear();
const types: Array<string> = ['FF', 'Solo', 'VR', 'Wingsuit'];
const verticals: Array<string> = ['Chutas', 'Head Up', 'Head Down'];
const others: Array<string> = ['Track', 'Trace'];
const modules: Array<string> = [...verticals, ...others];
this.min = pageData.jumpsByYears[0].year;
this.max = pageData.jumpsByYears[(pageData.jumpsByYears.length-1)].year;
this.seriesHeader = this._utilitiesService.getMonthsList();
const values: Array<number> = this.seriesHeader.map(() => 0);
this.seriesColTotal = [...values];
this.seriesColTotalClosed = [...values];
this.seriesColTotalLastYears = [...values];
this.seriesName = pageData.jumpsByYears.map((row: JumpYears) => {
this.seriesRow.push([...values]);
return row.year.toString();
});
this._jumpsByDate = pageData.jumpsByDate.map((row: JumpByDate) => {
this.seriesRow[(row.year-this.min)][(row.month-1)] = row.count;
this.seriesColTotal[(row.month-1)] += row.count;
if (row.year < currentYear) {
this.seriesColTotalClosed[(row.month-1)] += row.count;
}
if (row.year >= (currentYear-3) && row.year < currentYear) {
this.seriesColTotalLastYears[(row.month-1)] += row.count;
}
return row;
});
this.seriesRow.forEach((row: number[]) => {
const total = row.reduce((partialSum, a) => partialSum + a, 0);
this.seriesRowTotal.push(total);
});
this.seriesColTotalClosed.forEach((row: number) => {
const value: number = (row/(this.seriesName.length-2)); // -2 pour ne pas compter la première année
this.seriesRowAvg.push(parseInt(value.toFixed(0)));
});
this.seriesColTotalLastYears.forEach((row: number) => {
const value: number = (row/3); // Les 3 dernières années
this.seriesRowAvgLastYears.push(parseInt(value.toFixed(0)));
});
this.grandAvg = this.seriesRowAvg.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
this.grandAvgLastYears = this.seriesRowAvgLastYears.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
this.grandTotal = this.seriesColTotal.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
pageData.jumpsByCategory.forEach((row: JumpByCategorie) => {
if (types.indexOf(row.categorie) !== -1) {
this._jumpsByCategorie.push(row);
}
});
//const focus: Array<string> = ['FF', 'Solo']; // ['FF']
let verticalCount:number = 0;
let otherCount:number = 0;
pageData.jumpsByModule.forEach((row: JumpByModule) => {
//if (focus.indexOf(row.categorie) !== -1 && modules.indexOf(row.module) !== -1) {
if (modules.indexOf(row.module) !== -1) {
if (verticals.indexOf(row.module) !== -1) {
verticalCount += row.count;
} else {
otherCount += row.count;
}
}
});
if (verticalCount > 0) {
const row:JumpByModule = {
categorie: 'FF',
module: 'Vertical',
count: verticalCount
}
this._jumpsByModule.push(row);
}
if (otherCount > 0) {
const row:JumpByModule = {
categorie: 'FF',
module: 'Track/Trace',
count: otherCount
}
this._jumpsByModule.push(row);
}
for (let year = 0; year < pageData.jumpsByYears.length; year++) {
for (let index = 0; index < 12; index++) {
const month: string = ((index+1) < 10) ? `0${(index+1)}` : `${(index+1)}`;
this.seriesTypesHeader.push(`${month}-${pageData.jumpsByYears[year].year}`);
}
}
const emptyValues: Array<number> = this.seriesTypesHeader.map(() => 0);
this.seriesTypesName = types;
this.seriesTypesName.forEach(() => {
this.seriesTypesRow.push([...emptyValues]);
});
pageData.jumpsByDateByModule.forEach((row: JumpByDateByModule) => {
const indexOf: number = this.seriesTypesName.indexOf(row.categorie);
if (indexOf !== -1) {
this.seriesTypesRow[indexOf][(((row.year-this.min)*12)+row.month-1)] += row.count;
}
});
this.seriesTypesRowCumulated = this.seriesTypesRow.map((row: Array<number>) => {
let accumulated: number = 0;
return row.map((value: number) => {
accumulated += value;
return accumulated;
});
});
this.seriesTypesHeader = this.seriesTypesHeader.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
this.seriesTypesRow = this.seriesTypesRow.map((row: Array<number>) => {
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
});
this.seriesTypesRowCumulated = this.seriesTypesRowCumulated.map((row: Array<number>) => {
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
const data$: Observable<{ pageData: JumpPageData }> = this.route.data as Observable<{ pageData: JumpPageData }>;
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { pageData: JumpPageData }) => {
this.jump = data.pageData.jump;
this.prevSlug = data.pageData.prevJump?.slug;
this.nextSlug = data.pageData.nextJump?.slug;
this.title = `Saut n° ${this.jump.numero} `;
});
/*
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;
const data$: Observable<Jump> = this.route.data as Observable<Jump>;
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: Jump) => {
this.jump = data;
console.log('jump : ', this.jump);
});
*/
}
ngOnDestroy() {
this._data.unsubscribe();
}
public getCategories(): Array<JumpByCategorie> {
return this._jumpsByCategorie;
formatDuration(duration: number): string {
const start = new Date(0);
start.setSeconds(duration);
return start.toISOString().substring(14, 19);
}
public getModules(): Array<JumpByModule> {
return this._jumpsByModule;
}
public getModuleColor(index: number): number {
return (this._jumpsByCategorie.length + index);
private _resetErrors(): void {
this.errors = { errors: {} };
}
}
@@ -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">
<mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon>
ju_solide
{{ lastJump.author.username }}
</span>
</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">
<mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon>
ju_solide
{{ jump.author.username }}
</span>
</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">
Saut n°{{jump.numero}}<span class="flex-spacer"></span>
<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>
<mat-icon fontIcon="close" class="pointer" (click)="closeDialog()"></mat-icon>
</h2>
<mat-dialog-content>
<div class="container">
<h5 class="border-bottom text-light-info border-light-subtle pb-2 mb-3">Le {{ jump.date | date: 'dd/MM/yyyy' }} à {{ jump.lieu }} :</h5>
<div class="row mb-3">
<div class="d-flex border-bottom border-light-subtle text-light-info pb-2 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">
<dl class="dl-horizontal">
<dt class="text-cyan fw-normal">Lieu:</dt>
<dd>{{jump.lieu}}</dd>
<dt class="text-cyan fw-normal">Oaci:</dt>
<dd>{{jump.oaci}}</dd>
<dt class="text-cyan fw-normal">Aeronef:</dt>
<dd>{{jump.aeronef}}</dd>
<dt class="text-cyan fw-normal">Imat:</dt>
<dd>{{jump.imat}}</dd>
<dt class="text-cyan fw-normal">Voile:</dt>
<dd>{{jump.voile}}</dd>
<dt class="text-cyan fw-normal">Taille:</dt>
<dd>{{jump.taille}} ft<sup>2</sup></dd>
<dt class="text-cyan fw-normal">Participants:</dt>
<dd>{{jump.participants}}</dd>
<dt class="text-cyan fw-normal">Sautants:</dt>
<dd>
<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 class="col-lg-6 col-md-12">
<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>
<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) {
<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>
@@ -32,35 +72,140 @@
}
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
ju_solide
{{ jump.author.username }}
</span>
</dd>
</dl>
</div>
<div class="col-lg-6 col-md-12">
<dl class="dl-horizontal">
<dt class="text-cyan fw-normal">Hauteur:</dt>
<dd>{{jump.hauteur}} m</dd>
<dt class="text-cyan fw-normal">Deploiement:</dt>
<dd>{{jump.deploiement}} m</dd>
<dt class="text-cyan fw-normal">Categorie:</dt>
<dd>{{jump.categorie}}</dd>
<dt class="text-cyan fw-normal">Module:</dt>
<dd>{{jump.module}}</dd>
<dt class="text-cyan fw-normal">Accessoires:</dt>
<dd>{{jump.accessoires}}</dd>
<dt class="text-cyan fw-normal">Zone:</dt>
<dd>{{jump.zone}}</dd>
<dt class="text-cyan fw-normal">Dossier media:</dt>
<dd>{{jump.dossier}}</dd>
<dt class="text-cyan fw-normal">Video:</dt>
<dd>{{jump.video}}</dd>
<dt class="text-cyan fw-normal">Programme:</dt>
<dd>{{jump.programme}}</dd>
</dl>
@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-1"></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>
<div class="row my-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 en chute</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.freeFall.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.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>
<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>
</mat-dialog-content>
<mat-dialog-actions>
@@ -1,5 +1,5 @@
import { Component, Inject } from '@angular/core';
import { DatePipe } from '@angular/common';
import { DatePipe, DecimalPipe } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
@@ -12,11 +12,9 @@ import { Jump } from 'src/app/core/models';
templateUrl: 'jump-view.dialog.html',
standalone: true,
imports: [
DatePipe,
MatButtonModule,
MatDialogModule,
MatDividerModule,
MatIconModule
DatePipe, DecimalPipe,
MatButtonModule, MatDialogModule,
MatDividerModule, MatIconModule
]
})
export class JumpViewDialogComponent {
@@ -30,4 +28,10 @@ export class JumpViewDialogComponent {
// close the dialog without result
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 class="content">
<div class="d-flex">
<div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
@@ -103,5 +102,4 @@
</app-jump-table>
</mat-card-content>
</mat-card>
</div>
</div>
@@ -12,14 +12,20 @@
</div>
</div>
</div>
<div class="profile-page content">
<h1>{{title}}</h1>
<mat-divider></mat-divider>
<div class="profile-page">
<div class="d-flex">
<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>
<mat-card class="mt-4 bg-light text-black scaled">
<mat-card-content>
<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>
<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>
@@ -4,6 +4,7 @@
.banner {
background-color: var.$sidebar-footer;
padding: 1.6rem 0 1.3rem 0;
margin: -1.5rem -1.5rem 1.5rem -1.5rem;
.user-img {
width: 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">
<h1 class="mb-1">
{{ title }}
@@ -21,12 +20,22 @@
}
</mat-menu>
</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>
<mat-divider class="my-3"></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors>
<mat-card [hidden]="!questions.length">
<mat-card-content class="mt-4">
<form [formGroup]="qcmForm">
<mat-card-content>
<form [formGroup]="qcmForm" (ngSubmit)="onSubmit()">
@for (category of qcm.categories; track category; let index = $index) {
<mat-accordion>
<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) {
<div class="mb-3" [formGroupName]="index">
<label class="fs-5 fw-medium mb-2" for="radio-group-{{ category.num }}-{{ question.num }}">{{ question.num }} - {{ question.libelle }}</label>
<mat-radio-group id="radio-group-{{ category.num }}-{{ question.num }}" class="d-flex flex-column" (change)="onChange()">
<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) {
<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>
</div>
@@ -56,5 +65,17 @@
}
</form>
</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>
</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 { 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 { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
@@ -8,12 +8,15 @@ 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 { MatProgressBarModule } from '@angular/material/progress-bar';
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 { ListErrorsComponent, } from 'src/app/components/shared';
import { Errors, Qcm, User } from 'src/app/core/models';
import { ListErrorsComponent } from 'src/app/components/shared';
import { Errors, Qcm, QcmCategory, QcmQuestion, QcmQuestionState, User } from 'src/app/core/models';
import { QcmService, UserService } from 'src/app/core/services';
import data from 'src/qcm-bpa.json';
@@ -23,7 +26,8 @@ import data from 'src/qcm-bpa.json';
imports: [
FormsModule, ReactiveFormsModule,
MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule,
MatIconModule, MatMenuModule, MatRadioModule,
MatIconModule, MatMenuModule, MatProgressBarModule, MatRadioModule,
MatSelectModule,
ListErrorsComponent
],
templateUrl: './qcm.component.html',
@@ -32,6 +36,8 @@ import data from 'src/qcm-bpa.json';
export class QcmComponent implements OnInit, OnDestroy {
private _currentUser: Subscription = new Subscription();
private _data: Subscription = new Subscription();
private _timer: Subscription = new Subscription();
private _refresh = 1000;
public qcm: Qcm = {} as Qcm;
public currentUser: User = {} as User;
public canModify = false;
@@ -41,7 +47,13 @@ export class QcmComponent implements OnInit, OnDestroy {
public destroyRef = inject(DestroyRef);
public expandedIndex = 0;
public questionsCount = 0;
public answers: Array<QcmQuestionState> = [];
public qcmForm!: FormGroup;
public loading = true;
public totalAnswer = 0;
public timeProgress = 0;
public timeProgressColor = 'primary';
public timeMax = (60*45);
constructor(
private route: ActivatedRoute,
@@ -56,15 +68,16 @@ export class QcmComponent implements OnInit, OnDestroy {
}
get questions(): FormArray {
return <FormArray>this.qcmForm.controls['questions'];
return this.qcmForm.controls['questions'] as FormArray;
}
ngOnInit() {
//this.importQuestions();
//this.importChoices();
//this._importQuestions();
//this._importChoices();
// Retreive the prefetched QCM
const data$: Observable<{ qcm: Qcm }> = this.route.data as Observable<{ qcm: Qcm }>;
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { qcm: Qcm }) => {
this.questionsCount = 0;
this.qcm = data.qcm;
if (this.qcm.name === 'bpa') {
this.title = 'QCM du BPA';
@@ -77,13 +90,7 @@ export class QcmComponent implements OnInit, OnDestroy {
this.qcm.categories.forEach(category => {
this.questionsCount += category.questions.length;
category.questions.forEach(question => {
this.questions.push(
this.fb.group({
id: new FormControl({ value: question.num, disabled: false }),
choice: ['']
//choice: new FormControl({ value: '', disabled: false })
})
);
this.addQuestion(question);
});
});
});
@@ -93,6 +100,7 @@ export class QcmComponent implements OnInit, OnDestroy {
this.currentUser = userData;
this.canModify = userData.role === 'Admin';
});
this.loading = false;
}
ngOnDestroy() {
@@ -100,15 +108,64 @@ export class QcmComponent implements OnInit, OnDestroy {
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) {
this.expandedIndex = index;
}
onChange() {
//this.isDisabled = true;
onChange(category: QcmCategory, question: QcmQuestion, value: unknown) {
//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 {
data.categories.forEach((category) => {
category.questions.forEach(question => {
@@ -132,7 +189,7 @@ export class QcmComponent implements OnInit, OnDestroy {
}
}
importQuestions() {
private _importQuestions() {
try {
data.categories.forEach((category) => {
//Object.assign(this.jump, category);
@@ -154,4 +211,8 @@ export class QcmComponent implements OnInit, OnDestroy {
console.error(error);
}
}
private _resetErrors(): void {
this.errors = { errors: {} };
}
}
@@ -12,7 +12,7 @@
</div>
</div>
</div>
<div class="credentials-page content">
<div class="credentials-page">
<h1>{{title}}</h1>
<mat-divider></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors>
@@ -4,6 +4,7 @@
.banner {
background-color: var.$sidebar-footer;
padding: 1.6rem 0 1.3rem 0;
margin: -1.5rem -1.5rem 1.5rem -1.5rem;
.user-img {
width: var.$user-img-size;
height: var.$user-img-size;
@@ -12,7 +12,7 @@
</div>
</div>
</div>
<div class="settings-page content">
<div class="settings-page">
<h1>{{title}}</h1>
<mat-divider></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors>
@@ -4,6 +4,7 @@
.banner {
background-color: var.$sidebar-footer;
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);
margin-bottom: 1rem;
@@ -1,9 +1,4 @@
import {
Directive,
OnInit,
AfterViewInit,
AfterContentChecked
} from '@angular/core';
import { Directive, AfterContentChecked } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { AccordionLinkDirective } from './accordionlink.directive';
@@ -51,7 +46,7 @@ export class AccordionDirective implements AfterContentChecked {
ngAfterContentChecked(): void {
this.router.events
.pipe(filter(event => event instanceof NavigationEnd))
.subscribe(e => this.checkOpenLinks());
.subscribe(() => this.checkOpenLinks());
}
constructor(private router: Router) {
@@ -14,7 +14,7 @@ export class AccordionAnchorDirective {
}
@HostListener('click', ['$event'])
onClick(e: any) {
onClick() {
this.navlink.toggle();
}
}
@@ -15,7 +15,7 @@ import { AccordionDirective } from './accordion.directive';
})
export class AccordionLinkDirective implements OnInit, OnDestroy {
@Input()
public group: any;
public group!: string;
@HostBinding('class.selected')
@Input()
@@ -37,15 +37,15 @@ export class AccordionLinkDirective implements OnInit, OnDestroy {
this.nav = nav;
}
ngOnInit(): any {
ngOnInit() {
this.nav.addLink(this);
}
ngOnDestroy(): any {
ngOnDestroy(){
this.nav.removeGroup(this);
}
toggle(): any {
toggle() {
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';
@Component({
selector: 'app-aeronefs',
selector: 'app-aeronefs-pie',
standalone: true,
imports: [
MatBadgeModule, MatButtonModule, MatCardModule, MatDividerModule,
MatExpansionModule, MatIconModule, MatMenuModule,
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 _aeronefByYear: Subscription = new Subscription();
private _aeronefsByImat!: Array<AeronefByImat>;
@@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DropZonesComponent } from './drop-zones.component';
import { CanopiesModelsComponent } from './canopies-models.component';
describe('DropZonesComponent', () => {
let component: DropZonesComponent;
let fixture: ComponentFixture<DropZonesComponent>;
describe('CanopiesModelsComponent', () => {
let component: CanopiesModelsComponent;
let fixture: ComponentFixture<CanopiesModelsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [DropZonesComponent]
imports: [CanopiesModelsComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DropZonesComponent);
fixture = TestBed.createComponent(CanopiesModelsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -15,16 +15,16 @@ import { UtilitiesService, CanopiesService } from 'src/app/core/services';
import { CanopyModelBySize, CanopyModelByYear } from 'src/app/core/models';
@Component({
selector: 'app-canopy-models',
selector: 'app-canopies-models',
standalone: true,
imports: [
MatButtonModule, MatCardModule, MatDividerModule,
MatExpansionModule, MatIconModule, MatMenuModule,
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 _canopyByYear: Subscription = new Subscription();
private _canopiesBySize!: Array<CanopyModelBySize>;
@@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DropZonesBarComponent } from './drop-zones-bar.component';
import { CanopiesSizesComponent } from './canopies-sizes.component';
describe('DropZonesBarComponent', () => {
let component: DropZonesBarComponent;
let fixture: ComponentFixture<DropZonesBarComponent>;
describe('CanopiesSizesComponent', () => {
let component: CanopiesSizesComponent;
let fixture: ComponentFixture<CanopiesSizesComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [DropZonesBarComponent]
imports: [CanopiesSizesComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DropZonesBarComponent);
fixture = TestBed.createComponent(CanopiesSizesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -16,16 +16,16 @@ import { UtilitiesService, CanopiesService } from 'src/app/core/services';
import { CanopyBySize, CanopyByYear } from 'src/app/core/models';
@Component({
selector: 'app-canopy-sizes',
selector: 'app-canopies-sizes',
standalone: true,
imports: [
MatButtonModule, MatCardModule, MatDividerModule,
MatExpansionModule, MatIconModule, MatMenuModule,
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 _canopyByYear: Subscription = new Subscription();
private _canopiesBySize!: Array<CanopyBySize>;
@@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CanopyModelsComponent } from './canopy-models.component';
import { DropzonesBarComponent } from './dropzones-bar.component';
describe('CanopyModelsComponent', () => {
let component: CanopyModelsComponent;
let fixture: ComponentFixture<CanopyModelsComponent>;
describe('DropzonesBarComponent', () => {
let component: DropzonesBarComponent;
let fixture: ComponentFixture<DropzonesBarComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [CanopyModelsComponent]
imports: [DropzonesBarComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CanopyModelsComponent);
fixture = TestBed.createComponent(DropzonesBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -17,7 +17,7 @@ import { UtilitiesService, DropZonesService } from 'src/app/core/services';
import { DropZoneByOaci, DropZoneByYear } from 'src/app/core/models';
@Component({
selector: 'app-drop-zones-bar',
selector: 'app-dropzones-bar',
standalone: true,
imports: [
MatButtonModule, MatCardModule, MatDividerModule,
@@ -25,9 +25,9 @@ import { DropZoneByOaci, DropZoneByYear } from 'src/app/core/models';
BaseChartDirective, ChartistModule,
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 _dropzoneByYear: Subscription = new Subscription();
private _dropzonesByOaci!: Array<DropZoneByOaci>;
@@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CanopySizesComponent } from './canopy-sizes.component';
import { DropzonesPieComponent } from './dropzones-pie.component';
describe('CanopySizesComponent', () => {
let component: CanopySizesComponent;
let fixture: ComponentFixture<CanopySizesComponent>;
describe('DropzonesPieComponent', () => {
let component: DropzonesPieComponent;
let fixture: ComponentFixture<DropzonesPieComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [CanopySizesComponent]
imports: [DropzonesPieComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CanopySizesComponent);
fixture = TestBed.createComponent(DropzonesPieComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -16,16 +16,16 @@ import { UtilitiesService, DropZonesService } from 'src/app/core/services';
import { DropZoneByOaci, DropZoneByYear } from 'src/app/core/models';
@Component({
selector: 'app-drop-zones',
selector: 'app-dropzones-pie',
standalone: true,
imports: [
MatButtonModule, MatCardModule, MatDividerModule,
MatExpansionModule, MatIconModule, MatMenuModule,
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 _dropzoneByYear: Subscription = new Subscription();
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 './canopy-models/canopy-models.component';
export * from './canopy-sizes/canopy-sizes.component';
export * from './drop-zones/drop-zones.component';
export * from './drop-zones-bar/drop-zones-bar.component';
export * from './canopies-models/canopies-models.component';
export * from './canopies-sizes/canopies-sizes.component';
export * from './dropzones-pie/dropzones-pie.component';
export * from './dropzones-bar/dropzones-bar.component';
export * from './jumps-by-month/jumps-by-month.component';
@@ -74,6 +74,7 @@ export class JumpsByMonthComponent implements OnInit, OnDestroy {
}
private _loadJumpByDate() {
const minoration = 2; // -2 pour ne pas compter la première année
this.seriesHeader = this._utilitiesService.getMonthsList();
const values: Array<number> = this.seriesHeader.map(() => 0);
this.seriesColTotal = [...values];
@@ -111,7 +112,7 @@ export class JumpsByMonthComponent implements OnInit, OnDestroy {
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
const value: number = (row/(this.seriesName.length-minoration));
this.seriesRowAvg.push(parseInt(value.toFixed(0)));
});
this.seriesColTotalLastYears.forEach((row: number) => {
@@ -3,7 +3,7 @@
<canvas baseChart class="w-100 h-auto"
[data]="chartConfig.barChartData"
[options]="chartConfig.barChartOptions"
[plugins]="chartConfig.barChartPlugins"
[plugins]="chartConfig.barChartPlugins!"
[legend]="chartConfig.barChartLegend"
[type]="'bar'">
</canvas>
@@ -39,7 +39,7 @@ export class BarChartComponent implements OnChanges {
}
private _loadBarChart(): void {
const data: ChartDataset<'bar'> = {
const dataset: ChartDataset<'bar'> = {
data: [...this.values],
label: this.label,
backgroundColor: this.colors.backgroundColor,
@@ -47,7 +47,7 @@ export class BarChartComponent implements OnChanges {
borderWidth: 1
};
this.chartConfig.barChartData.labels = [...this.names];
this.chartConfig.barChartData.datasets!.push(data);
this.chartConfig.barChartData.datasets!.push(dataset);
/*this.chartConfig.barChartOptions!.scales = {
x: {display: false},
y: {display: true}
@@ -3,7 +3,7 @@
<canvas baseChart
[data]="chartConfig.barChartData"
[options]="chartConfig.barChartOptions"
[plugins]="chartConfig.barChartPlugins"
[plugins]="chartConfig.barChartPlugins!"
[legend]="chartConfig.barChartLegend"
[type]="'bar'">
</canvas>
@@ -39,7 +39,7 @@ export class BarHorizontalChartComponent implements OnChanges {
}
private _loadBarChart(): void {
const data: ChartDataset<'bar'> = {
const dataset: ChartDataset<'bar'> = {
data: [...this.values],
label: this.label,
backgroundColor: this.colors.backgroundColor,
@@ -47,7 +47,7 @@ export class BarHorizontalChartComponent implements OnChanges {
borderWidth: 1
};
this.chartConfig.barChartData.labels = [...this.names];
this.chartConfig.barChartData.datasets!.push(data);
this.chartConfig.barChartData.datasets!.push(dataset);
this.chartConfig.barChartLegend = this.legend;
this.displayCharts = true;
}
@@ -3,7 +3,7 @@
<canvas baseChart class="w-100 h-auto"
[data]="chartConfig.barChartData"
[options]="chartConfig.barChartOptions"
[plugins]="chartConfig.barChartPlugins"
[plugins]="chartConfig.barChartPlugins!"
[legend]="chartConfig.barChartLegend"
[type]="'bar'">
</canvas>
@@ -34,7 +34,7 @@ export class BarsChartComponent implements OnChanges, AfterContentChecked {
@Input() legend: boolean = false;
ngOnChanges() {
//this._loadBarChart();
this._loadBarChart();
}
ngAfterContentChecked() {
@@ -63,7 +63,7 @@ export class BarsChartComponent implements OnChanges, AfterContentChecked {
}
};
this.names.forEach((label: string, index: number) => {
const data: ChartDataset<'bar'> = {
const dataset: ChartDataset<'bar'> = {
data: [...this.values[index]],
label: label,
backgroundColor: this.colors.backgroundColor[index],
@@ -71,7 +71,7 @@ export class BarsChartComponent implements OnChanges, AfterContentChecked {
borderWidth: 1,
//stack: 'Stack 0'
};
this.chartConfig.barChartData.datasets!.push(data);
this.chartConfig.barChartData.datasets!.push(dataset);
});
this.chartConfig.barChartLegend = this.legend;
this.displayCharts = true;
@@ -50,7 +50,7 @@ export class CircleChartComponent implements OnChanges {
}
/*private _loadCircleChart(): void {
const data: ChartDataset<'circle'> = {
const dataset: ChartDataset<'circle'> = {
data: [...this.values],
label: 'Nombre total de sauts',
backgroundColor: this._utilitiesService.getSeriesColors(0.8),
@@ -58,7 +58,7 @@ export class CircleChartComponent implements OnChanges {
borderWidth: 1
};
this.chartConfig.circleChartLabels = [...this.names];
this.chartConfig.circleChartDatasets!.push(data);
this.chartConfig.circleChartDatasets!.push(dataset);
this.displayCharts = true;
}*/
}
@@ -1,5 +1,5 @@
import { Component, Input, OnChanges } from '@angular/core';
import { ChartDataset } from 'chart.js';
import { ChartDataset, Point } from 'chart.js';
import { BaseChartDirective } from 'ng2-charts';
import { UtilitiesService } from 'src/app/core/services';
@@ -32,6 +32,7 @@ export class LineChartComponent implements OnChanges {
borderColor: this._utilitiesService.getSeriesColors(1)
};
@Input() legend: boolean = false;
@Input() hideZero: boolean = false;
ngOnChanges() {
this._loadLineChart();
@@ -40,8 +41,14 @@ export class LineChartComponent implements OnChanges {
private _loadLineChart(): void {
this.chartConfig.lineChartData.labels = [...this.headers];
this.names.forEach((label: string, index: number) => {
const data: ChartDataset<'line'> = {
data: [...this.values[index]],
let data: Array<(number | Point | null)> = [];
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,
backgroundColor: this.colors.backgroundColor[index],
borderColor: this.colors.borderColor[index],
@@ -49,7 +56,7 @@ export class LineChartComponent implements OnChanges {
cubicInterpolationMode: 'monotone',
tension: 0
};
this.chartConfig.lineChartData.datasets!.push(data);
this.chartConfig.lineChartData.datasets!.push(dataset);
});
this.chartConfig.lineChartLegend = this.legend;
this.displayCharts = true;
@@ -44,7 +44,7 @@ export class LineAreaChartComponent implements OnChanges {
if (index === 0) {
fill = true;
}
const data: ChartDataset<'line'> = {
const dataset: ChartDataset<'line'> = {
data: [...this.values[index]],
label: label,
backgroundColor: this.colors.backgroundColor[index],
@@ -55,7 +55,7 @@ export class LineAreaChartComponent implements OnChanges {
cubicInterpolationMode: 'monotone',
tension: 0
};
this.chartConfig.lineChartData.datasets!.push(data);
this.chartConfig.lineChartData.datasets!.push(dataset);
});
this.chartConfig.lineChartLegend = this.legend;
this.displayCharts = true;
@@ -49,7 +49,7 @@ export class PieChartComponent implements OnChanges {
}
/*private _loadDoughnutChart(): void {
const data: ChartDataset<'doughnut'> = {
const dataset: ChartDataset<'doughnut'> = {
data: [...this.values],
label: 'Nombre total de sauts',
backgroundColor: this._utilitiesService.getSeriesColors(0.8),
@@ -57,7 +57,7 @@ export class PieChartComponent implements OnChanges {
borderWidth: 1
};
this.chartConfig.doughnutChartLabels = [...this.names];
this.chartConfig.doughnutChartDatasets!.push(data);
this.chartConfig.doughnutChartDatasets!.push(dataset);
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';
@Component({
@@ -10,7 +10,7 @@ import { NgIf, NgFor } from '@angular/common';
templateUrl: './history-table.component.html',
styleUrls: []
})
export class HistoryTableComponent implements OnChanges, AfterContentChecked {
export class HistoryTableComponent implements AfterContentChecked {
public seriesColTotal: number[] = [];
public grandTotal: number = 0;
@@ -22,10 +22,10 @@ export class HistoryTableComponent implements OnChanges, AfterContentChecked {
@Input() rows: Array<Array<number>> = [];
@Input() colors: string[] = [];
ngOnChanges() {
/*ngOnChanges() {
//this._loadHistoryTable();
//console.log('onChanges');
}
}*/
ngAfterContentChecked() {
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 { RouterLink } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { Observable, Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { Errors, Jump, JumpList, JumpListConfig } from 'src/app/core/models';
import { JumpsService } from 'src/app/core/services';
@@ -39,7 +39,7 @@ export class JumpPreviewComponent implements OnDestroy {
deleteJump() {
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({
next: () => {
this.router.navigateByUrl('/');
@@ -12,7 +12,7 @@
</button>
<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]="!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>
</div>
<div class="col-md-6 col-xs-12 px-0">
@@ -224,7 +224,7 @@
</ng-container>
</td>
</ng-container> -->
<!-- Video Column -->
<!-- Video Column
<ng-container matColumnDef="video">
<th class="pl-3" mat-header-cell *matHeaderCellDef> Video </th>
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
@@ -236,6 +236,41 @@
</ngx-skeleton-loader>
}
</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>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
@@ -247,13 +282,17 @@
<mat-icon>more_vert</mat-icon>
</button>
<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)">
<mat-icon aria-label="Modifier" fontIcon="edit" color="primary" class="mini"></mat-icon>
<span class="text-primary">Modifier</span>
</button>
<button mat-menu-item (click)="openDeleteDialog(element)">
<mat-icon aria-label="Supprimer" fontIcon="delete" color="danger" class="mini"></mat-icon>
<span class="text-danger">Supprimer</span>
<mat-icon aria-label="Supprimer" fontIcon="delete" color="raspberry" class="mini"></mat-icon>
<span class="text-raspberry">Supprimer</span>
</button>
</mat-menu>
}
@@ -273,57 +312,3 @@
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true" class="accent"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" class="text-middle accent"></tr>
</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 { 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 { 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 {
private _jumps: Subscription = new Subscription();
private _subscriptions: Array<Subscription> = [];
//private _jump: Subscription = new Subscription();
private _query: JumpListConfig = { type: 'all', filters: {} };
//private _lastjump: Jump = {} as Jump;
@@ -53,13 +54,13 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
'numero', 'date', 'lieu', 'oaci', 'aeronef',
'imat', 'hauteur', 'voile', 'taille', 'categorie',
'module', 'participants', 'programme', 'accessoires',
'zone', 'video', 'actions'
'zone', 'files', 'actions'
];*/
public displayedColumns: string[] = [
'numero', 'date', 'lieu', 'aeronef',
'hauteur', 'voile', 'categorie',
'participants', 'zone', 'accessoires',
'video', 'actions'
'files', 'actions'
];
//public displayedColumns: string[] = jumpColumns.map((col) => col.key)
public columnsSchema: Array<ColumnDefinition> = jumpColumns;
@@ -115,7 +116,9 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
ngOnDestroy() {
this._jumps.unsubscribe();
//this._jump.unsubscribe();
this._subscriptions.forEach(subscription => {
subscription.unsubscribe();
});
}
ngAfterContentChecked() {
@@ -155,6 +158,9 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
const jumps$: Observable<JumpList> = this._jumpsService.query(this._query);
this._jumps = jumps$.subscribe({
next: (data: JumpList) => {
//this.updateJumps(data.jumps);
//this.linkSkydiverIdJumps();
//this.getKmlJumpFiles(data.jumps);
this.loading = false;
this.jumps = new MatTableDataSource<Jump>(data.jumps);
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) {
this.isDeleting = true;
this._jumpsService.destroy(slug).pipe(take(1))
this._subscriptions.push(
this._jumpsService.destroy(slug)
.pipe(take(1))
.subscribe(() => {
this.router.navigateByUrl('/');
});
})
);
}
openDeleteDialog(jump: Jump): void {
@@ -183,12 +327,15 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
width: '60vw',
data: jump
});
dialogRef.afterClosed().pipe(take(1))
this._subscriptions.push(
dialogRef.afterClosed()
.pipe(take(1))
.subscribe(result => {
if (result != undefined) {
this._onDelete(result.slug);
}
});
})
);
}
openEditDialog(jump: Jump): void {
@@ -196,12 +343,15 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
width: '70vw',
data: jump
});
dialogRef.afterClosed().pipe(take(1))
this._subscriptions.push(
dialogRef.afterClosed()
.pipe(take(1))
.subscribe(result => {
if (result != undefined) {
this._onEdit(result);
}
});
})
);
}
openViewDialog(jump: Jump): void {
@@ -230,6 +380,7 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
private _onDelete(slug: string): void {
try {
this.isDeleting = true;
this._subscriptions.push(
this._jumpsService.destroy(slug)
.pipe(take(1))
.subscribe({
@@ -243,7 +394,8 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
error: (err) => {
this.errors = err;
}
});
})
);
} catch (error) {
console.error(error);
}
@@ -252,6 +404,7 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
private _onEdit(jump: Jump): void {
try {
this.isSubmitting = true;
this._subscriptions.push(
this._jumpsService.update(jump)
.pipe(take(1))
.subscribe({
@@ -266,7 +419,8 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
this.errors = err;
this.isSubmitting = false;
}
});
})
);
} catch (error) {
console.error(error);
}
@@ -1,6 +1,6 @@
<mat-toolbar class="topbar bg-primary">
<div class="navbar-header">
<a class="navbar-brand" href="index.html">
<a class="navbar-brand" href="/">
<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">
<path id="Jumper_2" fill="#FFFFFF"
@@ -281,8 +281,8 @@
<app-layout-footer></app-layout-footer>
</mat-sidenav>
<mat-sidenav-content class="page-wrapper">
<div class="page-content">
<!--<div class="page-content">-->
<router-outlet><!--<app-spinner></app-spinner>--></router-outlet>
</div>
<!--</div>-->
</mat-sidenav-content>
</mat-sidenav-container>
@@ -1,5 +1,4 @@
import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { Router, RouterOutlet, RouterModule } from '@angular/router';
import { MediaMatcher } from '@angular/cdk/layout';
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[] = [
{ state: 'dashboard', type: 'link', name: 'Dashboard', icon: 'dashboard', 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: 'canopy', type: 'link', name: 'Voiles', icon: 'paragliding', desc: '' },
{ state: 'dropzone', type: 'link', name: 'Dropzones', icon: 'adjust', desc: '' },
{ state: 'jump', type: 'link', name: 'Sauts', icon: 'cloud_download', desc: '' },
{ state: 'aeronefs', type: 'link', name: 'Aéronefs', icon: 'flight', desc: '' },
{ state: 'dropzones', type: 'link', name: 'Drop Zones', icon: 'flight_takeoff', desc: '' },
{ state: 'canopies', type: 'link', name: 'Voiles', icon: 'paragliding', desc: '' },
{ state: 'jumps', type: 'link', name: 'Sauts', icon: 'filter_drama', desc: '' },
{ state: 'calculator', type: 'link', name: 'Taille de voile', icon: 'calculate', 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[] = [
{ state: 'bpa', type: 'link', name: 'BPA', icon: 'rule' },
{ state: 'c', type: 'link', name: 'Brevet C', icon: 'rule' },
@@ -63,4 +68,7 @@ export class MenuItems {
getMenuPanel(): Menu[] {
return MENUPANEL;
}
getMenuJump(): Menu[] {
return MENUPJUMP;
}
}
@@ -11,7 +11,8 @@ export class ShowAuthedDirective implements OnInit, OnDestroy {
private _auth: Subscription = new Subscription();
constructor(
private templateRef: TemplateRef<any>,
//private templateRef: TemplateRef<any>,
private templateRef: TemplateRef<never>,
private userService: UserService,
private viewContainer: ViewContainerRef
) { }
+2 -2
View File
@@ -1,10 +1,10 @@
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 { UserService } from 'src/app/core/services';
export const adminGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
export const adminGuard: CanActivateFn = () => {
const router = inject(Router);
const userService = inject(UserService);
return userService.currentUser.pipe(take(1)).pipe(map(data => {
+2 -2
View File
@@ -1,10 +1,10 @@
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 { UserService } from 'src/app/core/services';
export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
export const authGuard: CanActivateFn = () => {
const router = inject(Router);
const userService = inject(UserService);
return userService.isAuthenticated.pipe(take(1)).pipe(map(data => {
+2 -2
View File
@@ -1,11 +1,11 @@
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router';
import { CanActivateFn } from '@angular/router';
import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';
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 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 {
barChartData: ChartConfiguration<'bar'>['data'],
barChartOptions: ChartConfiguration<'bar'>['options'],
barChartPlugins: any[],
barChartPlugins: ChartConfiguration<'bar'>['plugins'],
barChartLegend: boolean
}

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