Ajout des sources
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
Directive,
|
||||
OnInit,
|
||||
AfterViewInit,
|
||||
AfterContentChecked
|
||||
} from '@angular/core';
|
||||
import { Router, NavigationEnd } from '@angular/router';
|
||||
|
||||
import { AccordionLinkDirective } from './accordionlink.directive';
|
||||
import { filter } from 'rxjs/operators';
|
||||
|
||||
@Directive({
|
||||
selector: '[appAccordion]',
|
||||
standalone: true
|
||||
})
|
||||
export class AccordionDirective implements AfterContentChecked {
|
||||
protected navlinks: Array<AccordionLinkDirective> = [];
|
||||
|
||||
closeOtherLinks(selectedLink: AccordionLinkDirective): void {
|
||||
this.navlinks.forEach((link: AccordionLinkDirective) => {
|
||||
if (link !== selectedLink) {
|
||||
link.selected = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addLink(link: AccordionLinkDirective): void {
|
||||
this.navlinks.push(link);
|
||||
}
|
||||
|
||||
removeGroup(link: AccordionLinkDirective): void {
|
||||
const index = this.navlinks.indexOf(link);
|
||||
if (index !== -1) {
|
||||
this.navlinks.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
checkOpenLinks() {
|
||||
this.navlinks.forEach((link: AccordionLinkDirective) => {
|
||||
if (link.group) {
|
||||
const routeUrl = this.router.url;
|
||||
const currentUrl = routeUrl.split('/');
|
||||
if (currentUrl.indexOf(link.group) > 0) {
|
||||
link.selected = true;
|
||||
this.closeOtherLinks(link);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterContentChecked(): void {
|
||||
this.router.events
|
||||
.pipe(filter(event => event instanceof NavigationEnd))
|
||||
.subscribe(e => this.checkOpenLinks());
|
||||
}
|
||||
|
||||
constructor(private router: Router) {
|
||||
setTimeout(() => this.checkOpenLinks());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Directive, HostListener, Inject } from '@angular/core';
|
||||
|
||||
import { AccordionLinkDirective } from './accordionlink.directive';
|
||||
|
||||
@Directive({
|
||||
selector: '[appAccordionToggle]',
|
||||
standalone: true
|
||||
})
|
||||
export class AccordionAnchorDirective {
|
||||
protected navlink: AccordionLinkDirective;
|
||||
|
||||
constructor(@Inject(AccordionLinkDirective) navlink: AccordionLinkDirective) {
|
||||
this.navlink = navlink;
|
||||
}
|
||||
|
||||
@HostListener('click', ['$event'])
|
||||
onClick(e: any) {
|
||||
this.navlink.toggle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
Directive,
|
||||
HostBinding,
|
||||
Inject,
|
||||
Input,
|
||||
OnInit,
|
||||
OnDestroy
|
||||
} from '@angular/core';
|
||||
|
||||
import { AccordionDirective } from './accordion.directive';
|
||||
|
||||
@Directive({
|
||||
selector: '[appAccordionLink]',
|
||||
standalone: true
|
||||
})
|
||||
export class AccordionLinkDirective implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
public group: any;
|
||||
|
||||
@HostBinding('class.selected')
|
||||
@Input()
|
||||
get selected(): boolean {
|
||||
return this._selected;
|
||||
}
|
||||
|
||||
set selected(value: boolean) {
|
||||
this._selected = value;
|
||||
if (value) {
|
||||
this.nav.closeOtherLinks(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected _selected: boolean = false;
|
||||
protected nav: AccordionDirective;
|
||||
|
||||
constructor(@Inject(AccordionDirective) nav: AccordionDirective) {
|
||||
this.nav = nav;
|
||||
}
|
||||
|
||||
ngOnInit(): any {
|
||||
this.nav.addLink(this);
|
||||
}
|
||||
|
||||
ngOnDestroy(): any {
|
||||
this.nav.removeGroup(this);
|
||||
}
|
||||
|
||||
toggle(): any {
|
||||
this.selected = !this.selected;
|
||||
}
|
||||
}
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
export * from './accordionanchor.directive';
|
||||
export * from './accordionlink.directive';
|
||||
export * from './accordion.directive';
|
||||
@@ -0,0 +1,17 @@
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
||||
export function ConfirmedValidator(controlName: string, matchingControlName: string){
|
||||
return (formGroup: FormGroup) => {
|
||||
const control = formGroup.controls[controlName];
|
||||
const matchingControl = formGroup.controls[matchingControlName];
|
||||
|
||||
if (matchingControl.errors && !matchingControl.errors['confirmedValidator']) {
|
||||
return;
|
||||
}
|
||||
if (control.value !== matchingControl.value) {
|
||||
matchingControl.setErrors({ confirmedValidator: true });
|
||||
} else {
|
||||
matchingControl.setErrors(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './jump-add.dialog';
|
||||
export * from './jump-delete.dialog';
|
||||
export * from './jump-edit.dialog';
|
||||
@@ -0,0 +1,90 @@
|
||||
<h2 mat-dialog-title>Ajouter un saut</h2>
|
||||
<mat-divider class="mt-2 mb-1 border-light-subtle"></mat-divider>
|
||||
<mat-dialog-content>
|
||||
<div fxLayout="row wrap">
|
||||
<!-- column -->
|
||||
<div fxFlex.gt-lg="25" fxFlex.gt-md="50" fxFlex.gt-xs="100" fxFlex="100" class="px-2">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Numéro</mat-label>
|
||||
<input matInput [(ngModel)]="jump.numero">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Date</mat-label>
|
||||
<input matInput [(ngModel)]="jump.date">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Lieu</mat-label>
|
||||
<input matInput [(ngModel)]="jump.lieu">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Aeronef</mat-label>
|
||||
<input matInput [(ngModel)]="jump.aeronef">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Hauteur</mat-label>
|
||||
<input matInput [(ngModel)]="jump.hauteur">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Voile</mat-label>
|
||||
<input matInput [(ngModel)]="jump.voile">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Taille</mat-label>
|
||||
<input matInput [(ngModel)]="jump.taille">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Categorie</mat-label>
|
||||
<input matInput [(ngModel)]="jump.categorie">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Module</mat-label>
|
||||
<input matInput [(ngModel)]="jump.module">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<!-- column -->
|
||||
<div fxFlex.gt-lg="25" fxFlex.gt-md="50" fxFlex.gt-xs="100" fxFlex="100" class="px-2">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Participants</mat-label>
|
||||
<input matInput [(ngModel)]="jump.participants">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Programme</mat-label>
|
||||
<input matInput [(ngModel)]="jump.programme">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Oaci</mat-label>
|
||||
<input matInput [(ngModel)]="jump.oaci">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Imat</mat-label>
|
||||
<input matInput [(ngModel)]="jump.imat">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Accessoires</mat-label>
|
||||
<input matInput [(ngModel)]="jump.accessoires">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Zone</mat-label>
|
||||
<input matInput [(ngModel)]="jump.zone">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Dossier</mat-label>
|
||||
<input matInput [(ngModel)]="jump.dossier">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Video</mat-label>
|
||||
<input matInput [(ngModel)]="jump.video">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<!-- column -->
|
||||
<div fxFlex.gt-lg="25" fxFlex.gt-md="50" fxFlex.gt-xs="100" fxFlex="100" class="px-2">
|
||||
</div>
|
||||
<!-- column -->
|
||||
<div fxFlex.gt-lg="25" fxFlex.gt-md="50" fxFlex.gt-xs="100" fxFlex="100" class="px-2">
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="onCloseDialog()">Annuler</button>
|
||||
<button mat-button color="primary" [mat-dialog-close]="jump" cdkFocusInitial>Enregistrer</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
|
||||
import { Jump } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-add-dialog',
|
||||
templateUrl: 'jump-add.dialog.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
FormsModule,
|
||||
MatDialogModule, MatFormFieldModule, MatInputModule, MatDividerModule,
|
||||
MatSelectModule, MatOptionModule, MatButtonModule
|
||||
]
|
||||
})
|
||||
export class JumpAddDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<JumpAddDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public jump: Jump
|
||||
) { }
|
||||
|
||||
onCloseDialog(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<h2 mat-dialog-title>Supprimer le saut n°{{jump.numero}}</h2>
|
||||
<mat-divider class="mt-2 mb-1 border-light-subtle"></mat-divider>
|
||||
<mat-dialog-content>
|
||||
<p class="mt-2 mb-3 text-accent">Veuillez confirmer la suppression de ce saut. Attention, cette action est irréversible.</p>
|
||||
<h3>Données du saut :</h3>
|
||||
<div class="mb-3 clearfix">
|
||||
<dl class="dl-horizontal">
|
||||
<dt class="text-lowercase text-bold">numero:</dt>
|
||||
<dd>{{jump.numero}}</dd>
|
||||
<dt class="text-lowercase text-bold">date:</dt>
|
||||
<dd>{{jump.date}}</dd>
|
||||
<dt class="text-lowercase text-bold">lieu:</dt>
|
||||
<dd>{{jump.lieu}}</dd>
|
||||
<dt class="text-lowercase text-bold">oaci:</dt>
|
||||
<dd>{{jump.oaci}}</dd>
|
||||
<dt class="text-lowercase text-bold">aeronef:</dt>
|
||||
<dd>{{jump.aeronef}}</dd>
|
||||
<dt class="text-lowercase text-bold">imat:</dt>
|
||||
<dd>{{jump.imat}}</dd>
|
||||
<dt class="text-lowercase text-bold">hauteur:</dt>
|
||||
<dd>{{jump.hauteur}}</dd>
|
||||
<dt class="text-lowercase text-bold">voile:</dt>
|
||||
<dd>{{jump.voile}}</dd>
|
||||
<dt class="text-lowercase text-bold">taille:</dt>
|
||||
<dd>{{jump.taille}}</dd>
|
||||
<dt class="text-lowercase text-bold">categorie:</dt>
|
||||
<dd>{{jump.categorie}}</dd>
|
||||
<dt class="text-lowercase text-bold">module:</dt>
|
||||
<dd>{{jump.module}}</dd>
|
||||
<dt class="text-lowercase text-bold">participants:</dt>
|
||||
<dd>{{jump.participants}}</dd>
|
||||
<dt class="text-lowercase text-bold">programme:</dt>
|
||||
<dd>{{jump.programme}}</dd>
|
||||
<dt class="text-lowercase text-bold">accessoires:</dt>
|
||||
<dd>{{jump.accessoires}}</dd>
|
||||
<dt class="text-lowercase text-bold">zone:</dt>
|
||||
<dd>{{jump.zone}}</dd>
|
||||
<dt class="text-lowercase text-bold">dossier:</dt>
|
||||
<dd>{{jump.dossier}}</dd>
|
||||
<dt class="text-lowercase text-bold">video:</dt>
|
||||
<dd>{{jump.video}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<hr />
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="onCloseDialog()">Annuler</button>
|
||||
<button mat-button color="accent" [mat-dialog-close]="jump" cdkFocusInitial>Supprimer</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
|
||||
import { Jump } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-delete-dialog',
|
||||
templateUrl: 'jump-delete.dialog.html',
|
||||
standalone: true,
|
||||
imports: [MatDialogModule, MatButtonModule, MatDividerModule]
|
||||
})
|
||||
export class JumpDeleteDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<JumpDeleteDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public jump: Jump
|
||||
) { }
|
||||
|
||||
onCloseDialog(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<h2 mat-dialog-title>Editer un saut</h2>
|
||||
<mat-divider class="mt-2 mb-1 border-light-subtle"></mat-divider>
|
||||
<mat-dialog-content>
|
||||
<div fxLayout="row wrap">
|
||||
<!-- column -->
|
||||
<div fxFlex.gt-lg="50" fxFlex.gt-md="50" fxFlex.gt-xs="100" fxFlex="100" class="px-2">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Numéro</mat-label>
|
||||
<input matInput [(ngModel)]="jump.numero">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Date</mat-label>
|
||||
<input matInput [(ngModel)]="jump.date">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Lieu</mat-label>
|
||||
<input matInput [(ngModel)]="jump.lieu">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Aeronef</mat-label>
|
||||
<input matInput [(ngModel)]="jump.aeronef">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Hauteur</mat-label>
|
||||
<input matInput [(ngModel)]="jump.hauteur">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Voile</mat-label>
|
||||
<input matInput [(ngModel)]="jump.voile">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Taille</mat-label>
|
||||
<input matInput [(ngModel)]="jump.taille">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Categorie</mat-label>
|
||||
<input matInput [(ngModel)]="jump.categorie">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Module</mat-label>
|
||||
<input matInput [(ngModel)]="jump.module">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<!-- column -->
|
||||
<div fxFlex.gt-lg="50" fxFlex.gt-md="50" fxFlex.gt-xs="100" fxFlex="100" class="px-2">
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Participants</mat-label>
|
||||
<input matInput [(ngModel)]="jump.participants">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Programme</mat-label>
|
||||
<input matInput [(ngModel)]="jump.programme">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Oaci</mat-label>
|
||||
<input matInput [(ngModel)]="jump.oaci">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Imat</mat-label>
|
||||
<input matInput [(ngModel)]="jump.imat">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Accessoires</mat-label>
|
||||
<input matInput [(ngModel)]="jump.accessoires">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Zone</mat-label>
|
||||
<input matInput [(ngModel)]="jump.zone">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Dossier</mat-label>
|
||||
<input matInput [(ngModel)]="jump.dossier">
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="fill">
|
||||
<mat-label>Video</mat-label>
|
||||
<input matInput [(ngModel)]="jump.video">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="onCloseDialog()">Annuler</button>
|
||||
<button mat-button color="primary" [mat-dialog-close]="jump" cdkFocusInitial>Enregistrer</button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
|
||||
import { Jump } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-edit-dialog',
|
||||
templateUrl: 'jump-edit.dialog.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
FormsModule,
|
||||
MatDialogModule, MatFormFieldModule, MatInputModule, MatDividerModule,
|
||||
MatSelectModule, MatOptionModule, MatIconModule, MatButtonModule
|
||||
]
|
||||
})
|
||||
export class JumpEditDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<JumpEditDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public jump: Jump
|
||||
) { }
|
||||
|
||||
onCloseDialog(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './jump-list.component';
|
||||
export * from './jump-meta.component';
|
||||
export * from './jump-preview.component';
|
||||
export * from './jump-table.component';
|
||||
@@ -0,0 +1,18 @@
|
||||
<div class="clearfix mt-4 mb-1 border-bottom">
|
||||
<h1 class="float-md-start me-3">{{ title }} <span class="fs-4 text-accent" [hidden]="!jumpsCount">({{ jumpsCount }})</span></h1>
|
||||
<mat-spinner class="float-md-start" [diameter]="32" [hidden]="!loading" color="accent"></mat-spinner>
|
||||
<button [hidden]="!showAdd" mat-raised-button color="accent" [routerLink]="['/editor']" class="float-md-end">
|
||||
<mat-icon aria-label="Ajouter" fontIcon="add_notes"></mat-icon> Ajouter
|
||||
</button>
|
||||
</div>
|
||||
<span [hidden]="!jumps.length" class="small mt-2 me-2 fst-italic">Prix actualisés toutes les {{ (refresh / 1000) }} secondes.</span>
|
||||
<span [hidden]="jumps.length || loading">Aucun produit à afficher pour le moment</span>
|
||||
<span [hidden]="!loading">Chargement des produits en cours</span>
|
||||
<huapp-jump-preview *ngFor="let jump of jumps" [jump]="jump" [isUser]="isUser" [showMeta]="showMeta"></huapp-jump-preview>
|
||||
<nav [hidden]="loading || pages.length <= 1" class="clearfix mt-3">
|
||||
<ul class="pagination float-end">
|
||||
<li class="page-item" [ngClass]="{'active': pageNumber === currentPage}" *ngFor="let pageNumber of pages">
|
||||
<button class="page-link" (click)="setPageTo(pageNumber)">{{ pageNumber }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -0,0 +1,93 @@
|
||||
import { Component, OnInit, OnDestroy, Input, computed, Signal, WritableSignal } from '@angular/core';
|
||||
import { NgFor, NgClass, DatePipe } from '@angular/common';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { Errors, Jump, JumpList, JumpListConfig } from 'src/app/core/models';
|
||||
import { JumpsService } from 'src/app/core/services';
|
||||
import { JumpPreviewComponent } from './jump-preview.component';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-list',
|
||||
templateUrl: './jump-list.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgFor, NgClass, DatePipe,
|
||||
MatProgressSpinnerModule, MatButtonModule, RouterLink, MatIconModule,
|
||||
JumpPreviewComponent
|
||||
]
|
||||
})
|
||||
export class JumpListComponent implements OnInit, OnDestroy {
|
||||
private _jumps: Subscription = new Subscription();
|
||||
errors: Errors = { errors: {} };
|
||||
query!: JumpListConfig;
|
||||
jumps: Array<Jump> = [];
|
||||
jumpsCount = 0;
|
||||
loading = false;
|
||||
currentPage = 1;
|
||||
pages: Array<number> = [1];
|
||||
lastUpdate: Date = new Date();
|
||||
|
||||
constructor(
|
||||
private jumpsService: JumpsService
|
||||
) { }
|
||||
|
||||
@Input() limit = 0;
|
||||
@Input() refresh = 30000;
|
||||
@Input() title = '';
|
||||
@Input() showAdd = false;
|
||||
@Input() isUser = false;
|
||||
@Input() showMeta = false;
|
||||
@Input()
|
||||
set config(config: JumpListConfig) {
|
||||
if (config) {
|
||||
this.query = config;
|
||||
this.currentPage = 1;
|
||||
this.loading = true;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.runQuery();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._jumps.unsubscribe();
|
||||
}
|
||||
|
||||
setPageTo(pageNumber: number) {
|
||||
this.currentPage = pageNumber;
|
||||
this.runQuery();
|
||||
}
|
||||
|
||||
runQuery() {
|
||||
if (this.limit) {
|
||||
this.query.filters.limit = this.limit;
|
||||
this.query.filters.offset = (this.limit * (this.currentPage - 1));
|
||||
}
|
||||
|
||||
const jumps$: Observable<JumpList> = this.jumpsService.query(this.query);
|
||||
this._jumps = jumps$.subscribe({
|
||||
next: (data: JumpList) => {
|
||||
this.loading = false;
|
||||
this.jumps = data.jumps;
|
||||
this.jumpsCount = data.jumpsCount;
|
||||
if (this.limit) {
|
||||
this.pages = this.getTotalPages(this.jumpsCount, this.limit);
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getTotalPages(count: number, limit: number) {
|
||||
// Used from http://www.jstips.co/en/create-range-0...n-easily-using-one-line/
|
||||
return Array.from(new Array(Math.ceil(count / limit)), (val, index) => index + 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<div class="jump-meta float-end">
|
||||
<a [routerLink]="['/profile', jump.author.username]">
|
||||
<img [src]="jump.author.image" [alt]="jump.author.username" />
|
||||
</a>
|
||||
<div class="info">
|
||||
<a class="author" [routerLink]="['/profile', jump.author.username]">
|
||||
{{ jump.author.username }}
|
||||
</a>
|
||||
<span class="date">
|
||||
{{ jump.createdAt | date: 'longDate' }}
|
||||
</span>
|
||||
</div>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
@@ -0,0 +1,29 @@
|
||||
/* Jumps Meta */
|
||||
.jump-meta {
|
||||
display: block;
|
||||
position: relative;
|
||||
font-weight: 300;
|
||||
img {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border-radius: 30px;
|
||||
}
|
||||
.info {
|
||||
margin: 0 1rem 0 .4rem;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: 1rem;
|
||||
.author {
|
||||
display: block;
|
||||
font-weight: 500 !important;
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
.date {
|
||||
color: #bbb;
|
||||
font-size: .8rem;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
import { Jump } from 'src/app/core/models';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-meta',
|
||||
styleUrls: ['jump-meta.component.scss'],
|
||||
templateUrl: './jump-meta.component.html',
|
||||
standalone: true,
|
||||
imports: [RouterLink, DatePipe]
|
||||
})
|
||||
export class JumpMetaComponent {
|
||||
@Input() jump!: Jump;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<mat-card appearance="outlined" class="mt-4">
|
||||
<mat-card-header class="pt-2">
|
||||
<mat-card-title-group>
|
||||
<mat-card-title><a [routerLink]="['/jump', jump.slug]">#{{ jump.numero }}</a></mat-card-title>
|
||||
<mat-card-subtitle>{{ jump.categorie }} / {{ jump.module }} </mat-card-subtitle>
|
||||
</mat-card-title-group>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<mat-divider [class]="['mt-1', 'mb-2']"></mat-divider>
|
||||
<huapp-jump-meta [hidden]="!showMeta" [jump]="jump" class="float-sm-end"></huapp-jump-meta>
|
||||
<div class="row mt-4">
|
||||
<div class="col-sm-6">
|
||||
<dl class="row mb-2">
|
||||
<dt class="col-lg-4 col-md-5 col-sm-6 fw-normal">Date</dt>
|
||||
<dd class="col-lg-8 col-md-7 col-sm-6">{{ jump.date }}</dd>
|
||||
<dt class="col-lg-4 col-md-5 col-sm-6 fw-normal">Lieu</dt>
|
||||
<dd class="col-lg-8 col-md-7 col-sm-6">{{ jump.lieu }} {{ jump.oaci }}</dd>
|
||||
<dt class="col-lg-4 col-md-5 col-sm-6 fw-normal">Voile</dt>
|
||||
<dd class="col-lg-8 col-md-7 col-sm-6">{{ jump.voile }} {{ jump.taille }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<dl class="row mb-2">
|
||||
<dt class="col-lg-4 col-md-5 col-sm-6 fw-normal">Hauteur</dt>
|
||||
<dd class="col-lg-8 col-md-7 col-sm-6">{{ jump.hauteur }}</dd>
|
||||
<dt class="col-lg-4 col-md-5 col-sm-6 fw-normal">Aeronef</dt>
|
||||
<dd class="col-lg-8 col-md-7 col-sm-6">{{ jump.aeronef }} {{ jump.imat }}</dd>
|
||||
<dt class="col-lg-4 col-md-5 col-sm-6 fw-normal">Type de saut</dt>
|
||||
<dd class="col-lg-8 col-md-7 col-sm-6">{{ jump.categorie }} {{ jump.module }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-xs-12">
|
||||
<h5>Participants ({{ jump.participants }})</h5>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider [class]="['mt-2']"></mat-divider>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-button color="danger" [disabled]="isDeleting" (click)="deleteJump()">SUPPRIMER</button>
|
||||
<span class="flex-spacer"></span>
|
||||
<button mat-button [routerLink]="['/editor', jump.slug]">MODIFIER</button>
|
||||
<button mat-button color="primary" [routerLink]="['/jump', jump.slug]">VOIR</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
@@ -0,0 +1,44 @@
|
||||
/* Jumps preview */
|
||||
.jump-preview {
|
||||
border-top: 1px solid rgba(0, 0, 0, .1);
|
||||
padding: 1.4rem 0 0.5rem;
|
||||
.jump-meta {
|
||||
margin: 0 0 0.2rem;
|
||||
}
|
||||
h3 .preview-link {
|
||||
font-weight: 600 !important;
|
||||
font-size: 1.5rem !important;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
.preview-link {
|
||||
color: inherit !important;
|
||||
&:hover {
|
||||
text-decoration: inherit !important;
|
||||
}
|
||||
p {
|
||||
color: #999;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
line-height: 1.3rem;
|
||||
}
|
||||
span {
|
||||
max-width: 30%;
|
||||
font-size: .8rem;
|
||||
font-weight: 300;
|
||||
color: #bbb;
|
||||
vertical-align: middle;
|
||||
}
|
||||
ul {
|
||||
float: right;
|
||||
max-width: 50%;
|
||||
vertical-align: top;
|
||||
}
|
||||
ul li {
|
||||
font-weight: 300;
|
||||
font-size: .8rem !important;
|
||||
padding-top: .2rem;
|
||||
padding-bottom: .2rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Component, OnDestroy, Input } from '@angular/core';
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { JumpMetaComponent } from './jump-meta.component';
|
||||
import { Jump } from 'src/app/core/models';
|
||||
import { JumpsService } from 'src/app/core/services';
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-jump-preview',
|
||||
styleUrls: ['jump-preview.component.scss'],
|
||||
templateUrl: './jump-preview.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DecimalPipe, RouterLink,
|
||||
MatDividerModule, MatButtonModule, MatCardModule,
|
||||
JumpMetaComponent
|
||||
]
|
||||
})
|
||||
export class JumpPreviewComponent implements OnDestroy {
|
||||
@Input() jump!: Jump;
|
||||
@Input() isUser!: boolean;
|
||||
@Input() showMeta!: boolean;
|
||||
private _jump: Subscription = new Subscription();
|
||||
isDeleting = false;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private jumpsService: JumpsService
|
||||
) { }
|
||||
|
||||
ngOnDestroy() {
|
||||
this._jump.unsubscribe();
|
||||
}
|
||||
|
||||
deleteJump() {
|
||||
this.isDeleting = true;
|
||||
const jump$: Observable<any> = this.jumpsService.destroy(this.jump.slug);
|
||||
this._jump = jump$.subscribe({
|
||||
next: () => {
|
||||
this.router.navigateByUrl('/');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<div class="clearfix mt-4 mb-1 border-bottom">
|
||||
<h2 class="float-md-start me-3">{{ title }} <span class="fs-4 text-accent" [hidden]="!jumpsCount">({{ jumpsCount }})</span></h2>
|
||||
<mat-spinner class="float-md-start" [diameter]="32" [hidden]="!loading" color="accent"></mat-spinner>
|
||||
<button [hidden]="!showAdd" mat-raised-button color="accent" (click)="openAddDialog()" class="float-md-end">
|
||||
<mat-icon aria-label="Ajouter" fontIcon="add_notes"></mat-icon> Ajouter
|
||||
</button>
|
||||
</div>
|
||||
<span [hidden]="!jumpsCount" class="small mt-2 me-2 fst-italic">Dernier saut enregistré :</span>
|
||||
<span [hidden]="jumpsCount || loading">Aucun saut à afficher pour le moment.</span>
|
||||
<span [hidden]="!loading">Actualisation des sauts en cours</span>
|
||||
|
||||
<div class="row mx-0">
|
||||
<div class="col-md-6 col-xs-12 px-0">
|
||||
<form class="mt-3" [formGroup]="searchForm" (ngSubmit)="submitForm()">
|
||||
<mat-form-field appearance="fill">
|
||||
<input matInput type="number" placeholder="Rechercher un saut" formControlName="numero">
|
||||
<mat-error *ngIf="this.searchForm.controls['numero'].status === 'INVALID'">{{getErrorMessage()}}</mat-error>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" type="submit" class="ms-2">
|
||||
<mat-icon aria-label="Rechercher" fontIcon="search"></mat-icon> Rechercher
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6 col-xs-12 px-0">
|
||||
<mat-paginator [pageSize]="25" [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons class="mb-1 mt-2"></mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
<table mat-table [dataSource]="jumps" matSort class="table table-dark table-striped table-hover table-condensed mat-elevation-z2">
|
||||
<caption [hidden]="jumpsCount || loading">Aucun saut à afficher pour le moment.</caption>
|
||||
<!-- Numero Column -->
|
||||
<ng-container matColumnDef="numero">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Numero </th>
|
||||
<td class="text-right pl-3 pr-3" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.numero"><a [routerLink]="['/jump', element.slug]">{{element.numero}}</a></ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Date Column -->
|
||||
<ng-container matColumnDef="date">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Date </th>
|
||||
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.date">{{element.date}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Lieu Column -->
|
||||
<ng-container matColumnDef="lieu">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Lieu </th>
|
||||
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.lieu">{{element.lieu}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Oaci Column -->
|
||||
<ng-container matColumnDef="oaci">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Oaci </th>
|
||||
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.oaci">{{element.oaci}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Aeronef Column -->
|
||||
<ng-container matColumnDef="aeronef">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Aeronef </th>
|
||||
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.aeronef">{{element.aeronef}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Imat Column -->
|
||||
<ng-container matColumnDef="imat">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Imat </th>
|
||||
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.imat">{{element.imat}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Hauteur Column -->
|
||||
<ng-container matColumnDef="hauteur">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Hauteur </th>
|
||||
<td class="pl-3" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.hauteur">{{element.hauteur}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Voile Column -->
|
||||
<ng-container matColumnDef="voile">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Voile </th>
|
||||
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.voile">{{element.voile}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Taille Column -->
|
||||
<ng-container matColumnDef="taille">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Taille </th>
|
||||
<td class="text-nowrap text-right pl-3 pr-3" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.taille">{{element.taille}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Categorie Column -->
|
||||
<ng-container matColumnDef="categorie">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Categorie </th>
|
||||
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.categorie">{{element.categorie}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Module Column -->
|
||||
<ng-container matColumnDef="module">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Module </th>
|
||||
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.module">{{element.module}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Participants Column -->
|
||||
<ng-container matColumnDef="participants">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Participants </th>
|
||||
<td class="text-right pl-3 pr-3" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.participants">{{element.participants}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Programme Column -->
|
||||
<ng-container matColumnDef="programme">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Programme </th>
|
||||
<td class="pl-3" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.programme">{{element.programme}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Accessoires Column -->
|
||||
<ng-container matColumnDef="accessoires">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Accessoires </th>
|
||||
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.accessoires">{{element.accessoires}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Zone Column -->
|
||||
<ng-container matColumnDef="zone">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Zone </th>
|
||||
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.zone">{{element.zone}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Dossier Column -->
|
||||
<ng-container matColumnDef="dossier">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Dossier </th>
|
||||
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.dossier">{{element.dossier}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Video Column -->
|
||||
<ng-container matColumnDef="video">
|
||||
<th class="pl-3" mat-header-cell *matHeaderCellDef> Video </th>
|
||||
<td class="pl-3 text-nowrap" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.video">{{element.video}}</ng-container>
|
||||
<ng-container *ngIf="!element.numero"><ngx-skeleton-loader [theme]="{width: '30px', height: '13px', marginBottom: '0px'}"></ngx-skeleton-loader></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<!-- Actions Column -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<th class="pl-3 text-center" mat-header-cell *matHeaderCellDef> {{ showAdd ? 'Actions' : 'Favoris' }} </th>
|
||||
<td class="pl-2 text-nowrap text-center" mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.numero">
|
||||
<button [hidden]="!showAdd" mat-icon-button color="primary" (click)="openEditDialog(element)">
|
||||
<mat-icon aria-label="Modifier" fontIcon="edit"></mat-icon>
|
||||
</button>
|
||||
<button [hidden]="!showAdd" mat-icon-button color="danger" (click)="openDeleteDialog(element)">
|
||||
<mat-icon aria-label="Supprimer" fontIcon="delete"></mat-icon>
|
||||
</button>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!element.numero">
|
||||
<ngx-skeleton-loader [hidden]="!showAdd" appearance="circle" [theme]="{width: '32px', height: '32px', marginBottom: '0px', marginRight: '7px'}"></ngx-skeleton-loader>
|
||||
<ngx-skeleton-loader [hidden]="!showAdd" appearance="circle" [theme]="{width: '32px', height: '32px', marginBottom: '0px', marginRight: '7px'}"></ngx-skeleton-loader>
|
||||
<ngx-skeleton-loader appearance="circle" [theme]="{width: '20px', height: '20px', marginBottom: '0px', marginRight: '7px'}"></ngx-skeleton-loader>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" class="text-middle"></tr>
|
||||
</table>
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
/* TODO(mdc-migration): The following rule targets internal classes of card that may no longer apply for the MDC version. */
|
||||
mat-card-content {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.main-page {
|
||||
.feed-toggle {
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.sidebar {
|
||||
padding: 5px 10px 10px;
|
||||
background: #f3f3f3;
|
||||
border-radius: 4px;
|
||||
p {
|
||||
margin-bottom: .2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.table.table-condensed.mat-mdc-table {
|
||||
tr {
|
||||
height: inherit;
|
||||
}
|
||||
td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
import { Component, Input, ViewChild, OnChanges, OnDestroy, computed, Signal, WritableSignal } from '@angular/core';
|
||||
import { NgIf, DecimalPipe, DatePipe } from '@angular/common';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSort, MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||
import { MatSnackBar, MatSnackBarHorizontalPosition, MatSnackBarModule, MatSnackBarVerticalPosition } from '@angular/material/snack-bar';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
|
||||
import { Errors, Jump, JumpList, JumpListConfig, JumpViewResults } from 'src/app/core/models';
|
||||
import { JumpsService, BackendService } from 'src/app/core/services';
|
||||
import { JumpAddDialogComponent, JumpDeleteDialogComponent, JumpEditDialogComponent } from 'src/app/components/shared/dialogs'
|
||||
import { environment } from 'src/environments/environment';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgIf, DecimalPipe, DatePipe, RouterLink, FormsModule, ReactiveFormsModule, NgxSkeletonLoaderModule,
|
||||
MatFormFieldModule, MatInputModule,
|
||||
MatProgressSpinnerModule, MatButtonModule, MatIconModule, MatPaginatorModule,
|
||||
MatTableModule, MatSortModule, MatSnackBarModule, MatDialogModule,
|
||||
JumpAddDialogComponent, JumpDeleteDialogComponent, JumpEditDialogComponent
|
||||
],
|
||||
selector: 'huapp-jump-table',
|
||||
styleUrls: ['./jump-table.component.scss'],
|
||||
templateUrl: './jump-table.component.html'
|
||||
})
|
||||
export class JumpTableComponent implements OnChanges, OnDestroy {
|
||||
private _jumps: Subscription = new Subscription();
|
||||
errors!: Errors;
|
||||
query: JumpListConfig = { type: 'all', filters: {} };
|
||||
jump: Jump = {} as Jump;
|
||||
jumps!: MatTableDataSource<Jump>;
|
||||
jumpsCount = 0;
|
||||
currentPage = 1;
|
||||
displayedColumns: string[] = [
|
||||
'numero', 'date', 'lieu', 'oaci', 'aeronef',
|
||||
'imat', 'hauteur', 'voile', 'taille', 'categorie',
|
||||
'module', 'participants', 'programme', 'accessoires',
|
||||
'zone', 'video', 'actions'
|
||||
];
|
||||
lastUpdate: Date = new Date();
|
||||
loading = false;
|
||||
isSubmitting = false;
|
||||
isDeleting = false;
|
||||
searchForm: UntypedFormGroup;
|
||||
|
||||
@ViewChild(MatSort) sort!: MatSort;
|
||||
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private jumpsService: JumpsService,
|
||||
private _dataService: BackendService,
|
||||
private dialog: MatDialog,
|
||||
private fb: UntypedFormBuilder,
|
||||
private snackBar: MatSnackBar
|
||||
) {
|
||||
this._resetErrors();
|
||||
this.searchForm = this.fb.group({
|
||||
//numero: ['', Validators.required]
|
||||
numero: ''
|
||||
});
|
||||
}
|
||||
|
||||
@Input() limit = 0;
|
||||
@Input() refresh = 30000;
|
||||
@Input() title = '';
|
||||
@Input() showAdd = false;
|
||||
@Input() isUser = false;
|
||||
@Input()
|
||||
set config(config: JumpListConfig) {
|
||||
if (config) {
|
||||
this.query = config;
|
||||
this.currentPage = 1;
|
||||
}
|
||||
}
|
||||
|
||||
get config(): JumpListConfig {
|
||||
return this.query;
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.loading = true;
|
||||
this.query = this.config;
|
||||
this.runQuery();
|
||||
//this.loadJumps();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._jumps.unsubscribe();
|
||||
}
|
||||
|
||||
runQuery() {
|
||||
if (this.limit) {
|
||||
this.query.filters.limit = this.limit;
|
||||
this.query.filters.offset = (this.limit * (this.currentPage - 1));
|
||||
} else {
|
||||
this.query.filters.limit = 0;
|
||||
this.query.filters.offset = 0;
|
||||
}
|
||||
|
||||
const jumps$: Observable<JumpList> = this.jumpsService.query(this.query);
|
||||
this._jumps = jumps$.subscribe({
|
||||
next: (data: JumpList) => {
|
||||
this.loading = false;
|
||||
this.jumps = new MatTableDataSource<Jump>(data.jumps);
|
||||
this.jumpsCount = data.jumpsCount;
|
||||
this.paginator.length = data.jumpsCount;
|
||||
this.jumps.paginator = this.paginator;
|
||||
this.jumps.sort = this.sort;
|
||||
this._resetErrors();
|
||||
},
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadJumps() {
|
||||
const jumps$: Observable<JumpViewResults> = this._dataService.getJumps();
|
||||
this._jumps = jumps$.subscribe((jumpList) => {
|
||||
//console.log('ngOnInit');
|
||||
//console.log(res);
|
||||
//console.log(res["rows"]);
|
||||
this.jumpsCount = jumpList.total_rows;
|
||||
//this.last_jump = jumpList.rows[0];
|
||||
this.jumps = new MatTableDataSource<Jump>(jumpList.rows)
|
||||
this.jumps.paginator = this.paginator;
|
||||
this.jumps.sort = this.sort;
|
||||
console.log(this.jumps);
|
||||
});
|
||||
/*
|
||||
.subscribe((res) => this.jumps = new MatTableDataSource<Jump>(res["rows"]));
|
||||
*/
|
||||
//
|
||||
//.subscribe((res) => this.jumps = res["rows"]);
|
||||
//.subscribe((res) => console.log(res["rows"]));
|
||||
}
|
||||
|
||||
deleteJump(slug: string) {
|
||||
this.isDeleting = true;
|
||||
this.jumpsService.destroy(slug).pipe(take(1))
|
||||
.subscribe(() => {
|
||||
this.router.navigateByUrl('/');
|
||||
});
|
||||
}
|
||||
|
||||
openAddDialog(): void {
|
||||
const dialogRef = this.dialog.open(JumpAddDialogComponent, {
|
||||
width: '60vw',
|
||||
data: this.jump
|
||||
});
|
||||
dialogRef.afterClosed().pipe(take(1))
|
||||
.subscribe(result => {
|
||||
if (result != undefined) {
|
||||
this._onEntry(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openDeleteDialog(jump: Jump): void {
|
||||
const dialogRef = this.dialog.open(JumpDeleteDialogComponent, {
|
||||
width: '40vw',
|
||||
data: jump
|
||||
});
|
||||
dialogRef.afterClosed().pipe(take(1))
|
||||
.subscribe(result => {
|
||||
if (result != undefined) {
|
||||
this._onDelete(result.slug);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openEditDialog(jump: Jump): void {
|
||||
const dialogRef = this.dialog.open(JumpEditDialogComponent, {
|
||||
width: '50vw',
|
||||
data: jump
|
||||
});
|
||||
dialogRef.afterClosed().pipe(take(1))
|
||||
.subscribe(result => {
|
||||
if (result != undefined) {
|
||||
this._onEdit(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getErrorMessage() {
|
||||
if (this.searchForm.controls['numero'].errors !== null) {
|
||||
return 'Vous devez indiquer une valeur';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
submitForm() {
|
||||
if (this.searchForm.valid) {
|
||||
this.query.filters.numero = this.searchForm.value.numero;
|
||||
this._resetErrors();
|
||||
this.runQuery();
|
||||
}
|
||||
}
|
||||
|
||||
private _openSnackBar(content: string, title: string) {
|
||||
this.snackBar.open(content, title, {
|
||||
horizontalPosition: <MatSnackBarHorizontalPosition>'end',
|
||||
verticalPosition: <MatSnackBarVerticalPosition>'bottom',
|
||||
duration: 4000,
|
||||
});
|
||||
}
|
||||
|
||||
private _onDelete(slug: string): void {
|
||||
try {
|
||||
this.isDeleting = true;
|
||||
this.jumpsService.destroy(slug)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this._resetErrors();
|
||||
this._openSnackBar(`Le saut a été supprimé !`, 'OK');
|
||||
this.runQuery();
|
||||
},
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private _onEdit(jump: Jump): void {
|
||||
try {
|
||||
this.isSubmitting = true;
|
||||
this.jumpsService.update(jump)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (jump) => {
|
||||
this._resetErrors();
|
||||
this._openSnackBar(`Le saut n°${jump.numero} a été mis à jour !`, 'OK');
|
||||
this.runQuery();
|
||||
},
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private _onEntry(entry: NonNullable<unknown>): void {
|
||||
try {
|
||||
this.isSubmitting = true;
|
||||
Object.assign(this.jump, entry);
|
||||
this.jumpsService.save(this.jump)
|
||||
.pipe(take(1))
|
||||
.subscribe({
|
||||
next: (jump) => {
|
||||
this._openSnackBar(`Le saut n°${jump.numero} a été ajouté !`, 'OK');
|
||||
this._resetErrors();
|
||||
this.runQuery();
|
||||
},
|
||||
error: (err) => {
|
||||
this.errors = err;
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private _resetErrors(): void {
|
||||
this.errors = { errors: {} };
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export * from './accordion';
|
||||
export * from './confirmed.validator';
|
||||
export * from './dialogs';
|
||||
export * from './helpers-jump';
|
||||
export * from './layout/full.component';
|
||||
export * from './list-errors/list-errors.component';
|
||||
export * from './menu-items';
|
||||
export * from './spinner/spinner.component';
|
||||
export * from './show-authed.directive';
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
|
||||
<mat-toolbar color="primary" class="topbar">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="index.html">
|
||||
<span>
|
||||
<img src="assets/images/logo-light-icon.svg" alt="homepage" class="light-logo">
|
||||
</span>
|
||||
<span>
|
||||
<img src="assets/images/logo-light-text.svg" alt="homepage" class="light-logo">
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<button mat-icon-button (click)="snav.toggle()" value="sidebarclosed">
|
||||
<mat-icon fontIcon="menu"></mat-icon>
|
||||
</button>
|
||||
<span class="flex-spacer"></span>
|
||||
<span *appShowAuthed="true" class="fw-light me-1">@{{ currentUser.username }}</span>
|
||||
<div *appShowAuthed="true" class="position-relative">
|
||||
<button [matMenuTriggerFor]="profile" mat-icon-button>
|
||||
<img [src]="currentUser.image" alt="user" class="profile-pic">
|
||||
</button>
|
||||
<mat-menu #profile="matMenu">
|
||||
<button mat-menu-item [routerLink]="['/profile', currentUser.username]" routerLinkActive="active">
|
||||
<mat-icon fontIcon="account_circle" color="primary"></mat-icon> Mon compte
|
||||
</button>
|
||||
<button mat-menu-item routerLink="/settings" routerLinkActive="active">
|
||||
<mat-icon fontIcon="settings" color="primary"></mat-icon> Paramètres
|
||||
</button>
|
||||
<button mat-menu-item (click)="goToGitHub()">
|
||||
<mat-icon fontIcon="code" color="primary"></mat-icon> GitHub
|
||||
</button>
|
||||
<button mat-menu-item>
|
||||
<mat-icon fontIcon="notifications_off" color="primary"></mat-icon> Désactiver les notifications
|
||||
</button>
|
||||
<hr class="my-0" />
|
||||
<button mat-menu-item (click)="logout()">
|
||||
<mat-icon fontIcon="logout" color="warn"></mat-icon> Se déconnecter
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<span *appShowAuthed="false">
|
||||
<button mat-button routerLink="/login" routerLinkActive="active">Se connecter</button>
|
||||
<button mat-button routerLink="/register" routerLinkActive="active">Créer un compte</button>
|
||||
</span>
|
||||
</mat-toolbar>
|
||||
<mat-sidenav-container class="app-sidenav-container" [style.marginTop.px]="mobileQuery.matches ? 0 : 0">
|
||||
<mat-sidenav #snav id="snav" class="dark-sidebar pl-xs" [mode]="mobileQuery.matches ? 'side' : 'over'" fixedTopGap="0" [opened]="mobileQuery.matches" [disableClose]="mobileQuery.matches" >
|
||||
<div *appShowAuthed="true">
|
||||
<div class="user-profile" style="background-image: url('{{ currentUser.bg_image }}');">
|
||||
<div class="profile-img"> <img [src]="currentUser.image" alt="user"> </div>
|
||||
<div class="profile-text">
|
||||
<a [matMenuTriggerFor]="sdprofile"> {{ currentUser.firstname }} {{ currentUser.lastname }} <mat-icon fontIcon="expand_more" class="ms-5"></mat-icon></a>
|
||||
</div>
|
||||
<mat-menu #sdprofile="matMenu">
|
||||
<button mat-menu-item [routerLink]="['/profile', currentUser.username]" routerLinkActive="active">
|
||||
<mat-icon fontIcon="account_box" color="primary"></mat-icon> Mon compte
|
||||
</button>
|
||||
<button mat-menu-item routerLink="/settings" routerLinkActive="active">
|
||||
<mat-icon fontIcon="settings" color="primary"></mat-icon> Paramètres
|
||||
</button>
|
||||
<button mat-menu-item>
|
||||
<mat-icon fontIcon="notifications_off" color="primary"></mat-icon> Désactiver les notifications
|
||||
</button>
|
||||
<hr class="my-0" />
|
||||
<button mat-menu-item (click)="logout()">
|
||||
<mat-icon fontIcon="exit_to_app" color="warn"></mat-icon> Se déconnecter
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
<mat-nav-list appAccordion>
|
||||
<mat-list-item appAccordionLink *ngFor="let menuitem of menuItems.getMenuItems()" routerLinkActive="selected" group="{{menuitem.state}}">
|
||||
<a appAccordionToggle [routerLink]="['/', menuitem.state]" *ngIf="menuitem.type === 'link'">
|
||||
<mat-icon [fontIcon]="menuitem.icon" class="mt-0 mr-2"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
<span class="flex-spacer"></span>
|
||||
<span matBadge="{{ badge.value }}" matBadgeColor="warn" matBadgeOverlap="false" *ngFor="let badge of menuitem.badge"></span>
|
||||
</a>
|
||||
<a (click)="showQCMMenu = !showQCMMenu" *ngIf="menuitem.type === 'menu'">
|
||||
<mat-icon [fontIcon]="menuitem.icon" class="mt-0 mr-2"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
<span class="flex-spacer"></span>
|
||||
<span matBadge="{{ badge.value }}" matBadgeColor="warn" matBadgeOverlap="false" *ngFor="let badge of menuitem.badge"></span>
|
||||
</a>
|
||||
</mat-list-item>
|
||||
<div *ngIf="showQCMMenu">
|
||||
<mat-list-item appAccordionLink *ngFor="let menuqcm of menuItems.getMenuQCM()" routerLinkActive="selected" group="{{ menuqcm.state}}">
|
||||
<a class="ps-4" appAccordionToggle [routerLink]="['/', menuqcm.state]" *ngIf=" menuqcm.type === 'link'">
|
||||
<mat-icon [fontIcon]="menuqcm.icon" class="mt-0 mr-2"></mat-icon>
|
||||
<span>{{ menuqcm.name }}</span>
|
||||
<span class="flex-spacer"></span>
|
||||
</a>
|
||||
</mat-list-item>
|
||||
</div>
|
||||
</mat-nav-list>
|
||||
</div>
|
||||
<div *appShowAuthed="false">
|
||||
<div class="user-profile">
|
||||
<!-- <div class="profile-img"> <img src="/assets/images/users/user.jpg" alt="user"> </div> -->
|
||||
</div>
|
||||
<mat-nav-list appAccordion>
|
||||
<mat-list-item appAccordionLink *ngFor="let menuitem of menuItems.getMenuAuth()" routerLinkActive="selected" group="{{menuitem.state}}">
|
||||
<a appAccordionToggle [routerLink]="['/', menuitem.state]" *ngIf="menuitem.type === 'link'">
|
||||
<mat-icon [fontIcon]="menuitem.icon" class="mt-0 mr-2"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
<span class="flex-spacer"></span>
|
||||
<span matBadge="{{ badge.value }}" matBadgeColor="warn" matBadgeOverlap="false" *ngFor="let badge of menuitem.badge"></span>
|
||||
</a>
|
||||
<a (click)="showQCMMenu = !showQCMMenu" *ngIf="menuitem.type === 'menu'">
|
||||
<mat-icon [fontIcon]="menuitem.icon" class="mt-0 mr-2"></mat-icon>
|
||||
<span>{{ menuitem.name }}</span>
|
||||
<span class="flex-spacer"></span>
|
||||
<span matBadge="{{ badge.value }}" matBadgeColor="warn" matBadgeOverlap="false" *ngFor="let badge of menuitem.badge"></span>
|
||||
</a>
|
||||
</mat-list-item>
|
||||
<div *ngIf="showQCMMenu">
|
||||
<mat-list-item appAccordionLink *ngFor="let menuqcm of menuItems.getMenuQCM()" routerLinkActive="selected" group="{{ menuqcm.state}}">
|
||||
<a class="ps-4" appAccordionToggle [routerLink]="['/', menuqcm.state]" *ngIf=" menuqcm.type === 'link'">
|
||||
<mat-icon [fontIcon]="menuqcm.icon" class="mt-0 mr-2"></mat-icon>
|
||||
<span>{{ menuqcm.name }}</span>
|
||||
<span class="flex-spacer"></span>
|
||||
</a>
|
||||
</mat-list-item>
|
||||
</div>
|
||||
</mat-nav-list>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<div class="container-fluid text-end">
|
||||
<a class="me-1" [href]="siteLink" target="_blank">{{ author }}</a>
|
||||
<span class="copyright">
|
||||
© {{ today | date: 'yyyy' }}
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
</mat-sidenav>
|
||||
<mat-sidenav-content class="page-wrapper">
|
||||
<div class="page-content">
|
||||
<router-outlet><huapp-spinner></huapp-spinner></router-outlet>
|
||||
</div>
|
||||
</mat-sidenav-content>
|
||||
</mat-sidenav-container>
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Router, RouterOutlet, RouterLink, RouterModule } from '@angular/router';
|
||||
import { MediaMatcher } from '@angular/cdk/layout';
|
||||
import { MatBadgeModule } from '@angular/material/badge';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatListModule } from '@angular/material/list';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
|
||||
import { User } from 'src/app/core/models';
|
||||
import { UserService } from 'src/app/core/services';
|
||||
import { MenuItems, ShowAuthedDirective, SpinnerComponent, AccordionAnchorDirective, AccordionLinkDirective, AccordionDirective } from 'src/app/components/shared';
|
||||
|
||||
|
||||
/** @title Responsive sidenav */
|
||||
@Component({
|
||||
selector: 'huapp-full-layout',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule, RouterModule, RouterOutlet,
|
||||
MatBadgeModule, MatButtonModule, MatIconModule, MatListModule, MatMenuModule, MatSidenavModule, MatToolbarModule,
|
||||
ShowAuthedDirective, SpinnerComponent,
|
||||
AccordionAnchorDirective, AccordionLinkDirective, AccordionDirective
|
||||
],
|
||||
providers: [ MenuItems ],
|
||||
templateUrl: 'full.component.html',
|
||||
styleUrls: []
|
||||
})
|
||||
export class FullComponent implements OnInit, OnDestroy {
|
||||
private _currentUser: Subscription = new Subscription();
|
||||
private _mobileQueryListener: () => void;
|
||||
mobileQuery: MediaQueryList;
|
||||
currentUser: User = {} as User;
|
||||
showQCMMenu = false;
|
||||
today: number = Date.now();
|
||||
siteLink = 'https://www.rampeur.com';
|
||||
author = 'Solide Apps';
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private userService: UserService,
|
||||
changeDetectorRef: ChangeDetectorRef,
|
||||
media: MediaMatcher,
|
||||
public menuItems: MenuItems
|
||||
) {
|
||||
this.mobileQuery = media.matchMedia('(min-width: 768px)');
|
||||
this._mobileQueryListener = () => changeDetectorRef.detectChanges();
|
||||
this.mobileQuery.addListener(this._mobileQueryListener);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const user$: Observable<User> = this.userService.currentUser;
|
||||
this._currentUser = user$.subscribe(
|
||||
(userData) => {
|
||||
this.currentUser = userData;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._currentUser.unsubscribe();
|
||||
this.mobileQuery.removeListener(this._mobileQueryListener);
|
||||
}
|
||||
|
||||
goToGitHub() {
|
||||
window.open('https://github.com/rampeur', '_blank');
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.userService.purgeAuth();
|
||||
this.router.navigateByUrl('/login');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<mat-card appearance="outlined" class="error-messages mb-4" [hidden]="!errorList.length && !stacktrace">
|
||||
<mat-card-content>
|
||||
<ul class="error-messages" *ngIf="errorList.length">
|
||||
<li *ngFor="let error of errorList">
|
||||
{{ error }}
|
||||
</li>
|
||||
</ul>
|
||||
<pre class="error-messages m-2" *ngIf="stacktrace">{{ stacktrace }}</pre>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
|
||||
import { Errors } from 'src/app/core';
|
||||
@Component({
|
||||
selector: 'huapp-list-errors',
|
||||
templateUrl: './list-errors.component.html',
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatCardModule]
|
||||
})
|
||||
export class ListErrorsComponent {
|
||||
formattedErrors: Array<string | undefined> = [];
|
||||
stacktrace?: string;
|
||||
|
||||
@Input()
|
||||
set errors(errorList: Errors) {
|
||||
if (errorList.errors['stack'] !== undefined) {
|
||||
this.stacktrace = errorList.errors['stack'];
|
||||
delete errorList.errors['stack'];
|
||||
}
|
||||
this.formattedErrors = Object.keys(errorList.errors || {})
|
||||
.map((key: string) => {
|
||||
if (key !== 'stack') {
|
||||
return `${key}: ${errorList.errors[key]}`;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
get errorList() {
|
||||
return this.formattedErrors;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Menu } from 'src/app/core/models/menu.model';
|
||||
|
||||
const MENUAUTH: Menu[] = [
|
||||
{ state: 'login', type: 'link', name: 'Se connecter', icon: 'fingerprint', desc: '' },
|
||||
{ state: 'register', type: 'link', name: 'Créer un compte', icon: 'person_add_alt', desc: '' },
|
||||
{ state: 'qcm', type: 'menu', name: 'QCM Brevets', icon: 'rule_folder', desc: '' }
|
||||
];
|
||||
|
||||
const MENUITEMS: Menu[] = [
|
||||
{ state: 'dashboard', type: 'link', name: 'Dashboard', icon: 'dashboard', desc: '' },
|
||||
{ state: 'logbook', type: 'link', name: 'Carnet de saut', icon: 'assignment_turned_in', desc: '' },
|
||||
{ state: 'calculator', type: 'link', name: 'Taille de voile', icon: 'calculate', desc: '' },
|
||||
{ state: 'qcm', type: 'menu', name: 'QCM Brevets', icon: 'rule_folder', desc: '' }
|
||||
];
|
||||
|
||||
const MENUQCM: Menu[] = [
|
||||
{ state: 'button', type: 'link', name: 'BPA', icon: 'rule' },
|
||||
{ state: 'datepicker', type: 'link', name: 'Brevet C', icon: 'rule' },
|
||||
{ state: 'grid', type: 'link', name: 'Brevet D', icon: 'rule' }
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class MenuItems {
|
||||
getMenuAuth(): Menu[] {
|
||||
return MENUAUTH;
|
||||
}
|
||||
getMenuItems(): Menu[] {
|
||||
return MENUITEMS;
|
||||
}
|
||||
getMenuQCM(): Menu[] {
|
||||
return MENUQCM;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Directive, Input, OnInit, OnDestroy, TemplateRef, ViewContainerRef } from '@angular/core';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { UserService } from 'src/app/core/services';
|
||||
|
||||
@Directive({
|
||||
selector: '[appShowAuthed]',
|
||||
standalone: true
|
||||
})
|
||||
export class ShowAuthedDirective implements OnInit, OnDestroy {
|
||||
private _auth: Subscription = new Subscription();
|
||||
|
||||
constructor(
|
||||
private templateRef: TemplateRef<any>,
|
||||
private userService: UserService,
|
||||
private viewContainer: ViewContainerRef
|
||||
) { }
|
||||
|
||||
@Input() set appShowAuthed(condition: boolean) {
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
condition: boolean = false;
|
||||
//condition: boolean;
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
const isAuthenticated$: Observable<boolean> = this.userService.isAuthenticated; // .pipe(take(1));
|
||||
this._auth = isAuthenticated$.subscribe(
|
||||
(isAuthenticated) => {
|
||||
if (isAuthenticated && this.condition || !isAuthenticated && !this.condition) {
|
||||
this.viewContainer.createEmbeddedView(this.templateRef);
|
||||
} else {
|
||||
this.viewContainer.clear();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._auth.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="preloader" *ngIf="isSpinnerVisible">
|
||||
<div class="spinner">
|
||||
<div class="double-bounce1"></div>
|
||||
<div class="double-bounce2"></div>
|
||||
</div>
|
||||
</div>
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { Component, Input, OnDestroy, Inject, ViewEncapsulation } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'huapp-spinner',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './spinner.component.html',
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class SpinnerComponent implements OnDestroy {
|
||||
public isSpinnerVisible = true;
|
||||
|
||||
@Input()
|
||||
public backgroundColor = 'rgba(0, 115, 170, 0.69)';
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
@Inject(DOCUMENT) private document: Document
|
||||
) {
|
||||
this.router.events.subscribe(
|
||||
event => {
|
||||
if (event instanceof NavigationStart) {
|
||||
this.isSpinnerVisible = true;
|
||||
} else if (
|
||||
event instanceof NavigationEnd ||
|
||||
event instanceof NavigationCancel ||
|
||||
event instanceof NavigationError
|
||||
) {
|
||||
this.isSpinnerVisible = false;
|
||||
}
|
||||
},
|
||||
() => {
|
||||
this.isSpinnerVisible = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.isSpinnerVisible = false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user