Implémentation SkydiverId API

This commit is contained in:
Rampeur
2024-05-08 20:32:49 +02:00
parent 6dece821a8
commit 7c3f0c9648
54 changed files with 2323 additions and 1395 deletions
@@ -1,5 +1,4 @@
<div @flyInOut> <div @flyInOut>
<div class="content">
<div class="d-flex"> <div class="d-flex">
<div class="flex-fill"> <div class="flex-fill">
<h1 class="mb-1">{{title}}</h1> <h1 class="mb-1">{{title}}</h1>
@@ -48,4 +47,3 @@
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</div> </div>
</div>
+1 -1
View File
@@ -1,4 +1,4 @@
<div class="container content auth-page"> <div class="container auth-page">
<div class="row"> <div class="row">
<div class="col-md-6 offset-md-3 col-xs-12"> <div class="col-md-6 offset-md-3 col-xs-12">
<h1 class="text-xs-center">{{ title }}</h1> <h1 class="text-xs-center">{{ title }}</h1>
@@ -1,5 +1,4 @@
<div @flyInOut> <div @flyInOut>
<div class="calculator-page content">
<div class="d-flex"> <div class="d-flex">
<div class="flex-fill"> <div class="flex-fill">
<h1 class="mb-1">{{title}}</h1> <h1 class="mb-1">{{title}}</h1>
@@ -56,7 +55,7 @@
<table class="table table-striped table-dark table-hover mat-elevation-z2"> <table class="table table-striped table-dark table-hover mat-elevation-z2">
<thead> <thead>
<tr class="warn"> <tr class="warn">
<th class="first"></th> <th class="text-nowrap pe-4"></th>
<th class="text-nowrap">Taille actuelle</th> <th class="text-nowrap">Taille actuelle</th>
<th class="text-nowrap">Taille min</th> <th class="text-nowrap">Taille min</th>
<th class="text-nowrap">Taille min -11%</th> <th class="text-nowrap">Taille min -11%</th>
@@ -64,34 +63,34 @@
</thead> </thead>
<tbody> <tbody>
<tr class="warn"> <tr class="warn">
<th class="first">Taille de voile en ft<sup>2</sup></th> <th class="text-nowrap pe-4">Taille de voile en ft<sup>2</sup></th>
<td class="font-monospace text-{{ info.state.color }}"> <td class="font-monospace text-{{ info.state.color }}">
{{ info.sizesFeet.current }} </td> {{ info.sizesFeet.current }} </td>
<td class="font-monospace text-success"> ● {{ info.sizesFeet.min }} </td> <td class="font-monospace text-success"> ● {{ info.sizesFeet.min }} </td>
<td class="font-monospace text-warning"> ● {{ info.sizesFeet.min11 }} </td> <td class="font-monospace text-warning"> ● {{ info.sizesFeet.min11 }} </td>
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Charge alaire</th> <th class="text-nowrap pe-4">Charge alaire</th>
<td class="font-monospace">{{ info.charges.current | number : '1.2-3' }}</td> <td class="font-monospace">{{ info.charges.current | number : '1.2-3' }}</td>
<td class="font-monospace">{{ info.charges.min | number : '1.2-3' }}</td> <td class="font-monospace">{{ info.charges.min | number : '1.2-3' }}</td>
<td class="font-monospace">{{ info.charges.min11 | number : '1.2-3' }}</td> <td class="font-monospace">{{ info.charges.min11 | number : '1.2-3' }}</td>
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Taille de voile en m<sup>2</sup></th> <th class="text-nowrap pe-4">Taille de voile en m<sup>2</sup></th>
<td class="font-monospace">{{ info.sizesMeter.current | number : '1.2-3' }}</td> <td class="font-monospace">{{ info.sizesMeter.current | number : '1.2-3' }}</td>
<td class="font-monospace">{{ info.sizesMeter.min | number : '1.2-3' }}</td> <td class="font-monospace">{{ info.sizesMeter.min | number : '1.2-3' }}</td>
<td class="font-monospace">{{ info.sizesMeter.min11 | number : '1.2-3' }}</td> <td class="font-monospace">{{ info.sizesMeter.min11 | number : '1.2-3' }}</td>
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Coefficient kilo/livres</th> <th class="text-nowrap pe-4">Coefficient kilo/livres</th>
<td colspan="3" class="font-monospace text-start">{{ coeffKgLbs }}</td> <td colspan="3" class="font-monospace text-start">{{ coeffKgLbs }}</td>
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Coefficient pied/mètre</th> <th class="text-nowrap pe-4">Coefficient pied/mètre</th>
<td colspan="3" class="font-monospace text-start">{{ coeffFtM }}</td> <td colspan="3" class="font-monospace text-start">{{ coeffFtM }}</td>
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Formule charge alaire</th> <th class="text-nowrap pe-4">Formule charge alaire</th>
<td colspan="3" class="font-monospace text-start"> <td colspan="3" class="font-monospace text-start">
( ( {{ inputs.weight }} + {{ inputs.gear }} ) * {{ coeffKgLbs }} ) / ( ( {{ inputs.weight }} + {{ inputs.gear }} ) * {{ coeffKgLbs }} ) /
{{ inputs.current }} = {{ info.charges.current | number : '1.5' }}<br /> {{ inputs.current }} = {{ info.charges.current | number : '1.5' }}<br />
@@ -129,7 +128,7 @@
<table class="table table-striped table-dark table-hover mat-elevation-z2 mb-1"> <table class="table table-striped table-dark table-hover mat-elevation-z2 mb-1">
<thead> <thead>
<tr class="warn"> <tr class="warn">
<th class="first"></th> <th class="text-nowrap pe-4"></th>
@for (header of sizesHeader; track header; let index = $index) { @for (header of sizesHeader; track header; let index = $index) {
<th class="pl-3 pr-3 {{ tableHeader[(index+1)].active }}">{{ header.name }}</th> <th class="pl-3 pr-3 {{ tableHeader[(index+1)].active }}">{{ header.name }}</th>
} }
@@ -137,26 +136,26 @@
</thead> </thead>
<tbody> <tbody>
<tr class="warn"> <tr class="warn">
<th class="first">Taille de voile en ft<sup>2</sup></th> <th class="firtext-nowrap pe-4st">Taille de voile en ft<sup>2</sup></th>
@for (value of sizesValuesFeet; track value; let index = $index) { @for (value of sizesValuesFeet; track value; let index = $index) {
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td> <td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td>
} }
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Abbattement 11%</th> <th class="text-nowrap pe-4">Abbattement 11%</th>
@for (value of sizesMinValuesFeet; track value; let index = $index) { @for (value of sizesMinValuesFeet; track value; let index = $index) {
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td> <td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">{{ value }}</td>
} }
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Charge alaire</th> <th class="text-nowrap pe-4">Charge alaire</th>
@for (charge of sizesCharges; track charge; let index = $index) { @for (charge of sizesCharges; track charge; let index = $index) {
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}"> <td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">
{{ charge | number : '1.2-3' }}</td> {{ charge | number : '1.2-3' }}</td>
} }
</tr> </tr>
<tr class="warn"> <tr class="warn">
<th class="first">Taille de voile en m<sup>2</sup></th> <th class="text-nowrap pe-4">Taille de voile en m<sup>2</sup></th>
@for (value of sizesValuesMeter; track value; let index = $index) { @for (value of sizesValuesMeter; track value; let index = $index) {
<td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}"> <td class="pl-3 text-nowrap {{ tableHeader[(index+1)].active }}">
{{ value | number : '1.2-2' }}</td> {{ value | number : '1.2-2' }}</td>
@@ -175,7 +174,7 @@
class="table table-striped table-dark table-hover mat-elevation-z2"> class="table table-striped table-dark table-hover mat-elevation-z2">
<!-- weight column --> <!-- weight column -->
<ng-container matColumnDef="weight"> <ng-container matColumnDef="weight">
<th mat-header-cell *matHeaderCellDef mat-sort-header class="first {{tableHeader[0].active}}"> <th mat-header-cell *matHeaderCellDef mat-sort-header class="text-nowrap pe-4 {{tableHeader[0].active}}">
{{tableHeader[0].name}} </th> {{tableHeader[0].name}} </th>
<th mat-cell *matCellDef="let element;" class="text-end ps-3 pe-4 fs-6" role="button" <th mat-cell *matCellDef="let element;" class="text-end ps-3 pe-4 fs-6" role="button"
(click)="setCurrentWeight(element.weight)"> (click)="setCurrentWeight(element.weight)">
@@ -256,4 +255,3 @@
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</div> </div>
</div>
@@ -1,5 +1,4 @@
<div @flyInOut> <div @flyInOut>
<div class="content">
<div class="d-flex"> <div class="d-flex">
<div class="flex-fill"> <div class="flex-fill">
<h1 class="mb-1">{{title}}</h1> <h1 class="mb-1">{{title}}</h1>
@@ -55,4 +54,3 @@
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</div> </div>
</div>
@@ -1,4 +1,3 @@
<div class="content">
<h1 class="mb-1">{{title}}</h1> <h1 class="mb-1">{{title}}</h1>
<span [hidden]="!lastJump.numero" class="me-2 fst-italic0 fs-6"> <span [hidden]="!lastJump.numero" class="me-2 fst-italic0 fs-6">
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span> Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
@@ -35,4 +34,3 @@
</div> </div>
</div> </div>
</ng-container> </ng-container>
</div>
@@ -1,5 +1,4 @@
<div @flyInOut> <div @flyInOut>
<div class="content">
<div class="d-flex"> <div class="d-flex">
<div class="flex-fill"> <div class="flex-fill">
<h1 class="mb-1">{{title}}</h1> <h1 class="mb-1">{{title}}</h1>
@@ -54,4 +53,3 @@
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</div> </div>
</div>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
/* Home */
//@use 'variable' as var;
/*
#FF41F8
#F0060B
#F0070C
#CC26D5
#7702FF
#35A2EB
#B7DDD0
#B0D9CB
#D9CAAE
#9788C7
*/
@@ -41,6 +41,8 @@ export class HomeComponent implements OnInit {
public errors: Errors = { errors: {} }; public errors: Errors = { errors: {} };
public lastJump: Jump = {} as Jump; public lastJump: Jump = {} as Jump;
public destroyRef = inject(DestroyRef); public destroyRef = inject(DestroyRef);
public now = new Date();
public isJumpDay = false;
constructor( constructor(
private _jumpsService: JumpsService private _jumpsService: JumpsService
@@ -50,6 +52,12 @@ export class HomeComponent implements OnInit {
this._lastjump$ = this._jumpsService.getLastJump().pipe(takeUntilDestroyed(this.destroyRef)); this._lastjump$ = this._jumpsService.getLastJump().pipe(takeUntilDestroyed(this.destroyRef));
this._lastjump = this._lastjump$.subscribe((jump) => { this._lastjump = this._lastjump$.subscribe((jump) => {
this.lastJump = jump; this.lastJump = jump;
this.isJumpDay = this._isToday(new Date(this.lastJump.date));
}); });
} }
private _isToday(someDate: Date) {
return someDate.getDate() == this.now.getDate() &&
someDate.getMonth() == this.now.getMonth() &&
someDate.getFullYear() == this.now.getFullYear()
}
} }
+1
View File
@@ -6,6 +6,7 @@ export * from './dashboard/dashboard.component';
export * from './dropzone/dropzone.component'; export * from './dropzone/dropzone.component';
export * from './home/home.component'; export * from './home/home.component';
export * from './jump/jump.component'; export * from './jump/jump.component';
export * from './jumps/jumps.component';
export * from './logbook/logbook.component'; export * from './logbook/logbook.component';
export * from './profile/profile.component'; export * from './profile/profile.component';
export * from './qcm/qcm.component'; export * from './qcm/qcm.component';
+251 -100
View File
@@ -1,22 +1,46 @@
<div @flyInOut>
<div class="content">
<div class="d-flex"> <div class="d-flex">
<div class="flex-fill"> <div class="flex-fill">
<h1 class="mb-1">{{title}}</h1> <h1 class="mb-1">
<span [hidden]="!lastJump.numero" class="me-2 fs-6"> {{title}}
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span> <span class="fs-3 text-accent ms-2">{{ jump.categorie }} {{ jump.module }}</span>
<span class="fs-3 text-muted ms-2">{{ jump.voile }} {{ jump.taille }}</span>
</h1>
<span class="me-2 fs-6">
Le <span class="fs-5 mx-1 text-primary">{{ jump.date | date: 'dd/MM/yyyy' }}</span>
à <span class="fs-5 mx-1 text-primary">{{ jump.lieu }} {{ jump.oaci }}</span>
@if (jump.x2data) {
depuis <span class="fs-5 mx-1 text-primary">{{ jump.aeronef }} {{ jump.imat }}</span>
}
</span> </span>
</div> </div>
<span class="flex-spacer"></span> <span class="flex-spacer"></span>
<div class="align-self-center me-4">
<button mat-button [routerLink]="['/jump', prevSlug]" aria-label="Saut précédent" color="accent">
<mat-icon fontIcon="navigate_before"></mat-icon> {{ jump.numero - 1}}
</button>
<div class="vr mx-2 fs-3 align-middle"></div>
<span class="mx-2 align-middle">{{ jump.numero }}</span>
<div class="vr mx-2 fs-3 align-middle"></div>
<button mat-button [routerLink]="['/jump', nextSlug]" aria-label="Saut suivant" color="accent">
<mat-icon fontIcon="navigate_next" iconPositionEnd></mat-icon> {{ jump.numero +1}}
</button>
</div>
<div class="align-self-center me-2"> <div class="align-self-center me-2">
<button mat-icon-button [matMenuTriggerFor]="menuAeronef" aria-label="Menu Aeronef"> <button mat-icon-button [matMenuTriggerFor]="menuJump" aria-label="Menu saut">
<mat-icon fontIcon="more_vert"></mat-icon> <mat-icon fontIcon="more_vert"></mat-icon>
</button> </button>
<mat-menu #menuAeronef="matMenu"> <mat-menu #menuJump="matMenu">
@for (menuitem of menuItems.getMenuPanel(); track menuitem) { @if (jump.x2data) {
<button mat-menu-item> <button mat-menu-item>
<mat-icon [fontIcon]="menuitem.icon"></mat-icon> <mat-icon fontIcon="language" color="accent"></mat-icon>
<span>{{ menuitem.name }}</span> <span class="text-accent fw-medium">Obtenir le KML</span>
</button>
<mat-divider></mat-divider>
}
@for (menuitem of menuItems.getMenuJump(); track menuitem) {
<button mat-menu-item>
<mat-icon [fontIcon]="menuitem.icon" [color]="menuitem.color"></mat-icon>
<span class="text-{{menuitem.color}} fw-medium">{{ menuitem.name }}</span>
</button> </button>
} }
</mat-menu> </mat-menu>
@@ -24,109 +48,236 @@
</div> </div>
<mat-divider class="my-3"></mat-divider> <mat-divider class="my-3"></mat-divider>
<mat-card> <mat-card>
<mat-card-header> <mat-card-content>
<mat-card-title>{{ subtitle }}</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-2">
@if(displayCharts) {
<div class="row"> <div class="row">
<div class="col-xxl-6 col-xs-12"> <div class="col-lg-6 col-md-12">
<app-linearea-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor"></app-linearea-chart> <dl class="dl-horizontal">
<dt class="text-light-info fw-normal">Aeronef:</dt>
<dd>{{ jump.aeronef }} {{ jump.imat }}</dd>
<dt class="text-light-info fw-normal">Voile:</dt>
<dd>{{ jump.voile }} {{ jump.taille }} ft<sup>2</sup></dd>
@if (jump.programme) {
<dt class="text-light-info fw-normal">Programme:</dt>
<dd>{{ jump.programme }}</dd>
}
</dl>
</div> </div>
<div class="col-xxl-6 col-xs-12"> <div class="col-lg-6 col-md-12">
<app-bars-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor" [legend]="true"></app-bars-chart> <dl class="dl-horizontal">
@if (!jump.x2data) {
<dt class="text-light-info fw-normal">Hauteur:</dt>
<dd>{{ jump.hauteur }} m</dd>
<dt class="text-light-info fw-normal">Déploiement:</dt>
<dd>{{ jump.deploiement }} m</dd>
}
@for (file of jump.files; track file) {
<dt class="text-light-info fw-normal">Fichier {{ file.type }}</dt>
<dd>{{ file.name }}</dd>
}
</dl>
</div> </div>
</div> </div>
</mat-card-content>
</mat-card>
@if (jump.x2data) {
<mat-card class="mt-3">
<mat-card-content>
<div class="row my-3">
<div class="col-sm-4 col-xs-12 text-center">
<h6>Hauteur</h6>
@if (jump.x2data.altitude.exit > 4200) {
<mat-icon class="align-middle fs-5 me-2" fontIcon="mood" color="primary"></mat-icon>
}
<span class="text-primary fw-medium fs-5">{{ jump.x2data.altitude.exit }} m</span>
</div>
<div class="col-sm-4 col-xs-12 text-center">
<h6 class="d-flex justify-content-center"><mat-icon fontIcon="timer" class="me-1 fs-5"></mat-icon> Durée de la chute</h6>
<span class="text-primary fw-medium fs-4">{{ formatDuration(jump.x2data.duration.freeFall) }}</span>
</div>
<div class="col-sm-4 col-xs-12 text-center">
<h6>Déploiement</h6>
@if (jump.x2data.altitude.deployment < 650) {
<mat-icon class="align-middle fs-5 me-2" fontIcon="warning" color="raspberry"></mat-icon>
<span class="text-raspberry fw-medium fs-5">{{ jump.x2data.altitude.deployment }} m</span>
}
@else if (jump.x2data.altitude.deployment < 750) {
<mat-icon class="align-middle fs-5 me-2" fontIcon="local_florist" color="warn"></mat-icon>
<span class="text-warn fw-medium fs-5">{{ jump.x2data.altitude.deployment }} m</span>
}
@else if (jump.x2data.altitude.deployment > 1000) {
<mat-icon class="align-middle fs-5 me-2" fontIcon="mood" color="primary"></mat-icon>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.altitude.deployment }} m</span>
}
</div>
</div>
<div class="row mt-4">
<div class="{{ jump.accessoires ? 'col-sm-6 ' : '' }}col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-4"><mat-icon fontIcon="people" class="me-2"></mat-icon> Participants ({{ jump.sautants.length + 1 }})</h5>
@for (sautant of jump.sautants; track sautant) {
<span class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
{{ sautant }}
</span>
}
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
{{ jump.author.username }}
</span>
</div>
@if (jump.accessoires) {
<div class="col-sm-6 col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-4"><mat-icon fontIcon="auto_awesome" class="me-2"></mat-icon> Accessoires</h5>
<span class="badge rounded-pill bg-navy-light mb-2 me-1 pe-3 py-1 fw-normal">
<mat-icon class="align-middle me-1" fontIcon="auto_awesome"></mat-icon>
{{ jump.accessoires }}
</span>
</div>
} }
<div class="row"> </div>
<div class="col-xs-12"> </mat-card-content>
<mat-divider class="mt-3 mb-0"></mat-divider> </mat-card>
<table class="table table-striped table-dark table-hover"> <mat-card class="mt-3">
<thead> <mat-card-content>
<tr> <div class="row my-3">
<th></th> <div class="col-xs-12 text-center">
<th *ngFor='let name of seriesHeader; let i=index' class="text-end"> {{ name }} </th> <h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="speed" class="me-1"></mat-icon> Vitesse en chute</h5>
<th class="text-end">Total</th> </div>
</tr> <div class="col-sm-6 hidden-xs text-center">
</thead> <h6>Verticale</h6>
<tbody> </div>
<tr *ngFor='let values of seriesRow; let i=index'> <div class="col-sm-6 hidden-xs text-center">
<th class="{{seriesColorClass[i]}} text-end"> ● {{ seriesName[i] }} </th> <h6>Horizontale</h6>
<td *ngFor='let value of values; let i=index' class="font-monospace text-end"> </div>
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span> <div class="col-sm-3 col-xs-12 text-center">
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template> <h6>Vitesse maximale</h6>
</td> <span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.vertical.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
<th class="{{seriesColorClass[i]}} font-monospace text-end">{{ seriesRowTotal[i] }}</th> </div>
</tr> <div class="col-sm-3 col-xs-12 text-center">
</tbody> <h6>Vitesse moyenne</h6>
<tfoot> <span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.vertical.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
<tr> </div>
<th class="text-end">Total</th> <div class="col-sm-3 col-xs-12 text-center">
<th *ngFor='let value of seriesColTotal; let i=index' class="font-monospace text-end"> <h6>Vitesse maximale</h6>
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span> <span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.horizontal.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template> </div>
</th> <div class="col-sm-3 col-xs-12 text-center">
<th class="font-monospace text-end">{{ grandTotal }}</th> <h6>Vitesse moyenne</h6>
</tr> <span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.horizontal.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
<tr>
<th class="text-end">Moyenne</th>
<th *ngFor='let value of seriesRowAvg; let i=index' class="font-monospace text-end">
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
</th>
<th class="font-monospace text-end">{{ grandAvg | number : '1.0-1' }}</th>
</tr>
<tr>
<th class="text-end">Moyenne <small>3 dernières années</small></th>
<th *ngFor='let value of seriesRowAvgLastYears; let i=index' class="font-monospace text-end">
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
</th>
<th class="font-monospace text-end">{{ grandAvgLastYears | number : '1.0-1' }}</th>
</tr>
</tfoot>
</table>
</div> </div>
</div> </div>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
<mat-card class="mt-3"> <mat-card class="mt-3">
<mat-card-header> <mat-card-content>
<mat-card-title>Types de sauts</mat-card-title> <div class="row my-3">
</mat-card-header> <div class="col-xs-12 text-center">
<mat-card-content class="mt-2"> <h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="speed" class="me-1"></mat-icon> Vitesse sous voile</h5>
@if(displayCharts) { </div>
<div class="row"> <div class="col-sm-6 hidden-xs text-center">
<div class="col-xs-12"> <h6>Verticale</h6>
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRowCumulated" [colors]="seriesTypesColor" [legend]="true"></app-line-chart> </div>
<div class="col-sm-6 hidden-xs text-center">
<h6>Horizontale</h6>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Vitesse maximale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.vertical.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Vitesse moyenne</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.vertical.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Vitesse maximale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.horizontal.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Vitesse moyenne</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.horizontal.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div> </div>
</div> </div>
<mat-divider class="mt-2 mb-3"></mat-divider>
<div class="row">
@for (item of getCategories(); track $index) {
<div class="col-lg-2 col-sm-4 col-xs-6">
<app-circle-chart [headers]="seriesHeader" [names]="seriesName" [values]="[item.count, (lastJump.numero-item.count)]" [color]="$index" [label]="item.categorie"></app-circle-chart>
</div>
}
@for (item of getModules(); track $index) {
<div class="col-lg-2 col-sm-4 col-xs-6">
<app-circle-chart [headers]="seriesHeader" [names]="seriesName" [values]="[item.count, (lastJump.numero-item.count)]" [color]="getModuleColor($index)" [label]="item.module"></app-circle-chart>
</div>
}
</div>
<mat-divider class="my-3"></mat-divider>
<!--
<div class="row">
<div class="col-xxl-6 col-xs-12">
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRow" [colors]="seriesTypesColor" [legend]="true"></app-line-chart>
</div>
<div class="col-xxl-6 col-xs-12">
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRowCumulated" [colors]="seriesTypesColor" [legend]="true"></app-line-chart>
</div>
</div>
-->
}
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
<mat-card class="mt-3">
<mat-card-content>
<div class="row my-3">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="square_foot" class="me-1"></mat-icon> Finesse</h5>
</div>
<div 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>
</div> </div>
</mat-card-content>
</mat-card>
<mat-card class="mt-3">
<mat-card-content>
<div class="row my-3">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="straighten" class="me-1"></mat-icon> Distance</h5>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Distance au largage</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.distance.exitFromLandingZone| number : '1.0-0' }} m</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Durée sous voile</h6>
<span class="text-primary fw-medium fs-5">{{ formatDuration(jump.x2data.duration.underCanopy) }}</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Distance totale</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.distance.totalFlying | number : '1.0-0' }} m</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Distance horizontale</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.distance.totalHorizontal | number : '1.0-0' }} m</span>
</div>
</div>
</mat-card-content>
</mat-card>
}
@else {
<mat-card class="mt-3">
<mat-card-content>
<div class="row my-3">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-4"><mat-icon fontIcon="people" class="me-1"></mat-icon> Participants ({{ jump.sautants.length + 1 }})</h5>
@for (sautant of jump.sautants; track sautant) {
<span class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
{{ sautant }}
</span>
}
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
{{ jump.author.username }}
</span>
</div>
</div>
</mat-card-content>
</mat-card>
<mat-card class="mt-3">
<mat-card-content>
<div class="d-flex justify-content-center my-3 fs-5"><mat-icon fontIcon="satellite_alt" class="me-2"></mat-icon> Aucune donnée GPS</div>
</mat-card-content>
</mat-card>
}
@@ -6,10 +6,12 @@ describe('JumpComponent', () => {
let component: JumpComponent; let component: JumpComponent;
let fixture: ComponentFixture<JumpComponent>; let fixture: ComponentFixture<JumpComponent>;
beforeEach(() => { beforeEach(async () => {
TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [JumpComponent] imports: [JumpComponent]
}); })
.compileComponents();
fixture = TestBed.createComponent(JumpComponent); fixture = TestBed.createComponent(JumpComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
+31 -208
View File
@@ -1,248 +1,71 @@
import { trigger, state, style, animate, transition } from '@angular/animations';
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core'; import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute } from '@angular/router'; import { DatePipe, DecimalPipe } from '@angular/common';
import { CommonModule } from '@angular/common'; import { ActivatedRoute, RouterLink } from '@angular/router';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider'; import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { MenuItems } from 'src/app/components/shared'; import { ListErrorsComponent, MenuItems } from 'src/app/components/shared';
import { BarsChartComponent, CircleChartComponent, LineChartComponent, LineAreaChartComponent } from 'src/app/components/shared/helpers-chart'; import { Errors, Jump, JumpPageData } from 'src/app/core/models';
import { UtilitiesService } from 'src/app/core/services';
import { Jump, JumpByCategorie, JumpByDate, JumpByDateByModule, JumpByModule, JumpsPageData, JumpYears } from 'src/app/core/models';
@Component({ @Component({
selector: 'app-jump', selector: 'app-jump',
standalone: true, standalone: true,
imports: [ imports: [
CommonModule, DatePipe, DecimalPipe, RouterLink,
MatButtonModule, MatCardModule, MatDividerModule, MatButtonModule, MatCardModule,
MatExpansionModule, MatIconModule, MatMenuModule, MatDividerModule, MatIconModule, MatMenuModule,
BarsChartComponent, CircleChartComponent, LineChartComponent, LineAreaChartComponent ListErrorsComponent
], ],
templateUrl: './jump.component.html', templateUrl: './jump.component.html',
styleUrl: './jump.component.scss', styleUrl: './jump.component.scss'
animations: [
trigger('flyInOut', [
state('in', style({ transform: 'translateX(0)' })),
transition('void => *', [
style({ transform: 'translateX(-100%)' }),
animate(200)
]),
transition('* => void', [
style({ transform: 'translateX(100%)' }),
animate(200)
])
])
]
}) })
export class JumpComponent implements OnInit, OnDestroy { export class JumpComponent implements OnInit, OnDestroy {
private _data: Subscription = new Subscription(); private _data: Subscription = new Subscription();
private _jumpsByCategorie: Array<JumpByCategorie> = []; public title: string = '';
private _jumpsByModule: Array<JumpByModule> = []; public errors: Errors = { errors: {} };
private _jumpsByDate: Array<JumpByDate> = [];
public lastJump: Jump = {} as Jump;
public title: string = 'Sauts';
public subtitle: string = 'Nombre total de sauts par mois';
public displayCharts = false;
public destroyRef = inject(DestroyRef); public destroyRef = inject(DestroyRef);
public min = 0; public jump: Jump = {} as Jump;
public max = 0; public prevSlug: string = '';
public grandAvg = 0; public nextSlug: string = '';
public grandAvgLastYears = 0;
public grandTotal = 0;
public seriesHeader: string[] = [];
public seriesName: string[] = [];
public seriesRow: Array<Array<number>> = [];
public seriesColor: {
backgroundColor: string[],
borderColor: string[]
} = {
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
};
public seriesTypesHeader: string[] = [];
public seriesTypesName: string[] = [];
public seriesTypesRow: Array<Array<number>> = [];
public seriesTypesRowCumulated: Array<Array<number>> = [];
public seriesTypesColor: {
backgroundColor: string[],
borderColor: string[]
} = {
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'pastels'),
borderColor: this._utilitiesService.getSeriesColors(1, 'pastels')
};
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
public seriesRowTotal: number[] = [];
public seriesRowAvg: number[] = [];
public seriesRowAvgLastYears: number[] = [];
public seriesColTotal: number[] = [];
public seriesColTotalClosed: number[] = [];
public seriesColTotalLastYears: number[] = [];
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private _utilitiesService: UtilitiesService,
public menuItems: MenuItems public menuItems: MenuItems
) { } ) { }
ngOnInit() { ngOnInit() {
const data$: Observable<{ jumpsPageData: JumpsPageData }> = this.route.data as Observable<{ jumpsPageData: JumpsPageData }>; const data$: Observable<{ pageData: JumpPageData }> = this.route.data as Observable<{ pageData: JumpPageData }>;
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { jumpsPageData: JumpsPageData }) => { this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { pageData: JumpPageData }) => {
const pageData: JumpsPageData = data.jumpsPageData; this.jump = data.pageData.jump;
this.lastJump = pageData.lastjump; this.prevSlug = data.pageData.prevJump?.slug;
const currentYear: number = new Date().getFullYear(); this.nextSlug = data.pageData.nextJump?.slug;
const types: Array<string> = ['FF', 'Solo', 'VR', 'Wingsuit']; this.title = `Saut n° ${this.jump.numero} `;
const verticals: Array<string> = ['Chutas', 'Head Up', 'Head Down'];
const others: Array<string> = ['Track', 'Trace'];
const modules: Array<string> = [...verticals, ...others];
this.min = pageData.jumpsByYears[0].year;
this.max = pageData.jumpsByYears[(pageData.jumpsByYears.length-1)].year;
this.seriesHeader = this._utilitiesService.getMonthsList();
const values: Array<number> = this.seriesHeader.map(() => 0);
this.seriesColTotal = [...values];
this.seriesColTotalClosed = [...values];
this.seriesColTotalLastYears = [...values];
this.seriesName = pageData.jumpsByYears.map((row: JumpYears) => {
this.seriesRow.push([...values]);
return row.year.toString();
});
this._jumpsByDate = pageData.jumpsByDate.map((row: JumpByDate) => {
this.seriesRow[(row.year-this.min)][(row.month-1)] = row.count;
this.seriesColTotal[(row.month-1)] += row.count;
if (row.year < currentYear) {
this.seriesColTotalClosed[(row.month-1)] += row.count;
}
if (row.year >= (currentYear-3) && row.year < currentYear) {
this.seriesColTotalLastYears[(row.month-1)] += row.count;
}
return row;
});
this.seriesRow.forEach((row: number[]) => {
const total = row.reduce((partialSum, a) => partialSum + a, 0);
this.seriesRowTotal.push(total);
});
this.seriesColTotalClosed.forEach((row: number) => {
const value: number = (row/(this.seriesName.length-2)); // -2 pour ne pas compter la première année
this.seriesRowAvg.push(parseInt(value.toFixed(0)));
});
this.seriesColTotalLastYears.forEach((row: number) => {
const value: number = (row/3); // Les 3 dernières années
this.seriesRowAvgLastYears.push(parseInt(value.toFixed(0)));
});
this.grandAvg = this.seriesRowAvg.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
this.grandAvgLastYears = this.seriesRowAvgLastYears.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
this.grandTotal = this.seriesColTotal.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
pageData.jumpsByCategory.forEach((row: JumpByCategorie) => {
if (types.indexOf(row.categorie) !== -1) {
this._jumpsByCategorie.push(row);
}
});
//const focus: Array<string> = ['FF', 'Solo']; // ['FF']
let verticalCount:number = 0;
let otherCount:number = 0;
pageData.jumpsByModule.forEach((row: JumpByModule) => {
//if (focus.indexOf(row.categorie) !== -1 && modules.indexOf(row.module) !== -1) {
if (modules.indexOf(row.module) !== -1) {
if (verticals.indexOf(row.module) !== -1) {
verticalCount += row.count;
} else {
otherCount += row.count;
}
}
});
if (verticalCount > 0) {
const row:JumpByModule = {
categorie: 'FF',
module: 'Vertical',
count: verticalCount
}
this._jumpsByModule.push(row);
}
if (otherCount > 0) {
const row:JumpByModule = {
categorie: 'FF',
module: 'Track/Trace',
count: otherCount
}
this._jumpsByModule.push(row);
}
for (let year = 0; year < pageData.jumpsByYears.length; year++) {
for (let index = 0; index < 12; index++) {
const month: string = ((index+1) < 10) ? `0${(index+1)}` : `${(index+1)}`;
this.seriesTypesHeader.push(`${month}-${pageData.jumpsByYears[year].year}`);
}
}
const emptyValues: Array<number> = this.seriesTypesHeader.map(() => 0);
this.seriesTypesName = types;
this.seriesTypesName.forEach(() => {
this.seriesTypesRow.push([...emptyValues]);
});
pageData.jumpsByDateByModule.forEach((row: JumpByDateByModule) => {
const indexOf: number = this.seriesTypesName.indexOf(row.categorie);
if (indexOf !== -1) {
this.seriesTypesRow[indexOf][(((row.year-this.min)*12)+row.month-1)] += row.count;
}
});
this.seriesTypesRowCumulated = this.seriesTypesRow.map((row: Array<number>) => {
let accumulated: number = 0;
return row.map((value: number) => {
accumulated += value;
return accumulated;
});
});
this.seriesTypesHeader = this.seriesTypesHeader.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
this.seriesTypesRow = this.seriesTypesRow.map((row: Array<number>) => {
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
});
this.seriesTypesRowCumulated = this.seriesTypesRowCumulated.map((row: Array<number>) => {
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
}); });
/* /*
console.log('this.min', this.min); const data$: Observable<Jump> = this.route.data as Observable<Jump>;
console.log('this.max', this.max); this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: Jump) => {
console.log('this.lastJump', this.lastJump); this.jump = data;
console.log('this.seriesHeader', this.seriesHeader); console.log('jump : ', this.jump);
console.log('this.seriesName', this.seriesName);
console.log('this.seriesRow', this.seriesRow);
console.log('this.seriesRowTotal', this.seriesRowTotal);
console.log('this.seriesRowAvg', this.seriesRowAvg);
console.log('this.seriesRowAvgLastYears', this.seriesRowAvgLastYears);
console.log('this._jumpsByCategorie', this._jumpsByCategorie);
console.log('this._jumpsByModule', this._jumpsByModule);
console.log('this.seriesTypesHeader', this.seriesTypesHeader);
console.log('this.seriesTypesName', this.seriesTypesName);
console.log('this.seriesTypesRow', this.seriesTypesRow);
console.log('this.seriesTypesRowCumulated', this.seriesTypesRowCumulated);
*/
this.displayCharts = true;
}); });
*/
} }
ngOnDestroy() { ngOnDestroy() {
this._data.unsubscribe(); this._data.unsubscribe();
} }
public getCategories(): Array<JumpByCategorie> { formatDuration(duration: number): string {
return this._jumpsByCategorie; const start = new Date(0);
start.setSeconds(duration);
return start.toISOString().substring(14, 19);
} }
public getModules(): Array<JumpByModule> { private _resetErrors(): void {
return this._jumpsByModule; this.errors = { errors: {} };
}
public getModuleColor(index: number): number {
return (this._jumpsByCategorie.length + index);
} }
} }
@@ -0,0 +1,130 @@
<div @flyInOut>
<div class="d-flex">
<div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
<span [hidden]="!lastJump.numero" class="me-2 fs-6">
Dernier saut enregistré : <span class="fs-5 mx-1 text-primary">{{ lastJump.numero }}<sup>ème</sup></span> à <span class="fs-5 mx-1 text-primary">{{ lastJump.lieu }}</span> le <span class="fs-5 mx-1 text-primary">{{ lastJump.date | date: 'dd/MM/yyyy' }}</span>
</span>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2">
<button mat-icon-button [matMenuTriggerFor]="menuJumps" aria-label="Menu sauts">
<mat-icon fontIcon="more_vert"></mat-icon>
</button>
<mat-menu #menuJumps="matMenu">
@for (menuitem of menuItems.getMenuPanel(); track menuitem) {
<button mat-menu-item>
<mat-icon [fontIcon]="menuitem.icon"></mat-icon>
<span>{{ menuitem.name }}</span>
</button>
}
</mat-menu>
</div>
</div>
<mat-divider class="my-3"></mat-divider>
<mat-card>
<mat-card-header>
<mat-card-title>{{ subtitle }}</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-2">
@if(displayCharts) {
<div class="row">
<div class="col-xxl-6 col-xs-12">
<app-linearea-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor"></app-linearea-chart>
</div>
<div class="col-xxl-6 col-xs-12">
<app-bars-chart [headers]="seriesHeader" [names]="seriesName" [values]="seriesRow" [colors]="seriesColor" [legend]="true"></app-bars-chart>
</div>
</div>
}
<div class="row">
<div class="col-xs-12">
<mat-divider class="mt-3 mb-0"></mat-divider>
<table class="table table-striped table-dark table-hover">
<thead>
<tr>
<th></th>
<th *ngFor='let name of seriesHeader; let i=index' class="text-end"> {{ name }} </th>
<th class="text-end">Total</th>
</tr>
</thead>
<tbody>
<tr *ngFor='let values of seriesRow; let i=index'>
<th class="{{seriesColorClass[i]}} text-end"> ● {{ seriesName[i] }} </th>
<td *ngFor='let value of values; let i=index' class="font-monospace text-end">
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template>
</td>
<th class="{{seriesColorClass[i]}} font-monospace text-end">{{ seriesRowTotal[i] }}</th>
</tr>
</tbody>
<tfoot>
<tr>
<th class="text-end">Total</th>
<th *ngFor='let value of seriesColTotal; let i=index' class="font-monospace text-end">
<span *ngIf="value; else elseBlock" class="pr-1">{{ value }}</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value }}</span></ng-template>
</th>
<th class="font-monospace text-end">{{ grandTotal }}</th>
</tr>
<tr>
<th class="text-end">Moyenne</th>
<th *ngFor='let value of seriesRowAvg; let i=index' class="font-monospace text-end">
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
</th>
<th class="font-monospace text-end">{{ grandAvg | number : '1.0-1' }}</th>
</tr>
<tr>
<th class="text-end">Moyenne <small>3 dernières années</small></th>
<th *ngFor='let value of seriesRowAvgLastYears; let i=index' class="font-monospace text-end">
<span *ngIf="value; else elseBlock" class="pr-1">{{ value | number : '1.0-1' }}</span>
<ng-template #elseBlock><span class="text-empty pr-1">{{ value | number : '1.0-1' }}</span></ng-template>
</th>
<th class="font-monospace text-end">{{ grandAvgLastYears | number : '1.0-1' }}</th>
</tr>
</tfoot>
</table>
</div>
</div>
</mat-card-content>
</mat-card>
<mat-card class="mt-3">
<mat-card-header>
<mat-card-title>Types de sauts</mat-card-title>
</mat-card-header>
<mat-card-content class="mt-2">
@if(displayCharts) {
<div class="row">
<div class="col-xs-12">
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRowCumulated" [colors]="seriesTypesColor" [legend]="true"></app-line-chart>
</div>
</div>
<mat-divider class="mt-2 mb-3"></mat-divider>
<div class="row">
@for (item of getCategories(); track $index) {
<div class="col-lg-2 col-sm-4 col-xs-6">
<app-circle-chart [headers]="seriesHeader" [names]="seriesName" [values]="[item.count, (lastJump.numero-item.count)]" [color]="$index" [label]="item.categorie"></app-circle-chart>
</div>
}
@for (item of getModules(); track $index) {
<div class="col-lg-2 col-sm-4 col-xs-6">
<app-circle-chart [headers]="seriesHeader" [names]="seriesName" [values]="[item.count, (lastJump.numero-item.count)]" [color]="getModuleColor($index)" [label]="item.module"></app-circle-chart>
</div>
}
</div>
<mat-divider class="my-3"></mat-divider>
<!--
<div class="row">
<div class="col-xxl-6 col-xs-12">
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRow" [colors]="seriesTypesColor" [legend]="true"></app-line-chart>
</div>
<div class="col-xxl-6 col-xs-12">
<app-line-chart [headers]="seriesTypesHeader" [names]="seriesTypesName" [values]="seriesTypesRowCumulated" [colors]="seriesTypesColor" [legend]="true"></app-line-chart>
</div>
</div>
-->
}
</mat-card-content>
</mat-card>
</div>
@@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { JumpsComponent } from './jumps.component';
describe('JumpsComponent', () => {
let component: JumpsComponent;
let fixture: ComponentFixture<JumpsComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [JumpsComponent]
});
fixture = TestBed.createComponent(JumpsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
+249
View File
@@ -0,0 +1,249 @@
import { trigger, state, style, animate, transition } from '@angular/animations';
import { Component, DestroyRef, inject, OnInit, OnDestroy } from '@angular/core';
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute } from '@angular/router';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { Observable, Subscription } from 'rxjs';
import { MenuItems } from 'src/app/components/shared';
import { BarsChartComponent, CircleChartComponent, LineChartComponent, LineAreaChartComponent } from 'src/app/components/shared/helpers-chart';
import { UtilitiesService } from 'src/app/core/services';
import { Jump, JumpByCategorie, JumpByDate, JumpByDateByModule, JumpByModule, JumpsPageData, JumpYears } from 'src/app/core/models';
@Component({
selector: 'app-jumps',
standalone: true,
imports: [
CommonModule,
MatButtonModule, MatCardModule, MatDividerModule,
MatExpansionModule, MatIconModule, MatMenuModule,
BarsChartComponent, CircleChartComponent, LineChartComponent, LineAreaChartComponent
],
templateUrl: './jumps.component.html',
styleUrl: './jumps.component.scss',
animations: [
trigger('flyInOut', [
state('in', style({ transform: 'translateX(0)' })),
transition('void => *', [
style({ transform: 'translateX(-100%)' }),
animate(200)
]),
transition('* => void', [
style({ transform: 'translateX(100%)' }),
animate(200)
])
])
]
})
export class JumpsComponent implements OnInit, OnDestroy {
private _data: Subscription = new Subscription();
private _jumpsByCategorie: Array<JumpByCategorie> = [];
private _jumpsByModule: Array<JumpByModule> = [];
private _jumpsByDate: Array<JumpByDate> = [];
public lastJump: Jump = {} as Jump;
public title: string = 'Sauts';
public subtitle: string = 'Nombre total de sauts par mois';
public displayCharts = false;
public destroyRef = inject(DestroyRef);
public min = 0;
public max = 0;
public grandAvg = 0;
public grandAvgLastYears = 0;
public grandTotal = 0;
public seriesHeader: string[] = [];
public seriesName: string[] = [];
public seriesRow: Array<Array<number>> = [];
public seriesColor: {
backgroundColor: string[],
borderColor: string[]
} = {
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'all'),
borderColor: this._utilitiesService.getSeriesColors(1, 'all')
};
public seriesTypesHeader: string[] = [];
public seriesTypesName: string[] = [];
public seriesTypesRow: Array<Array<number>> = [];
public seriesTypesRowCumulated: Array<Array<number>> = [];
public seriesTypesColor: {
backgroundColor: string[],
borderColor: string[]
} = {
backgroundColor: this._utilitiesService.getSeriesColors(0.8, 'pastels'),
borderColor: this._utilitiesService.getSeriesColors(1, 'pastels')
};
public seriesColorClass: string[] = this._utilitiesService.getChartColors();
public seriesRowTotal: number[] = [];
public seriesRowAvg: number[] = [];
public seriesRowAvgLastYears: number[] = [];
public seriesColTotal: number[] = [];
public seriesColTotalClosed: number[] = [];
public seriesColTotalLastYears: number[] = [];
constructor(
private route: ActivatedRoute,
private _utilitiesService: UtilitiesService,
public menuItems: MenuItems
) { }
ngOnInit() {
const data$: Observable<{ pageData: JumpsPageData }> = this.route.data as Observable<{ pageData: JumpsPageData }>;
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { pageData: JumpsPageData }) => {
const pageData: JumpsPageData = data.pageData;
this.lastJump = pageData.lastjump;
const currentYear: number = new Date().getFullYear();
const types: Array<string> = ['FF', 'Solo', 'VR', 'Wingsuit'];
const verticals: Array<string> = ['Chutas', 'Head Up', 'Head Down'];
const others: Array<string> = ['Track', 'Trace'];
const modules: Array<string> = [...verticals, ...others];
const minoration = 2; // -2 pour ne pas compter la première année
this.min = pageData.jumpsByYears[0].year;
this.max = pageData.jumpsByYears[(pageData.jumpsByYears.length-1)].year;
this.seriesHeader = this._utilitiesService.getMonthsList();
const values: Array<number> = this.seriesHeader.map(() => 0);
this.seriesColTotal = [...values];
this.seriesColTotalClosed = [...values];
this.seriesColTotalLastYears = [...values];
this.seriesName = pageData.jumpsByYears.map((row: JumpYears) => {
this.seriesRow.push([...values]);
return row.year.toString();
});
this._jumpsByDate = pageData.jumpsByDate.map((row: JumpByDate) => {
this.seriesRow[(row.year-this.min)][(row.month-1)] = row.count;
this.seriesColTotal[(row.month-1)] += row.count;
if (row.year < currentYear) {
this.seriesColTotalClosed[(row.month-1)] += row.count;
}
if (row.year >= (currentYear-3) && row.year < currentYear) {
this.seriesColTotalLastYears[(row.month-1)] += row.count;
}
return row;
});
this.seriesRow.forEach((row: number[]) => {
const total = row.reduce((partialSum, a) => partialSum + a, 0);
this.seriesRowTotal.push(total);
});
this.seriesColTotalClosed.forEach((row: number) => {
const value: number = (row/(this.seriesName.length-minoration));
this.seriesRowAvg.push(parseInt(value.toFixed(0)));
});
this.seriesColTotalLastYears.forEach((row: number) => {
const value: number = (row/3); // Les 3 dernières années
this.seriesRowAvgLastYears.push(parseInt(value.toFixed(0)));
});
this.grandAvg = this.seriesRowAvg.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
this.grandAvgLastYears = this.seriesRowAvgLastYears.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
this.grandTotal = this.seriesColTotal.reduce((partialSum, accumulated) => partialSum + accumulated, 0);
pageData.jumpsByCategory.forEach((row: JumpByCategorie) => {
if (types.indexOf(row.categorie) !== -1) {
this._jumpsByCategorie.push(row);
}
});
//const focus: Array<string> = ['FF', 'Solo']; // ['FF']
let verticalCount:number = 0;
let otherCount:number = 0;
pageData.jumpsByModule.forEach((row: JumpByModule) => {
//if (focus.indexOf(row.categorie) !== -1 && modules.indexOf(row.module) !== -1) {
if (modules.indexOf(row.module) !== -1) {
if (verticals.indexOf(row.module) !== -1) {
verticalCount += row.count;
} else {
otherCount += row.count;
}
}
});
if (verticalCount > 0) {
const row:JumpByModule = {
categorie: 'FF',
module: 'Vertical',
count: verticalCount
}
this._jumpsByModule.push(row);
}
if (otherCount > 0) {
const row:JumpByModule = {
categorie: 'FF',
module: 'Track/Trace',
count: otherCount
}
this._jumpsByModule.push(row);
}
for (let year = 0; year < pageData.jumpsByYears.length; year++) {
for (let index = 0; index < 12; index++) {
const month: string = ((index+1) < 10) ? `0${(index+1)}` : `${(index+1)}`;
this.seriesTypesHeader.push(`${month}-${pageData.jumpsByYears[year].year}`);
}
}
const emptyValues: Array<number> = this.seriesTypesHeader.map(() => 0);
this.seriesTypesName = types;
this.seriesTypesName.forEach(() => {
this.seriesTypesRow.push([...emptyValues]);
});
pageData.jumpsByDateByModule.forEach((row: JumpByDateByModule) => {
const indexOf: number = this.seriesTypesName.indexOf(row.categorie);
if (indexOf !== -1) {
this.seriesTypesRow[indexOf][(((row.year-this.min)*12)+row.month-1)] += row.count;
}
});
this.seriesTypesRowCumulated = this.seriesTypesRow.map((row: Array<number>) => {
let accumulated: number = 0;
return row.map((value: number) => {
accumulated += value;
return accumulated;
});
});
this.seriesTypesHeader = this.seriesTypesHeader.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
this.seriesTypesRow = this.seriesTypesRow.map((row: Array<number>) => {
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
});
this.seriesTypesRowCumulated = this.seriesTypesRowCumulated.map((row: Array<number>) => {
return row.slice((pageData.jumpsByDateByModule[0].month+1), -(pageData.jumpsByDateByModule[(pageData.jumpsByDateByModule.length-1)].month));
});
/*
console.log('this.min', this.min);
console.log('this.max', this.max);
console.log('this.lastJump', this.lastJump);
console.log('this.seriesHeader', this.seriesHeader);
console.log('this.seriesName', this.seriesName);
console.log('this.seriesRow', this.seriesRow);
console.log('this.seriesRowTotal', this.seriesRowTotal);
console.log('this.seriesRowAvg', this.seriesRowAvg);
console.log('this.seriesRowAvgLastYears', this.seriesRowAvgLastYears);
console.log('this._jumpsByCategorie', this._jumpsByCategorie);
console.log('this._jumpsByModule', this._jumpsByModule);
console.log('this.seriesTypesHeader', this.seriesTypesHeader);
console.log('this.seriesTypesName', this.seriesTypesName);
console.log('this.seriesTypesRow', this.seriesTypesRow);
console.log('this.seriesTypesRowCumulated', this.seriesTypesRowCumulated);
*/
this.displayCharts = true;
});
}
ngOnDestroy() {
this._data.unsubscribe();
}
public getCategories(): Array<JumpByCategorie> {
return this._jumpsByCategorie;
}
public getModules(): Array<JumpByModule> {
return this._jumpsByModule;
}
public getModuleColor(index: number): number {
return (this._jumpsByCategorie.length + index);
}
}
@@ -245,7 +245,7 @@
} }
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark"> <span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon> <mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon>
ju_solide {{ lastJump.author.username }}
</span> </span>
</div> </div>
</div> </div>
@@ -180,7 +180,7 @@
} }
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark"> <span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon> <mat-icon class="align-middle pointer me-1" fontIcon="person"></mat-icon>
ju_solide {{ jump.author.username }}
</span> </span>
</div> </div>
</div> </div>
@@ -1,29 +1,69 @@
<h2 mat-dialog-title class="bg-cyan border-bottom border-light-subtle d-flex mb-3 pt-3 pb-2"> <h2 mat-dialog-title class="bg-navy-light border-bottom border-light-subtle d-flex mb-3 pt-3 pb-2">
Saut n° {{jump.numero}}<span class="flex-spacer"></span> Saut n° {{jump.numero}}<span class="flex-spacer"></span>
<mat-icon fontIcon="close" class="pointer" (click)="closeDialog()"></mat-icon> <mat-icon fontIcon="close" class="pointer" (click)="closeDialog()"></mat-icon>
</h2> </h2>
<mat-dialog-content> <mat-dialog-content>
<div class="container"> <div class="container">
<h5 class="border-bottom text-light-info border-light-subtle pb-2 mb-3">Le {{ jump.date | date: 'dd/MM/yyyy' }} à {{ jump.lieu }} :</h5> <div class="d-flex border-bottom border-light-subtle text-light-info pb-2 mb-3">
<div class="row mb-3"> <div class="flex-fill">
<h5>Le {{ jump.date | date: 'dd/MM/yyyy' }} à {{ jump.lieu }} {{ jump.zone ? jump.zone : jump.oaci }} :</h5>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2">
<span class="text-accent fs-5">{{ jump.categorie }}</span>
@if (jump.module) {
<span class="fs-5 mx-2">/</span>
<span class="text-muted fs-5">{{ jump.module }}</span>
}
</div>
</div>
<div class="row">
<div class="col-lg-6 col-md-12"> <div class="col-lg-6 col-md-12">
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt class="text-cyan fw-normal">Lieu:</dt> <dt class="text-light-info fw-normal">Aeronef:</dt>
<dd>{{jump.lieu}}</dd> <dd>{{ jump.aeronef }} {{ jump.imat }}</dd>
<dt class="text-cyan fw-normal">Oaci:</dt> <dt class="text-light-info fw-normal">Voile:</dt>
<dd>{{jump.oaci}}</dd> <dd>{{ jump.voile }} {{ jump.taille }} ft<sup>2</sup></dd>
<dt class="text-cyan fw-normal">Aeronef:</dt> @if (jump.programme) {
<dd>{{jump.aeronef}}</dd> <dt class="text-light-info fw-normal">Programme:</dt>
<dt class="text-cyan fw-normal">Imat:</dt> <dd>{{ jump.programme }}</dd>
<dd>{{jump.imat}}</dd> }
<dt class="text-cyan fw-normal">Voile:</dt> </dl>
<dd>{{jump.voile}}</dd> </div>
<dt class="text-cyan fw-normal">Taille:</dt> <div class="col-lg-6 col-md-12">
<dd>{{jump.taille}} ft<sup>2</sup></dd> <dl class="dl-horizontal">
<dt class="text-cyan fw-normal">Participants:</dt> @if (!jump.x2data) {
<dd>{{jump.participants}}</dd> <dt class="text-light-info fw-normal">Hauteur:</dt>
<dt class="text-cyan fw-normal">Sautants:</dt> <dd>{{ jump.hauteur }} m</dd>
<dd> <dt class="text-light-info fw-normal">Déploiement:</dt>
<dd>{{ jump.deploiement }} m</dd>
}
@for (file of jump.files; track file) {
<dt class="text-light-info fw-normal">Fichier {{ file.type }}</dt>
<dd>{{ file.name }}</dd>
}
</dl>
</div>
</div>
<mat-divider></mat-divider>
@if (jump.x2data) {
<div class="row my-3">
<div class="col-sm-4 col-xs-12 text-center">
<h6>Hauteur</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.altitude.exit }} m</span>
</div>
<div class="col-sm-4 col-xs-12 text-center">
<h6 class="d-flex justify-content-center"><mat-icon fontIcon="timer" class="me-1 fs-5"></mat-icon> Durée de la chute</h6>
<span class="text-primary fw-medium fs-5">{{formatDuration(jump.x2data.duration.freeFall)}}</span>
</div>
<div class="col-sm-4 col-xs-12 text-center">
<h6>Déploiement</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.altitude.deployment }} m</span>
</div>
</div>
<div class="row my-4">
<div class="{{ jump.accessoires ? 'col-sm-6 ' : '' }}col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-4"><mat-icon fontIcon="people" class="me-1"></mat-icon> Participants ({{ jump.sautants.length + 1 }})</h5>
@for (sautant of jump.sautants; track sautant) { @for (sautant of jump.sautants; track sautant) {
<span class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark"> <span class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon> <mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
@@ -32,35 +72,140 @@
} }
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark"> <span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon> <mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
ju_solide {{ jump.author.username }}
</span> </span>
</dd>
</dl>
</div> </div>
<div class="col-lg-6 col-md-12"> @if (jump.accessoires) {
<dl class="dl-horizontal"> <div class="col-sm-6 col-xs-12 text-center">
<dt class="text-cyan fw-normal">Hauteur:</dt> <h5 class="d-flex justify-content-center mb-4"><mat-icon fontIcon="auto_awesome" class="me-1"></mat-icon> Accessoires</h5>
<dd>{{jump.hauteur}} m</dd> <span class="badge rounded-pill bg-navy-light mb-2 me-1 pe-3 py-1 fw-normal">
<dt class="text-cyan fw-normal">Deploiement:</dt> <mat-icon class="align-middle me-1" fontIcon="auto_awesome"></mat-icon>
<dd>{{jump.deploiement}} m</dd> {{ jump.accessoires }}
<dt class="text-cyan fw-normal">Categorie:</dt> </span>
<dd>{{jump.categorie}}</dd> </div>
<dt class="text-cyan fw-normal">Module:</dt> }
<dd>{{jump.module}}</dd> </div>
<dt class="text-cyan fw-normal">Accessoires:</dt>
<dd>{{jump.accessoires}}</dd> <div class="row my-3">
<dt class="text-cyan fw-normal">Zone:</dt> <div class="col-xs-12 text-center">
<dd>{{jump.zone}}</dd> <h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="speed" class="me-1"></mat-icon> Vitesse en chute</h5>
<dt class="text-cyan fw-normal">Dossier media:</dt> </div>
<dd>{{jump.dossier}}</dd> <div class="col-sm-3 col-xs-12 text-center">
<dt class="text-cyan fw-normal">Video:</dt> <h6>Max Verticale</h6>
<dd>{{jump.video}}</dd> <span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.vertical.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
<dt class="text-cyan fw-normal">Programme:</dt> </div>
<dd>{{jump.programme}}</dd> <div class="col-sm-3 col-xs-12 text-center">
</dl> <h6>Max Horizontale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.horizontal.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Moyenne Verticale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.vertical.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Moyenne Horizontale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.freeFall.horizontal.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div> </div>
</div> </div>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<div class="row mt-4 mb-3">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="speed" class="me-1"></mat-icon> Vitesse sous voile</h5>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Max Verticale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.vertical.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Max Horizontale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.horizontal.max / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Moyenne Verticale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.vertical.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Moyenne Horizontale</h6>
<span class="text-primary fw-medium fs-5">{{ (jump.x2data.speed.underCanopy.horizontal.avg / 1000 * 60 * 60) | number : '1.0-0' }} km/h</span>
</div>
</div>
<mat-divider></mat-divider>
<div class="row mt-3">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="square_foot" class="me-1"></mat-icon> Finesse</h5>
</div>
<div class="col-sm-6 col-xs-12 text-center">
<h6>Finesse en chute</h6>
</div>
<div class="col-sm-6 col-xs-12 text-center">
<h6>Finesse sous voile</h6>
</div>
</div>
<div class="row mb-3">
<div class="col-sm-3 col-xs-12 text-center">
<h6>Finesse Max</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.glideRatio.freeFall.max | number : '1.2-2' }}</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Finesse Moyenne</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.glideRatio.freeFall.avg | number : '1.2-2' }}</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Finesse Max</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.glideRatio.underCanopy.max | number : '1.2-2' }}</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Finesse Moyenne</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.glideRatio.underCanopy.avg | number : '1.2-2' }}</span>
</div>
</div>
<mat-divider></mat-divider>
<div class="row my-3">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-3"><mat-icon fontIcon="straighten" class="me-1"></mat-icon> Distance</h5>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Distance au largage</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.distance.exitFromLandingZone| number : '1.0-0' }} m</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6 class="d-flex justify-content-center"><mat-icon fontIcon="timer" class="me-1 fs-5"></mat-icon> Durée sous voile</h6>
<span class="text-primary fw-medium fs-5">{{formatDuration(jump.x2data.duration.underCanopy)}}</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Distance totale</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.distance.totalFlying | number : '1.0-0' }} m</span>
</div>
<div class="col-sm-3 col-xs-12 text-center">
<h6>Distance horizontale</h6>
<span class="text-primary fw-medium fs-5">{{ jump.x2data.distance.totalHorizontal | number : '1.0-0' }} m</span>
</div>
</div>
<mat-divider></mat-divider>
}
@else {
<div class="row my-4">
<div class="col-xs-12 text-center">
<h5 class="d-flex justify-content-center mb-4"><mat-icon fontIcon="people" class="me-1"></mat-icon> Participants ({{ jump.sautants.length + 1 }})</h5>
@for (sautant of jump.sautants; track sautant) {
<span class="badge rounded-pill bg-primary mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
{{ sautant }}
</span>
}
<span class="badge rounded-pill bg-accent mb-2 me-1 pe-3 py-1 fw-normal text-dark">
<mat-icon class="align-middle me-1" fontIcon="person"></mat-icon>
{{ jump.author.username }}
</span>
</div>
</div>
<mat-divider></mat-divider>
<div class="d-flex justify-content-center my-3 fs-5"><mat-icon fontIcon="satellite_alt" class="me-2"></mat-icon> Aucune donnée GPS</div>
<mat-divider></mat-divider>
}
</div> </div>
</mat-dialog-content> </mat-dialog-content>
<mat-dialog-actions> <mat-dialog-actions>
@@ -1,5 +1,5 @@
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { DatePipe } from '@angular/common'; import { DatePipe, DecimalPipe } from '@angular/common';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider'; import { MatDividerModule } from '@angular/material/divider';
@@ -12,11 +12,9 @@ import { Jump } from 'src/app/core/models';
templateUrl: 'jump-view.dialog.html', templateUrl: 'jump-view.dialog.html',
standalone: true, standalone: true,
imports: [ imports: [
DatePipe, DatePipe, DecimalPipe,
MatButtonModule, MatButtonModule, MatDialogModule,
MatDialogModule, MatDividerModule, MatIconModule
MatDividerModule,
MatIconModule
] ]
}) })
export class JumpViewDialogComponent { export class JumpViewDialogComponent {
@@ -30,4 +28,10 @@ export class JumpViewDialogComponent {
// close the dialog without result // close the dialog without result
this.dialogRef.close(); this.dialogRef.close();
} }
formatDuration(duration: number): string {
const start = new Date(0);
start.setSeconds(duration);
return start.toISOString().substring(14, 19);
}
} }
@@ -1,5 +1,4 @@
<div @flyInOut> <div @flyInOut>
<div class="content">
<div class="d-flex"> <div class="d-flex">
<div class="flex-fill"> <div class="flex-fill">
<h1 class="mb-1">{{title}}</h1> <h1 class="mb-1">{{title}}</h1>
@@ -104,4 +103,3 @@
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</div> </div>
</div>
@@ -12,14 +12,20 @@
</div> </div>
</div> </div>
</div> </div>
<div class="profile-page content"> <div class="profile-page">
<h1>{{title}}</h1> <div class="d-flex">
<mat-divider></mat-divider> <div class="flex-fill">
<h1 class="mb-1">{{title}}</h1>
</div>
<span class="flex-spacer"></span>
<div class="align-self-center me-2 text-end"></div>
</div>
<mat-divider class="my-3"></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors> <app-list-errors [errors]="errors"></app-list-errors>
<mat-card class="mt-4 bg-light text-black scaled"> <mat-card class="mt-4 bg-light text-black scaled">
<mat-card-content> <mat-card-content>
<div class="text-center"> <div class="text-center">
<a href="index.html" class="d-block p-4"><img height="72" width="90" src="assets/images/vector/upt_logo.png" alt="Logo UPT"></a> <a href="/" class="d-block p-4"><img height="72" width="90" src="assets/images/vector/upt_logo.png" alt="Logo UPT"></a>
<mat-divider class="border-primary w-50 mx-auto"></mat-divider> <mat-divider class="border-primary w-50 mx-auto"></mat-divider>
<h1 class="mt-3 mb-4"><span class="pt-1 pb-2 px-2">Vector 3 - <small class="text-muted">V306 Micron</small></span></h1> <h1 class="mt-3 mb-4"><span class="pt-1 pb-2 px-2">Vector 3 - <small class="text-muted">V306 Micron</small></span></h1>
<mat-divider class="border-primary w-50 mx-auto"></mat-divider> <mat-divider class="border-primary w-50 mx-auto"></mat-divider>
@@ -4,6 +4,7 @@
.banner { .banner {
background-color: var.$sidebar-footer; background-color: var.$sidebar-footer;
padding: 1.6rem 0 1.3rem 0; padding: 1.6rem 0 1.3rem 0;
margin: -1.5rem -1.5rem 1.5rem -1.5rem;
.user-img { .user-img {
width: var.$user-img-size; width: var.$user-img-size;
height: var.$user-img-size; height: var.$user-img-size;
+30 -7
View File
@@ -1,4 +1,3 @@
<div class="content">
<div class="d-flex"> <div class="d-flex">
<div class="flex-fill"> <div class="flex-fill">
<h1 class="mb-1"> <h1 class="mb-1">
@@ -24,9 +23,21 @@
</div> </div>
<mat-divider class="my-3"></mat-divider> <mat-divider class="my-3"></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors> <app-list-errors [errors]="errors"></app-list-errors>
<mat-card [hidden]="!questions.length"> @if (questions.controls.length) {
<mat-card-content class="mt-4"> <mat-card [hidden]="!questions.controls.length">
<form [formGroup]="qcmForm"> <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="squareItem clearfix">
@for (category of qcm.categories; track category.num) {
@for (question of category.questions; track question.num) {
<li class="rounded-0">{{ question.num }}</li>
}
}
</ol>
</div>
<mat-card-content>
<form [formGroup]="qcmForm" (ngSubmit)="onSubmit()">
@for (category of qcm.categories; track category; let index = $index) { @for (category of qcm.categories; track category; let index = $index) {
<mat-accordion> <mat-accordion>
<mat-expansion-panel class="mb-3" [expanded]="expandedIndex === index" (opened)="setExpandedIndex(index)"> <mat-expansion-panel class="mb-3" [expanded]="expandedIndex === index" (opened)="setExpandedIndex(index)">
@@ -43,9 +54,9 @@
@for (question of category.questions; track question; let index = $index) { @for (question of category.questions; track question; let index = $index) {
<div class="mb-3" [formGroupName]="index"> <div class="mb-3" [formGroupName]="index">
<label class="fs-5 fw-medium mb-2" for="radio-group-{{ category.num }}-{{ question.num }}">{{ question.num }} - {{ question.libelle }}</label> <label class="fs-5 fw-medium mb-2" for="radio-group-{{ category.num }}-{{ question.num }}">{{ question.num }} - {{ question.libelle }}</label>
<mat-radio-group id="radio-group-{{ category.num }}-{{ question.num }}" class="d-flex flex-column" (change)="onChange()"> <mat-radio-group formControlName="choice" id="radio-group-{{ category.num }}-{{ question.num }}" class="d-flex flex-column" (change)="onChange(category, question, $event.value)">
@for (choice of question.choices; track choice) { @for (choice of question.choices; track choice) {
<mat-radio-button class="example-radio-button" [value]="choice.index" [color]="choice.correct ? 'primary' : 'warn'">{{ choice.libelle }}</mat-radio-button> <mat-radio-button [value]="choice.index" [color]="choice.correct ? 'primary' : 'warn'">{{ choice.libelle }}</mat-radio-button>
} }
</mat-radio-group> </mat-radio-group>
</div> </div>
@@ -56,5 +67,17 @@
} }
</form> </form>
</mat-card-content> </mat-card-content>
</mat-card> <!--
<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>
}
</div>
</form>
-->
</mat-card>
}
+37
View File
@@ -0,0 +1,37 @@
/* QCM */
@use 'variable' as var;
ol.squareItem {
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;
}
}
}
+75 -18
View File
@@ -8,12 +8,15 @@ import { MatDividerModule } from '@angular/material/divider';
import { MatExpansionModule } from '@angular/material/expansion'; import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatRadioModule } from '@angular/material/radio'; import { MatRadioModule } from '@angular/material/radio';
import { Observable, Subscription } from 'rxjs'; import { MatSelectModule } from '@angular/material/select';
import { interval, Observable, Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { MenuItems } from 'src/app/components/shared'; import { MenuItems } from 'src/app/components/shared';
import { ListErrorsComponent, } from 'src/app/components/shared'; import { ListErrorsComponent } from 'src/app/components/shared';
import { Errors, Qcm, User } from 'src/app/core/models'; import { Errors, Qcm, QcmCategory, QcmQuestion, User } from 'src/app/core/models';
import { QcmService, UserService } from 'src/app/core/services'; import { QcmService, UserService } from 'src/app/core/services';
import data from 'src/qcm-bpa.json'; import data from 'src/qcm-bpa.json';
@@ -23,7 +26,8 @@ import data from 'src/qcm-bpa.json';
imports: [ imports: [
FormsModule, ReactiveFormsModule, FormsModule, ReactiveFormsModule,
MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule, MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule,
MatIconModule, MatMenuModule, MatRadioModule, MatIconModule, MatMenuModule, MatProgressBarModule, MatRadioModule,
MatSelectModule,
ListErrorsComponent ListErrorsComponent
], ],
templateUrl: './qcm.component.html', templateUrl: './qcm.component.html',
@@ -32,6 +36,8 @@ import data from 'src/qcm-bpa.json';
export class QcmComponent implements OnInit, OnDestroy { export class QcmComponent implements OnInit, OnDestroy {
private _currentUser: Subscription = new Subscription(); private _currentUser: Subscription = new Subscription();
private _data: Subscription = new Subscription(); private _data: Subscription = new Subscription();
private _timer: Subscription = new Subscription();
private _refresh = 1000;
public qcm: Qcm = {} as Qcm; public qcm: Qcm = {} as Qcm;
public currentUser: User = {} as User; public currentUser: User = {} as User;
public canModify = false; public canModify = false;
@@ -42,6 +48,11 @@ export class QcmComponent implements OnInit, OnDestroy {
public expandedIndex = 0; public expandedIndex = 0;
public questionsCount = 0; public questionsCount = 0;
public qcmForm!: FormGroup; public qcmForm!: FormGroup;
public loading = true;
public totalAnswer = 0;
public timeProgress = 0;
public timeProgressColor = 'primary';
public timeMax = (60*45);
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@@ -56,15 +67,16 @@ export class QcmComponent implements OnInit, OnDestroy {
} }
get questions(): FormArray { get questions(): FormArray {
return <FormArray>this.qcmForm.controls['questions']; return this.qcmForm.controls['questions'] as FormArray;
} }
ngOnInit() { ngOnInit() {
//this.importQuestions(); //this._importQuestions();
//this.importChoices(); //this._importChoices();
// Retreive the prefetched QCM // Retreive the prefetched QCM
const data$: Observable<{ qcm: Qcm }> = this.route.data as Observable<{ qcm: Qcm }>; const data$: Observable<{ qcm: Qcm }> = this.route.data as Observable<{ qcm: Qcm }>;
this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { qcm: Qcm }) => { this._data = data$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((data: { qcm: Qcm }) => {
this.questionsCount = 0;
this.qcm = data.qcm; this.qcm = data.qcm;
if (this.qcm.name === 'bpa') { if (this.qcm.name === 'bpa') {
this.title = 'QCM du BPA'; this.title = 'QCM du BPA';
@@ -77,13 +89,7 @@ export class QcmComponent implements OnInit, OnDestroy {
this.qcm.categories.forEach(category => { this.qcm.categories.forEach(category => {
this.questionsCount += category.questions.length; this.questionsCount += category.questions.length;
category.questions.forEach(question => { category.questions.forEach(question => {
this.questions.push( this.addQuestion(question);
this.fb.group({
id: new FormControl({ value: question.num, disabled: false }),
choice: ['']
//choice: new FormControl({ value: '', disabled: false })
})
);
}); });
}); });
}); });
@@ -93,6 +99,7 @@ export class QcmComponent implements OnInit, OnDestroy {
this.currentUser = userData; this.currentUser = userData;
this.canModify = userData.role === 'Admin'; this.canModify = userData.role === 'Admin';
}); });
this.loading = false;
} }
ngOnDestroy() { ngOnDestroy() {
@@ -100,15 +107,61 @@ export class QcmComponent implements OnInit, OnDestroy {
this._currentUser.unsubscribe(); this._currentUser.unsubscribe();
} }
private _starTimer(): void {
this._timer = interval(1000)
.pipe(take(this.timeMax))
.subscribe(() => {
this.timeProgress++;
if (this.timeProgress > (this.timeMax/100*70) && this.timeProgress <= (this.timeMax/100*90)) {
this.timeProgressColor = 'warning';
} else if (this.timeProgress > (this.timeMax/100*90) && this.timeProgress <= (this.timeMax/100*98)) {
this.timeProgressColor = 'danger';
} else if (this.timeProgress > (this.timeMax/100*98)) {
this.timeProgressColor = 'purple';
}
if (this.timeProgress >= this.timeMax) {
this.timeProgressColor = 'accent';
this._timer.unsubscribe();
}
});
}
addQuestion(question: QcmQuestion) {
const item = this.fb.group({
id: [question.num],
choice: ['']
});
this.questions.push(item);
}
setExpandedIndex(index: number) { setExpandedIndex(index: number) {
this.expandedIndex = index; this.expandedIndex = index;
} }
onChange() { onChange(category: QcmCategory, question: QcmQuestion, value: unknown) {
//this.isDisabled = true; //console.log('onChange', category.num, question.num, value);
if (this.timeProgress === 0) {
this._starTimer();
}
this.qcm.categories[(category.num-1)].questions[(question.num-1)].choices.forEach(choice => {
if (choice.index === value) {
if (choice.correct) {
console.log(`Réponse à la question ${question.num} de la catégorie ${category.num} : correcte`);
} else {
console.log(`Réponse à la question ${question.num} de la catégorie ${category.num} : incorrecte`);
}
}
});
this.totalAnswer++;
this.questions.controls[(question.num-1)].disable();
//console.log(this.questions.controls[(question.num-1)].disabled);
} }
importChoices() { onSubmit() {
console.log('onSubmit');
}
private _importChoices() {
try { try {
data.categories.forEach((category) => { data.categories.forEach((category) => {
category.questions.forEach(question => { category.questions.forEach(question => {
@@ -132,7 +185,7 @@ export class QcmComponent implements OnInit, OnDestroy {
} }
} }
importQuestions() { private _importQuestions() {
try { try {
data.categories.forEach((category) => { data.categories.forEach((category) => {
//Object.assign(this.jump, category); //Object.assign(this.jump, category);
@@ -154,4 +207,8 @@ export class QcmComponent implements OnInit, OnDestroy {
console.error(error); console.error(error);
} }
} }
private _resetErrors(): void {
this.errors = { errors: {} };
}
} }
@@ -12,7 +12,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="credentials-page content"> <div class="credentials-page">
<h1>{{title}}</h1> <h1>{{title}}</h1>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors> <app-list-errors [errors]="errors"></app-list-errors>
@@ -4,6 +4,7 @@
.banner { .banner {
background-color: var.$sidebar-footer; background-color: var.$sidebar-footer;
padding: 1.6rem 0 1.3rem 0; padding: 1.6rem 0 1.3rem 0;
margin: -1.5rem -1.5rem 1.5rem -1.5rem;
.user-img { .user-img {
width: var.$user-img-size; width: var.$user-img-size;
height: var.$user-img-size; height: var.$user-img-size;
@@ -12,7 +12,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="settings-page content"> <div class="settings-page">
<h1>{{title}}</h1> <h1>{{title}}</h1>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<app-list-errors [errors]="errors"></app-list-errors> <app-list-errors [errors]="errors"></app-list-errors>
@@ -4,6 +4,7 @@
.banner { .banner {
background-color: var.$sidebar-footer; background-color: var.$sidebar-footer;
padding: 1.6rem 0 1.3rem 0; padding: 1.6rem 0 1.3rem 0;
margin: -1.5rem -1.5rem 1.5rem -1.5rem;
/* /*
background-color: mat.get-color-from-palette(var.$md-custom-warn, 700); background-color: mat.get-color-from-palette(var.$md-custom-warn, 700);
margin-bottom: 1rem; margin-bottom: 1rem;
@@ -74,6 +74,7 @@ export class JumpsByMonthComponent implements OnInit, OnDestroy {
} }
private _loadJumpByDate() { private _loadJumpByDate() {
const minoration = 2; // -2 pour ne pas compter la première année
this.seriesHeader = this._utilitiesService.getMonthsList(); this.seriesHeader = this._utilitiesService.getMonthsList();
const values: Array<number> = this.seriesHeader.map(() => 0); const values: Array<number> = this.seriesHeader.map(() => 0);
this.seriesColTotal = [...values]; this.seriesColTotal = [...values];
@@ -111,7 +112,7 @@ export class JumpsByMonthComponent implements OnInit, OnDestroy {
this.seriesRowTotal.push(total); this.seriesRowTotal.push(total);
}); });
this.seriesColTotalClosed.forEach((row: number) => { this.seriesColTotalClosed.forEach((row: number) => {
const value: number = (row/(this.seriesName.length-2)); // -2 pour ne pas compter la première année const value: number = (row/(this.seriesName.length-minoration));
this.seriesRowAvg.push(parseInt(value.toFixed(0))); this.seriesRowAvg.push(parseInt(value.toFixed(0)));
}); });
this.seriesColTotalLastYears.forEach((row: number) => { this.seriesColTotalLastYears.forEach((row: number) => {
@@ -12,7 +12,7 @@
</button> </button>
<mat-spinner [diameter]="32" [hidden]="!loading" color="accent" class="align-middle ms-2"></mat-spinner> <mat-spinner [diameter]="32" [hidden]="!loading" color="accent" class="align-middle ms-2"></mat-spinner>
<span [hidden]="jumpsCount || loading" class="ms-2 fst-italic">Aucun saut à afficher pour le moment.</span> <span [hidden]="jumpsCount || loading" class="ms-2 fst-italic">Aucun saut à afficher pour le moment.</span>
<span [hidden]="!loading" class="ms-2 fst-italic">Actualisation des sauts en cours</span> <span [hidden]="!loading" class="ms-2 fst-italic">Chargement des sauts en cours</span>
</form> </form>
</div> </div>
<div class="col-md-6 col-xs-12 px-0"> <div class="col-md-6 col-xs-12 px-0">
@@ -224,7 +224,7 @@
</ng-container> </ng-container>
</td> </td>
</ng-container> --> </ng-container> -->
<!-- Video Column --> <!-- Video Column
<ng-container matColumnDef="video"> <ng-container matColumnDef="video">
<th class="pl-3" mat-header-cell *matHeaderCellDef> Video </th> <th class="pl-3" mat-header-cell *matHeaderCellDef> Video </th>
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element"> <td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
@@ -236,6 +236,41 @@
</ngx-skeleton-loader> </ngx-skeleton-loader>
} }
</td> </td>
</ng-container> -->
<!-- Files Column -->
<ng-container matColumnDef="files">
<th class="pl-3" mat-header-cell *matHeaderCellDef> Fichiers </th>
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
@if (element.files) {
@for (file of element.files; track $index) {
@switch (file.type) {
@case ('video') {
<button mat-button (click)="openViewDialog(element)" color="navy-light">
<mat-icon fontIcon="videocam me-2"></mat-icon> {{ file.type }}
</button>
}
@case ('image') {
<button mat-button (click)="openViewDialog(element)" color="navy-light">
<mat-icon fontIcon="image me-2"></mat-icon> {{ file.type }}
</button>
}
@case ('kml') {
<button mat-button (click)="openViewDialog(element)" color="navy-light">
<mat-icon fontIcon="language me-2"></mat-icon> {{ file.type }}
</button>
}
@default {
<button mat-button (click)="openViewDialog(element)" color="navy-light">
<mat-icon fontIcon="description me-2"></mat-icon> {{ file.type }}
</button>
}
}
}
}
@if (!element.numero) {
<ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader>
}
</td>
</ng-container> </ng-container>
<!-- Actions Column --> <!-- Actions Column -->
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
@@ -247,13 +282,17 @@
<mat-icon>more_vert</mat-icon> <mat-icon>more_vert</mat-icon>
</button> </button>
<mat-menu [hidden]="!showAdd" #actionMenu="matMenu"> <mat-menu [hidden]="!showAdd" #actionMenu="matMenu">
<button mat-menu-item [routerLink]="['/jump', element.slug]">
<mat-icon aria-label="Voir" fontIcon="visibility" color="info" class="mini"></mat-icon>
<span class="text-info">Détails</span>
</button>
<button mat-menu-item (click)="openEditDialog(element)"> <button mat-menu-item (click)="openEditDialog(element)">
<mat-icon aria-label="Modifier" fontIcon="edit" color="primary" class="mini"></mat-icon> <mat-icon aria-label="Modifier" fontIcon="edit" color="primary" class="mini"></mat-icon>
<span class="text-primary">Modifier</span> <span class="text-primary">Modifier</span>
</button> </button>
<button mat-menu-item (click)="openDeleteDialog(element)"> <button mat-menu-item (click)="openDeleteDialog(element)">
<mat-icon aria-label="Supprimer" fontIcon="delete" color="danger" class="mini"></mat-icon> <mat-icon aria-label="Supprimer" fontIcon="delete" color="raspberry" class="mini"></mat-icon>
<span class="text-danger">Supprimer</span> <span class="text-raspberry">Supprimer</span>
</button> </button>
</mat-menu> </mat-menu>
} }
@@ -21,7 +21,7 @@ import { take } from 'rxjs/operators';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { FrenchPaginator } from 'src/app/components/shared/french-paginator.component'; import { FrenchPaginator } from 'src/app/components/shared/french-paginator.component';
import { ColumnDefinition, Errors, Jump, JumpList, JumpListConfig, jumpColumns } from 'src/app/core/models'; import { ColumnDefinition, Errors, Jump, JumpByDay, JumpFile, JumpFileType, JumpList, JumpListConfig, jumpColumns } from 'src/app/core/models';
import { JumpsService, JumpTableService } from 'src/app/core/services'; import { JumpsService, JumpTableService } from 'src/app/core/services';
import { JumpAddDialogComponent, JumpDeleteDialogComponent, JumpEditDialogComponent, JumpViewDialogComponent } from 'src/app/components/logbook/dialogs' import { JumpAddDialogComponent, JumpDeleteDialogComponent, JumpEditDialogComponent, JumpViewDialogComponent } from 'src/app/components/logbook/dialogs'
@@ -41,6 +41,7 @@ import { JumpAddDialogComponent, JumpDeleteDialogComponent, JumpEditDialogCompon
}) })
export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChecked { export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChecked {
private _jumps: Subscription = new Subscription(); private _jumps: Subscription = new Subscription();
private _subscriptions: Array<Subscription> = [];
//private _jump: Subscription = new Subscription(); //private _jump: Subscription = new Subscription();
private _query: JumpListConfig = { type: 'all', filters: {} }; private _query: JumpListConfig = { type: 'all', filters: {} };
//private _lastjump: Jump = {} as Jump; //private _lastjump: Jump = {} as Jump;
@@ -53,13 +54,13 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
'numero', 'date', 'lieu', 'oaci', 'aeronef', 'numero', 'date', 'lieu', 'oaci', 'aeronef',
'imat', 'hauteur', 'voile', 'taille', 'categorie', 'imat', 'hauteur', 'voile', 'taille', 'categorie',
'module', 'participants', 'programme', 'accessoires', 'module', 'participants', 'programme', 'accessoires',
'zone', 'video', 'actions' 'zone', 'files', 'actions'
];*/ ];*/
public displayedColumns: string[] = [ public displayedColumns: string[] = [
'numero', 'date', 'lieu', 'aeronef', 'numero', 'date', 'lieu', 'aeronef',
'hauteur', 'voile', 'categorie', 'hauteur', 'voile', 'categorie',
'participants', 'zone', 'accessoires', 'participants', 'zone', 'accessoires',
'video', 'actions' 'files', 'actions'
]; ];
//public displayedColumns: string[] = jumpColumns.map((col) => col.key) //public displayedColumns: string[] = jumpColumns.map((col) => col.key)
public columnsSchema: Array<ColumnDefinition> = jumpColumns; public columnsSchema: Array<ColumnDefinition> = jumpColumns;
@@ -115,7 +116,9 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
ngOnDestroy() { ngOnDestroy() {
this._jumps.unsubscribe(); this._jumps.unsubscribe();
//this._jump.unsubscribe(); this._subscriptions.forEach(subscription => {
subscription.unsubscribe();
});
} }
ngAfterContentChecked() { ngAfterContentChecked() {
@@ -155,6 +158,9 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
const jumps$: Observable<JumpList> = this._jumpsService.query(this._query); const jumps$: Observable<JumpList> = this._jumpsService.query(this._query);
this._jumps = jumps$.subscribe({ this._jumps = jumps$.subscribe({
next: (data: JumpList) => { next: (data: JumpList) => {
//this.updateJumps(data.jumps);
//this.linkSkydiverIdJumps();
//this.getKmlJumpFiles(data.jumps);
this.loading = false; this.loading = false;
this.jumps = new MatTableDataSource<Jump>(data.jumps); this.jumps = new MatTableDataSource<Jump>(data.jumps);
this.jumpsCount = data.jumpsCount; this.jumpsCount = data.jumpsCount;
@@ -170,12 +176,150 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
}); });
} }
linkSkydiverIdJumps() {
const config: JumpListConfig = { type: 'all', filters: {} } as JumpListConfig;
config.filters.limit = 200;
config.filters.offset = 0;
config.filters.dateRangeStart = '2024-01-01 00:00:00';
//const year = new Date().getFullYear();
//config.filters.dateRangeEnd = `${year}-12-31 23:59:59`;
config.filters.dateRangeEnd = '2024-12-31 23:59:59';
//console.log(config.filters);
const subscription: Subscription = this._jumpsService.getAllByDay(config)
.pipe(take(1))
.subscribe({
next: (days) => {
this._resetErrors();
//console.log('getAllByDay : ', days);
days.forEach(day => {
this.loadSkydiverIdJumps(day, config);
});
},
error: (err) => {
this.errors = err;
}
});
this._subscriptions.push(subscription);
}
loadSkydiverIdJumps(day: JumpByDay, config: JumpListConfig) {
config.filters.dateRangeStart = `${day.jumps[0].date!.substring(0, 10)} 00:00:00`;
config.filters.dateRangeEnd = `${day.jumps[0].date!.substring(0, 10)} 23:59:59`;
const subscription: Subscription = this._jumpsService.getAllFromSkydiverIdApi(config)
.pipe(take(1))
.subscribe({
next: (data) => {
if (data.items.length) {
const diff = day.count - data.items.length;
if (diff < 0) {
console.error(`${diff} ${diff > 1 ? 'données' : 'donnée'} altimètre de trop le ${day.jumps[0].date!.substring(0, 10)}`);
} else {
if (diff > 0) {
console.error(`${diff}/${day.count} ${diff > 1 ? 'données altimètre manquantes' : 'donnée altimètre manquante'} le ${day.jumps[0].date!.substring(0, 10)}`, day.jumps, data.items);
}
console.log(`${day.count} ${day.count > 1 ? 'sauts' : 'saut'} le ${data.items[0].date!.substring(0, 10)}`, day.jumps.sort((a, b) => b.numero! - a.numero!), data.items);
data.items.map((jump, index) => {
const file: JumpFile = {
name: jump.name,
path: `/Volumes/Storage/Skydive/X2_Logs/${day.jumps[index].date!.substring(0, 4)}/`,
type: JumpFileType.CSV
} as JumpFile;
this._subscriptions.push(
this._jumpsService.saveX2Data(day.jumps[index].slug!, file, jump)
.pipe(take(1))
.subscribe({
next: (jump) => {
this._resetErrors();
console.log(`Le fichier ${file.name} a été ajouté au saut n°${jump.numero} !`, 'OK');
},
error: (err) => {
this.errors = err;
}
})
);
return jump;
});
}
//console.log(`${day.count} ${day.count > 1 ? 'sauts' : 'saut'} le ${day.jumps[0].date!.substring(0, 10)}`);
//console.log(`${data.items.length} ${data.items.length > 1 ? 'sauts' : 'saut'} le ${data.items[0].date!.substring(0, 10)}`, data);
} else {
console.info('Aucune données altimètre pour le ', day.jumps[0].date!.substring(0, 10));
}
},
error: (err) => {
console.error('getAllFromSkydiverIdApi error');
this.errors = err;
}
});
this._subscriptions.push(subscription);
}
updateJumps(jumps: Array<Jump>) {
jumps.map(jump => {
jump.files = [];
if (jump.dossier !== "" && jump.video !== "") {
const file: JumpFile = {
name: jump.video!,
path: jump.dossier!,
type: JumpFileType.VIDEO
} as JumpFile;
this._subscriptions.push(
this._jumpsService.saveFile(jump.slug, file)
.pipe(take(1))
.subscribe({
next: (file) => {
this._resetErrors();
jump.files.push(file);
console.log(`Le fichier ${file.name} a été ajouté au saut n°${jump.numero} !`, 'OK');
},
error: (err) => {
this.errors = err;
}
})
);
}
return jump;
});
}
getKmlJumpFiles(jumps: Array<Jump>) {
jumps.map(jump => {
if (jump.x2data !== null) {
//console.log(`Le fichier ${jump.x2data.name} existe pour le saut n°${jump.numero} !`, `https://skydiver.id/api/jump/${jump.x2data.id}/kml`);
const file: JumpFile = {
name: jump.x2data.name.substring(0, -4) + '.kml',
path: `/X2_Kmls/${jump.date.substring(0, 4)}/`,
type: JumpFileType.KML
} as JumpFile;
this._subscriptions.push(
this._jumpsService.saveKml(jump.slug, file)
.pipe(take(1))
.subscribe({
next: (file) => {
this._resetErrors();
console.log(`Le fichier ${file.path}${file.name} a été ajouté au saut n°${jump.numero} !`, 'OK');
},
error: (err) => {
this.errors = err;
}
})
);
/*
*/
}
return jump;
});
}
deleteJump(slug: string) { deleteJump(slug: string) {
this.isDeleting = true; this.isDeleting = true;
this._jumpsService.destroy(slug).pipe(take(1)) this._subscriptions.push(
this._jumpsService.destroy(slug)
.pipe(take(1))
.subscribe(() => { .subscribe(() => {
this.router.navigateByUrl('/'); this.router.navigateByUrl('/');
}); })
);
} }
openDeleteDialog(jump: Jump): void { openDeleteDialog(jump: Jump): void {
@@ -183,12 +327,15 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
width: '60vw', width: '60vw',
data: jump data: jump
}); });
dialogRef.afterClosed().pipe(take(1)) this._subscriptions.push(
dialogRef.afterClosed()
.pipe(take(1))
.subscribe(result => { .subscribe(result => {
if (result != undefined) { if (result != undefined) {
this._onDelete(result.slug); this._onDelete(result.slug);
} }
}); })
);
} }
openEditDialog(jump: Jump): void { openEditDialog(jump: Jump): void {
@@ -196,12 +343,15 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
width: '70vw', width: '70vw',
data: jump data: jump
}); });
dialogRef.afterClosed().pipe(take(1)) this._subscriptions.push(
dialogRef.afterClosed()
.pipe(take(1))
.subscribe(result => { .subscribe(result => {
if (result != undefined) { if (result != undefined) {
this._onEdit(result); this._onEdit(result);
} }
}); })
);
} }
openViewDialog(jump: Jump): void { openViewDialog(jump: Jump): void {
@@ -230,6 +380,7 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
private _onDelete(slug: string): void { private _onDelete(slug: string): void {
try { try {
this.isDeleting = true; this.isDeleting = true;
this._subscriptions.push(
this._jumpsService.destroy(slug) this._jumpsService.destroy(slug)
.pipe(take(1)) .pipe(take(1))
.subscribe({ .subscribe({
@@ -243,7 +394,8 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
error: (err) => { error: (err) => {
this.errors = err; this.errors = err;
} }
}); })
);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@@ -252,6 +404,7 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
private _onEdit(jump: Jump): void { private _onEdit(jump: Jump): void {
try { try {
this.isSubmitting = true; this.isSubmitting = true;
this._subscriptions.push(
this._jumpsService.update(jump) this._jumpsService.update(jump)
.pipe(take(1)) .pipe(take(1))
.subscribe({ .subscribe({
@@ -266,7 +419,8 @@ export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChe
this.errors = err; this.errors = err;
this.isSubmitting = false; this.isSubmitting = false;
} }
}); })
);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@@ -1,6 +1,6 @@
<mat-toolbar class="topbar bg-primary"> <mat-toolbar class="topbar bg-primary">
<div class="navbar-header"> <div class="navbar-header">
<a class="navbar-brand" href="index.html"> <a class="navbar-brand" href="/">
<span class="logo"> <span class="logo">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="190px" height="33px" viewBox="0 0 190 33" enable-background="new 0 0 190 33" xml:space="preserve"> <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="190px" height="33px" viewBox="0 0 190 33" enable-background="new 0 0 190 33" xml:space="preserve">
<path id="Jumper_2" fill="#FFFFFF" <path id="Jumper_2" fill="#FFFFFF"
@@ -281,8 +281,8 @@
<app-layout-footer></app-layout-footer> <app-layout-footer></app-layout-footer>
</mat-sidenav> </mat-sidenav>
<mat-sidenav-content class="page-wrapper"> <mat-sidenav-content class="page-wrapper">
<div class="page-content"> <!--<div class="page-content">-->
<router-outlet><!--<app-spinner></app-spinner>--></router-outlet> <router-outlet><!--<app-spinner></app-spinner>--></router-outlet>
</div> <!--</div>-->
</mat-sidenav-content> </mat-sidenav-content>
</mat-sidenav-container> </mat-sidenav-container>
@@ -1,5 +1,4 @@
import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core'; import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { Router, RouterOutlet, RouterModule } from '@angular/router'; import { Router, RouterOutlet, RouterModule } from '@angular/router';
import { MediaMatcher } from '@angular/cdk/layout'; import { MediaMatcher } from '@angular/cdk/layout';
import { MatBadgeModule } from '@angular/material/badge'; import { MatBadgeModule } from '@angular/material/badge';
File diff suppressed because one or more lines are too long
+10 -2
View File
@@ -13,12 +13,17 @@ const MENUITEMS: Menu[] = [
{ state: 'logbook', type: 'link', name: 'Carnet de saut', icon: 'assignment_turned_in', desc: '' }, { state: 'logbook', type: 'link', name: 'Carnet de saut', icon: 'assignment_turned_in', desc: '' },
{ state: 'aeronef', type: 'link', name: 'Aéronefs', icon: 'flight', desc: '' }, { state: 'aeronef', type: 'link', name: 'Aéronefs', icon: 'flight', desc: '' },
{ state: 'canopy', type: 'link', name: 'Voiles', icon: 'paragliding', desc: '' }, { state: 'canopy', type: 'link', name: 'Voiles', icon: 'paragliding', desc: '' },
{ state: 'dropzone', type: 'link', name: 'Dropzones', icon: 'adjust', desc: '' }, { state: 'dropzone', type: 'link', name: 'Drop Zones', icon: 'flight_takeoff', desc: '' },
{ state: 'jump', type: 'link', name: 'Sauts', icon: 'cloud_download', desc: '' }, { state: 'jumps', type: 'link', name: 'Sauts', icon: 'cloud', desc: '' },
{ state: 'calculator', type: 'link', name: 'Taille de voile', icon: 'calculate', desc: '' }, { state: 'calculator', type: 'link', name: 'Taille de voile', icon: 'calculate', desc: '' },
{ state: 'qcm', type: 'menu', name: 'QCM Brevets', icon: 'rule_folder', desc: '' } { state: 'qcm', type: 'menu', name: 'QCM Brevets', icon: 'rule_folder', desc: '' }
]; ];
const MENUPJUMP: Menu[] = [
{ state: '#', type: 'link', name: 'Modifier', icon: 'edit', desc: '', color: 'primary' },
{ state: '#', type: 'link', name: 'Supprimer', icon: 'delete', desc: '', color: 'raspberry' }
];
const MENUQCM: Menu[] = [ const MENUQCM: Menu[] = [
{ state: 'bpa', type: 'link', name: 'BPA', icon: 'rule' }, { state: 'bpa', type: 'link', name: 'BPA', icon: 'rule' },
{ state: 'c', type: 'link', name: 'Brevet C', icon: 'rule' }, { state: 'c', type: 'link', name: 'Brevet C', icon: 'rule' },
@@ -63,4 +68,7 @@ export class MenuItems {
getMenuPanel(): Menu[] { getMenuPanel(): Menu[] {
return MENUPANEL; return MENUPANEL;
} }
getMenuJump(): Menu[] {
return MENUPJUMP;
}
} }
+1 -1
View File
@@ -6,7 +6,6 @@ export * from './calculator.model';
export * from './canopy.model'; export * from './canopy.model';
export * from './canopysize.model'; export * from './canopysize.model';
export * from './chart.model'; export * from './chart.model';
export * from './reducerow.model';
export * from './dropzone.model'; export * from './dropzone.model';
export * from './errors.model'; export * from './errors.model';
export * from './jump.model'; export * from './jump.model';
@@ -14,4 +13,5 @@ export * from './jump-list.model';
export * from './jump-list-config.model'; export * from './jump-list-config.model';
export * from './profile.model'; export * from './profile.model';
export * from './qcm.model'; export * from './qcm.model';
export * from './shared.model';
export * from './user.model'; export * from './user.model';
@@ -33,4 +33,6 @@ export interface JumpListFilters {
hauteurRangeEnd?: number; hauteurRangeEnd?: number;
yearRangeStart?: number; yearRangeStart?: number;
yearRangeEnd?: number; yearRangeEnd?: number;
dateRangeStart?: string;
dateRangeEnd?: string;
} }
+97
View File
@@ -1,6 +1,7 @@
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { Profile } from './profile.model'; import { Profile } from './profile.model';
import { PageLinks, PageMeta } from './shared.model';
export interface Jump { export interface Jump {
slug: string; slug: string;
@@ -23,12 +24,26 @@ export interface Jump {
zone?: string; zone?: string;
dossier?: string; dossier?: string;
video?: string; video?: string;
files: Array<JumpFile>;
x2data: X2Data;
author: Profile; author: Profile;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
//isEdit?: boolean; //isEdit?: boolean;
isSelected?: boolean; isSelected?: boolean;
} }
export enum JumpFileType {
CSV = "csv",
IMAGE = "image",
KML = "kml",
VIDEO = "video",
}
export interface JumpFile {
slug: string;
name: string;
path: string;
type: JumpFileType;
}
export interface JumpForm { export interface JumpForm {
date: FormControl<string>; date: FormControl<string>;
@@ -69,6 +84,11 @@ export interface JumpByCategorie {
count: number; count: number;
} }
export interface JumpByDay {
jumps: Array<Partial<Jump>>;
count: number;
}
export interface JumpByModule { export interface JumpByModule {
categorie: string; categorie: string;
module: string; module: string;
@@ -88,12 +108,89 @@ export interface JumpYears {
count: number; count: number;
} }
export interface SkydiverIdJumps {
items: Array<X2Data>;
_links: PageLinks;
_meta: PageMeta;
}
export interface X2Data {
id: string;
name: string;
type: string;
description: string;
landingZone: string;
speed: {
freeFall: {
vertical: {
max: number;
avg: number;
};
horizontal: {
max: number;
avg: number;
};
};
underCanopy: {
vertical: {
max: number;
avg: number;
};
horizontal: {
max: number;
avg: number;
};
};
};
glideRatio: {
freeFall: {
max: number;
avg: number;
};
underCanopy: {
max: number;
avg: number;
};
};
duration: {
freeFall: number;
underCanopy: number;
};
distance: {
exitFromLandingZone: number;
totalFlying: number;
totalHorizontal: number;
};
altitude: {
exit: number;
deployment: number;
};
cutaway: string;
malfunctions:Array<
{
name: string;
type: string;
}
>;
date: string;
createdOn: string;
}
export interface JumpPageData {
jump: Jump;
prevJump: Jump;
nextJump: Jump;
_links: PageLinks;
_meta: PageMeta;
}
export interface JumpsPageData { export interface JumpsPageData {
jumpsByCategory: Array<JumpByCategorie>; jumpsByCategory: Array<JumpByCategorie>;
jumpsByDate: Array<JumpByDate>; jumpsByDate: Array<JumpByDate>;
jumpsByDateByModule: Array<JumpByDateByModule>; jumpsByDateByModule: Array<JumpByDateByModule>;
jumpsByModule: Array<JumpByModule>; jumpsByModule: Array<JumpByModule>;
jumpsByYears: Array<JumpYears>; jumpsByYears: Array<JumpYears>;
//jumpsFromSkydiverIdApi: SkydiverIdJumps;
lastjump: Jump; lastjump: Jump;
/*yearsCount: number; /*yearsCount: number;
jumpsByCategoryCount: number; jumpsByCategoryCount: number;
+1
View File
@@ -7,4 +7,5 @@ export interface Menu {
icon: string; icon: string;
desc?: string; desc?: string;
badge?: Badge[]; badge?: Badge[];
color?: string;
} }
+30
View File
@@ -0,0 +1,30 @@
export interface PageLinks {
self: {
href: string;
};
first: {
href: string;
};
last: {
href: string;
};
prev: {
href: string;
};
next: {
href: string;
};
}
export interface PageMeta {
totalCount: number;
pageCount: number;
currentPage: number;
perPage: number;
}
export interface ReduceRow {
key: string[];
value: number;
}
@@ -2,10 +2,10 @@ import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, ResolveFn } from '@angular/router'; import { ActivatedRouteSnapshot, ResolveFn } from '@angular/router';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { Jump } from 'src/app/core/models'; import { JumpPageData } from 'src/app/core/models';
import { JumpsService } from 'src/app/core/services'; import { JumpsService } from 'src/app/core/services';
export const jumpResolver: ResolveFn<Jump> = ( export const jumpResolver: ResolveFn<JumpPageData> = (
route: ActivatedRouteSnapshot route: ActivatedRouteSnapshot
) => { ) => {
return inject(JumpsService).get(route.params['slug']).pipe(take(1)); return inject(JumpsService).get(route.params['slug']).pipe(take(1));
@@ -23,7 +23,9 @@ export class ApplicationsService {
return this.apiService return this.apiService
.get( .get(
'/applications' + ((config.type === 'feed') ? '/feed' : ''), '/applications' + ((config.type === 'feed') ? '/feed' : ''),
new HttpParams({ fromObject: <any>params }) new HttpParams({ fromObject: <{
[param: string]: string | number | boolean | readonly (string | number | boolean)[];
}>params })
); );
} }
+6 -10
View File
@@ -27,8 +27,8 @@ export class CalculatorService {
private _getTableLine(weight: number): number { private _getTableLine(weight: number): number {
let line: number; let line: number;
let min = 60; const min = 60;
let max = 110; const max = 110;
if (weight < min) { if (weight < min) {
line = min; line = min;
} else if (weight > max) { } else if (weight > max) {
@@ -52,14 +52,14 @@ export class CalculatorService {
/* const maxRange = 2000; */ /* const maxRange = 2000; */
/* DT48 - 08 février 2024 */ /* DT48 - 08 février 2024 */
const maxRange = 1600; const maxRange = 1600;
let result: CalculatorResult = { const result: CalculatorResult = {
min: 59, min: 59,
min11: 34 min11: 34
}; };
if (jumps > maxRange) { if (jumps > maxRange) {
return result; return result;
} }
let index: number = (this._getTableLine(weight) - this._getTableLine(0)); const index: number = (this._getTableLine(weight) - this._getTableLine(0));
result.min = weightSizes[index].ranges[this._getTableColumn(jumps)].value!; result.min = weightSizes[index].ranges[this._getTableColumn(jumps)].value!;
result.min11 = this._reduceLimit(result.min, 11); result.min11 = this._reduceLimit(result.min, 11);
@@ -71,16 +71,14 @@ export class CalculatorService {
} }
public getCanopySizes(weight: number, reduce = false): number[] { public getCanopySizes(weight: number, reduce = false): number[] {
var data: number[] = [];
if (weight < 60) { if (weight < 60) {
weight = 60; weight = 60;
} }
if (weight > 110) { if (weight > 110) {
weight = 110; weight = 110;
} }
if (weight >= 60 && weight <= 110) {
const line = (weight - this._getTableLine(0)); const line = (weight - this._getTableLine(0));
let data = weightSizes[line]; const data = weightSizes[line];
if (reduce) { if (reduce) {
return data.ranges.map((range: WeightSizeRange): number => { return data.ranges.map((range: WeightSizeRange): number => {
return Math.ceil(range.value! * (100 - 11) / 100); return Math.ceil(range.value! * (100 - 11) / 100);
@@ -91,15 +89,13 @@ export class CalculatorService {
}); });
} }
} }
return data;
}
public getCharge(canopySize: number, nakedWeight: number, equipementWeight: number): number { public getCharge(canopySize: number, nakedWeight: number, equipementWeight: number): number {
return (((nakedWeight + equipementWeight) * this.coeffKgLbs) / canopySize); return (((nakedWeight + equipementWeight) * this.coeffKgLbs) / canopySize);
} }
public getRangeNum(jumps: number): number { public getRangeNum(jumps: number): number {
let data: WeightSizeRange[] = weightSizes[0].ranges; const data: WeightSizeRange[] = weightSizes[0].ranges;
let num: number = 1; let num: number = 1;
/* DT48 - 13 mars 2020 */ /* DT48 - 13 mars 2020 */
/* const maxRange = 2000; */ /* const maxRange = 2000; */
+2 -2
View File
@@ -11,8 +11,8 @@ export class JumpTableService {
_jumpRefreshBS = new BehaviorSubject<boolean>(false); _jumpRefreshBS = new BehaviorSubject<boolean>(false);
constructor() { constructor() {
this.tableRefresh = false;; this.tableRefresh = false;
this.jumpRefresh = false;; this.jumpRefresh = false;
//this._tableRefreshBS.next(this.tableRefresh); //this._tableRefreshBS.next(this.tableRefresh);
//this._jumpRefreshBS.next(this.jumpRefresh); //this._jumpRefreshBS.next(this.jumpRefresh);
+55 -23
View File
@@ -4,7 +4,11 @@ import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { ApiService } from './api.service'; import { ApiService } from './api.service';
import { Jump, JumpByCategorie, JumpByDate, JumpByModule, JumpList, JumpListConfig, JumpListFilters } from 'src/app/core/models'; import {
Jump, JumpByCategorie, JumpByDate, JumpByDay, JumpByModule, JumpFile,
JumpList, JumpListConfig, JumpListFilters, JumpPageData,
X2Data, SkydiverIdJumps
} from 'src/app/core/models';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class JumpsService { export class JumpsService {
@@ -44,52 +48,67 @@ export class JumpsService {
*/ */
const params: JumpListFilters = {} as JumpListFilters; const params: JumpListFilters = {} as JumpListFilters;
Object.assign(params, config.filters); Object.assign(params, config.filters);
return this.apiService return this.apiService
.get( .get(
'/jumps' + ((config.type === 'feed') ? '/feed' : ''), '/jumps' + ((config.type === 'feed') ? '/feed' : ''),
new HttpParams({ fromObject: <any>params }) new HttpParams({ fromObject: <{
[param: string]: string | number | boolean | readonly (string | number | boolean)[];
}>params })
); );
} }
get(slug: string): Observable<Jump> { get(slug: string): Observable<JumpPageData> {
return this.apiService.get(`/jumps/${slug}`) /* return this.apiService.get(`/jumps/${slug}`)
.pipe(map(data => data.jump)); .pipe(map(data => {
return { jump: data.jump, prevJump: data.prevJump, nextJump: data.nextJump };
})); */
return this.apiService.get(`/jumps/${slug}`);
} }
getAll(): Observable<[Jump]> { getAll(): Observable<Array<Jump>> {
return this.apiService.get('/jumps') return this.apiService.get('/jumps').pipe(map(data => data.jumps));
.pipe(map(data => data.jumps));
} }
getAllByDate(): Observable<Array<JumpByDate>> { getAllByDate(): Observable<Array<JumpByDate>> {
return this.apiService.get('/jumps/allByDate') return this.apiService.get('/jumps/allByDate').pipe(map(data => data.jumps));
.pipe(map(data => data.jumps));
} }
getAllByCategorie(): Observable<Array<JumpByCategorie>> { getAllByCategorie(): Observable<Array<JumpByCategorie>> {
return this.apiService.get('/jumps/allByCategorie') return this.apiService.get('/jumps/allByCategorie').pipe(map(data => data.jumps));
.pipe(map(data => data.jumps)); }
getAllByDay(config: JumpListConfig): Observable<Array<JumpByDay>> {
const params: JumpListFilters = {} as JumpListFilters;
Object.assign(params, config.filters);
return this.apiService.get('/jumps/allByDay', new HttpParams({ fromObject: <{
[param: string]: string | number | boolean | readonly (string | number | boolean)[];
}>params }))
.pipe(map(data => data.days));
} }
getAllByModule(): Observable<Array<JumpByModule>> { getAllByModule(): Observable<Array<JumpByModule>> {
return this.apiService.get('/jumps/allByModule') return this.apiService.get('/jumps/allByModule').pipe(map(data => data.jumps));
}
getAllFromSkydiverIdApi(config: JumpListConfig): Observable<SkydiverIdJumps> {
const params: JumpListFilters = {} as JumpListFilters;
Object.assign(params, config.filters);
return this.apiService.get('/jumps/allFromSkydiverIdApi', new HttpParams({ fromObject: <{
[param: string]: string | number | boolean | readonly (string | number | boolean)[];
}>params }))
.pipe(map(data => data.jumps)); .pipe(map(data => data.jumps));
} }
getLastJump(): Observable<Jump> { getLastJump(): Observable<Jump> {
return this.apiService.get('/jumps/last') return this.apiService.get('/jumps/last').pipe(map(data => data.jump));
.pipe(map(data => data.jump));
} }
create(jump: Jump): Observable<Jump> { create(jump: Jump): Observable<Jump> {
return this.apiService.post('/jumps/', { jump: jump }) return this.apiService.post('/jumps/', { jump: jump }).pipe(map(data => data.jump));
.pipe(map(data => data.jump));
} }
update(jump: Jump): Observable<Jump> { update(jump: Jump): Observable<Jump> {
return this.apiService.put(`/jumps/${jump.slug}`, { jump: jump }) return this.apiService.put(`/jumps/${jump.slug}`, { jump: jump }).pipe(map(data => data.jump));
.pipe(map(data => data.jump));
} }
destroy(slug: string) { destroy(slug: string) {
@@ -99,14 +118,27 @@ export class JumpsService {
save(jump: Jump): Observable<Jump> { save(jump: Jump): Observable<Jump> {
// If we're updating an existing jump // If we're updating an existing jump
if (jump.slug) { if (jump.slug) {
return this.apiService.put('/jumps/' + jump.slug, { jump: jump }) return this.apiService.put(`/jumps/${jump.slug}`, { jump: jump }).pipe(map(data => data.jump));
.pipe(map(data => data.jump));
// Otherwise, create a new jump // Otherwise, create a new jump
} else { } else {
return this.apiService.post('/jumps/', { jump: jump }) return this.apiService.post('/jumps/', { jump: jump }).pipe(map(data => data.jump));
}
}
saveX2Data(slug: string, file: JumpFile, data: X2Data): Observable<Jump> {
return this.apiService.post(`/jumps/data/${slug}`, { file: file, data: data })
.pipe(map(data => data.jump)); .pipe(map(data => data.jump));
} }
saveFile(slug: string, file: JumpFile): Observable<JumpFile> {
return this.apiService.post(`/jumps/file/${slug}`, { file: file })
.pipe(map(data => data.file));
}
saveKml(slug: string, file: JumpFile): Observable<JumpFile> {
return this.apiService.post(`/jumps/kml/${slug}`, { file: file })
.pipe(map(data => data.file));
} }
getAeronefs() { getAeronefs() {
+1 -1
View File
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BubbleDataPoint, Chart, ChartTypeRegistry, Point } from 'chart.js'; import { Chart } from 'chart.js';
import { Configuration } from 'ng-chartist'; import { Configuration } from 'ng-chartist';
import { BarConfig, CircleConfig, DoughnutConfig, LineConfig } from 'src/app/core/models'; import { BarConfig, CircleConfig, DoughnutConfig, LineConfig } from 'src/app/core/models';
+13 -12
View File
@@ -2,13 +2,13 @@ import { Routes } from '@angular/router';
import { import {
AeronefComponent, CalculatorComponent, CanopyComponent, CredentialsComponent, AeronefComponent, CalculatorComponent, CanopyComponent, CredentialsComponent,
DashboardComponent, DropzoneComponent, JumpComponent, LogbookComponent, DashboardComponent, DropzoneComponent, JumpComponent, JumpsComponent,
ProfileComponent, QcmComponent, SettingsComponent LogbookComponent, ProfileComponent, QcmComponent, SettingsComponent
} from 'src/app/components'; } from 'src/app/components';
import { authGuard } from 'src/app/core/guards'; import { authGuard } from 'src/app/core/guards';
import { import {
aeronefsPageResolver, authResolver, canopiesPageResolver, dropZonesPageResolver, aeronefsPageResolver, authResolver, canopiesPageResolver, dropZonesPageResolver,
jumpsPageResolver, lastjumpResolver, profileResolver, qcmResolver jumpResolver, jumpsPageResolver, lastjumpResolver, profileResolver, qcmResolver
} from 'src/app/core/resolvers'; } from 'src/app/core/resolvers';
export const AUTH_ROUTES: Routes = [ export const AUTH_ROUTES: Routes = [
@@ -53,19 +53,20 @@ export const AUTH_ROUTES: Routes = [
} }
}, },
{ {
path: 'jump', path: 'jumps',
component: JumpsComponent,
canActivate: [ authGuard ],
resolve: {
pageData: jumpsPageResolver
}
},
{
path: 'jump/:slug',
component: JumpComponent, component: JumpComponent,
canActivate: [ authGuard ], canActivate: [ authGuard ],
resolve: { resolve: {
jumpsPageData: jumpsPageResolver pageData: jumpResolver
},
children: [
{
path: ':slug',
component: JumpComponent,
canActivate: [ authGuard ]
} }
]
}, },
{ {
path: 'logbook', path: 'logbook',
+22
View File
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 982 241" style="width: max(calc(700 * min(100vw, 2560px) / 1470), 350px); height: auto;">
<g clip-path="url(#wordmark_a)">
<path fill="url(#wordmark_b)" d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.603 64.311h-44.432Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.038-7.037 7.038 0 12.847 1.145 17.348 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.746 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.619 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.01 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.091 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.815 58.815 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"></path>
<path fill="url(#wordmark_c)" d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.603 64.311h-44.432Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.038-7.037 7.038 0 12.847 1.145 17.348 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.746 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.619 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.01 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.091 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.815 58.815 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"></path>
</g>
<defs _ngcontent-ng-c433524483="">
<radialGradient id="wordmark_c" cx="0" cy="0" r="1" gradientTransform="rotate(118.122 171.182 60.81) scale(205.794)" gradientUnits="userSpaceOnUse">
<stop stop-color="#FF41F8"></stop>
<stop offset=".707" stop-color="#FF41F8" stop-opacity=".5"></stop>
<stop offset="1" stop-color="#FF41F8" stop-opacity="0"></stop>
</radialGradient>
<linearGradient id="wordmark_b" x1="0" x2="982" y1="192" y2="192" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0060B"></stop>
<stop offset="0" stop-color="#F0070C"></stop>
<stop offset=".526" stop-color="#CC26D5"></stop>
<stop offset="1" stop-color="#7702FF"></stop>
</linearGradient>
<clipPath id="wordmark_a">
<path fill="#fff" d="M0 0h982v239H0z"></path>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 9.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

-5
View File
@@ -13,14 +13,9 @@ html {
background: $white; background: $white;
.page-wrapper{ .page-wrapper{
height:calc(100vh - 64px); height:calc(100vh - 64px);
.page-content{
padding-bottom: 30px;
.content {
padding: 1.5rem; padding: 1.5rem;
} }
} }
}
}
body { body {
height: 100%; height: 100%;
margin: 0; margin: 0;