import { CommonModule } from '@angular/common'; import { trigger, state, style, animate, transition } from '@angular/animations'; import { Component, AfterContentChecked, OnInit, OnDestroy, computed, signal, Signal, inject } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatDividerModule } from '@angular/material/divider'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; import { MatSliderModule } from '@angular/material/slider'; import { MatSnackBar, MatSnackBarConfig, MatSnackBarHorizontalPosition, MatSnackBarModule, MatSnackBarVerticalPosition, } from '@angular/material/snack-bar'; import { Observable, Subscription } from 'rxjs'; import { take } from 'rxjs/operators'; import { JumpAddDialogComponent } from '@components/logbook/dialogs'; import { MenuItems, JumpTableComponent } from '@components/shared'; import { Errors, Jump, JumpFile, JumpFileType, JumpListConfig, Range, User, X2Data } from '@models'; import { JumpsService, JumpTableService, UserService } from '@services'; @Component({ imports: [ CommonModule, RouterModule, FormsModule, MatButtonModule, MatCardModule, MatDividerModule, MatExpansionModule, MatIconModule, MatInputModule, MatMenuModule, MatDialogModule, MatSliderModule, MatSnackBarModule, JumpTableComponent, ], selector: 'app-logbook', templateUrl: './logbook.component.html', styleUrl: './logbook.component.scss', animations: [ trigger('flyInOut', [ state('in', style({ transform: 'translateY(0)' })), transition('void => *', [style({ transform: 'translateY(-100%)' }), animate(200)]), transition('* => void', [style({ transform: 'translateY(100%)' }), animate(200)]), ]), ], }) export class LogbookComponent implements OnInit, OnDestroy, AfterContentChecked { private route = inject(ActivatedRoute); private router = inject(Router); private dialog = inject(MatDialog); private snackBar = inject(MatSnackBar); private _jumpsService = inject(JumpsService); private _jumpTableService = inject(JumpTableService); private _userService = inject(UserService); menuItems = inject(MenuItems); private _currentUser: Subscription = new Subscription(); private _subscriptions: Array = []; private _data: Subscription = new Subscription(); private _lastjump: Subscription = new Subscription(); private _lastjump$: Observable = new Observable(); public jumpsCount = 0; public errors!: Errors; public title = 'Carnet de sauts'; public jump: Jump = {} as Jump; public lastJump: Jump = {} as Jump; public currentUser: User = {} as User; public listConfig: JumpListConfig = { type: 'all', filters: {} }; public isAuthenticated = false; public isUser = true; public canModify = false; public isSubmitting = false; public tableRefresh = false; public jumpRefresh = false; public jumpsSinceHundred: Signal = signal(0); public jumpsToHundred: Signal = signal(100); public rangesMinMax: { numero: Range; taille: Range; participants: Range; hauteur: Range; year: Range; } = { numero: { start: 1, end: 100000 }, taille: { start: 1, end: 400 }, participants: { start: 1, end: 200 }, hauteur: { start: 0, end: 10000 }, year: { start: 1970, end: 2020 }, }; ngOnInit() { //this.importJumps(); const data$: Observable<{ isAuthenticated: boolean; lastjump: Jump }> = this.route.data as Observable<{ isAuthenticated: boolean; lastjump: Jump; }>; this._data = data$.subscribe((data: { isAuthenticated: boolean; lastjump: Jump }) => { this.isAuthenticated = data.isAuthenticated; if (!this.isAuthenticated) { this.router.navigateByUrl('/login'); return; } const currentUser$: Observable = this._userService.currentUser; this._currentUser = currentUser$.subscribe((userData: User) => { this.currentUser = userData; this.canModify = this.currentUser.role === 'Admin'; }); const currentYear: number = new Date().getFullYear(); this.lastJump = data['lastjump']; this.rangesMinMax = { numero: { start: 1, end: data.lastjump.numero }, taille: { start: data.lastjump.taille!, end: 230 }, participants: { start: 1, end: 20 }, hauteur: { start: 1000, end: 8000 }, year: { start: 2018, end: currentYear }, }; this.listConfig.filters.numeroRangeStart = this.rangesMinMax.numero.start; this.listConfig.filters.numeroRangeEnd = this.rangesMinMax.numero.end; this.listConfig.filters.tailleRangeStart = this.rangesMinMax.taille.start; this.listConfig.filters.tailleRangeEnd = this.rangesMinMax.taille.end; this.listConfig.filters.participantsRangeStart = this.rangesMinMax.participants.start; this.listConfig.filters.participantsRangeEnd = this.rangesMinMax.participants.end / 2; this.listConfig.filters.hauteurRangeStart = this.rangesMinMax.hauteur.start; this.listConfig.filters.hauteurRangeEnd = this.rangesMinMax.hauteur.end; this.listConfig.filters.yearRangeStart = this.rangesMinMax.year.start; this.listConfig.filters.yearRangeEnd = this.rangesMinMax.year.end; this.jumpsSinceHundred = computed(() => this.lastJump.numero % 100); this.jumpsToHundred = computed(() => 100 - this.jumpsSinceHundred()); /* this._lastjump$ = this._jumpsService.getLastJump(); this._lastjump = this._lastjump$.subscribe((jump) => { const currentYear: number = new Date().getFullYear(); this.lastJump = jump; this.rangesMinMax = { numero: {start: 1, end: jump.numero}, taille: {start: jump.taille!, end: 230}, participants: {start: 1, end: 20}, hauteur: {start: 1000, end: 8000}, year: {start: 2018, end: currentYear} } this.listConfig.filters.numeroRangeStart = this.rangesMinMax.numero.start; this.listConfig.filters.numeroRangeEnd = this.rangesMinMax.numero.end; this.listConfig.filters.tailleRangeStart = this.rangesMinMax.taille.start; this.listConfig.filters.tailleRangeEnd = this.rangesMinMax.taille.end; this.listConfig.filters.participantsRangeStart = this.rangesMinMax.participants.start; this.listConfig.filters.participantsRangeEnd = (this.rangesMinMax.participants.end / 2); this.listConfig.filters.hauteurRangeStart = this.rangesMinMax.hauteur.start; this.listConfig.filters.hauteurRangeEnd = this.rangesMinMax.hauteur.end; this.listConfig.filters.yearRangeStart = this.rangesMinMax.year.start; this.listConfig.filters.yearRangeEnd = this.rangesMinMax.year.end; this.jumpsSinceHundred = computed(() => (this.lastJump.numero % 100)); this.jumpsToHundred = computed(() => (100 - this.jumpsSinceHundred())); }); */ }); } ngOnDestroy() { this._data.unsubscribe(); this._currentUser.unsubscribe(); this._lastjump.unsubscribe(); this._subscriptions.forEach((subscription) => { subscription.unsubscribe(); }); } ngAfterContentChecked() { if (this.tableRefresh != this._jumpTableService.tableRefresh) { this.tableRefresh = this._jumpTableService.tableRefresh; this._lastjump$ = this._jumpsService.getLastJump(); this._lastjump = this._lastjump$.subscribe((jump) => { this.lastJump = jump; }); console.log('lastjump has been updated in logbook component'); } } openAddDialog(): void { this.jump = {}; this.jump.numero = this.lastJump.numero + 1; this.jump.sautants = []; this.jump.participants = 1; const dialogRef = this.dialog.open(JumpAddDialogComponent, { width: '70vw', data: { jump: this.jump, lastJump: this.lastJump }, }); dialogRef .afterClosed() .pipe(take(1)) .subscribe((result) => { if (result != undefined) { this._onEntry(result); } }); } setTotalResults(value: number): void { this.jumpsCount = value; } private _loadSkydiverIdJumps(jumps: Array) { const config: JumpListConfig = { type: 'all', filters: {} } as JumpListConfig; config.filters.limit = 200; config.filters.offset = 0; config.filters.dateRangeStart = `${jumps[0].date!.substring(0, 10)} 00:00:00`; config.filters.dateRangeEnd = `${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 = jumps.length - data.items.length; if (diff < 0) { console.error( `${diff} ${diff > 1 ? 'données' : 'donnée'} altimètre de trop le ${jumps[0].date!.substring(0, 10)}`, ); } if (diff > 0) { console.error( `${diff}/${jumps.length} ${diff > 1 ? 'données altimètre manquantes' : 'donnée altimètre manquante'} le ${jumps[0].date!.substring(0, 10)}`, jumps, data.items, ); } console.log( `${jumps.length} ${jumps.length > 1 ? 'sauts' : 'saut'} le ${data.items[0].date!.substring(0, 10)}`, jumps.sort((a, b) => b.numero! - a.numero!), data.items, ); this._addX2Data(data.items, jumps); } else { console.info('Aucune données altimètre pour le ', jumps[0].date!.substring(0, 10)); } }, error: (err) => { console.error('getAllFromSkydiverIdApi error'); this.errors = err; }, }); this._subscriptions.push(subscription); } private _addJump(jumps: Array, index: number = 0) { this._jumpsService .save(jumps[index]) .pipe(take(1)) .subscribe({ next: (jump) => { jumps[index] = jump; console.log('Ajout du saut', jump.numero); Object.assign(this.jump, jump); this._openSnackBar(`Le saut n°${jump.numero} a été ajouté !`, 'Annuler'); this._resetErrors(); if (jumps.length == index + 1) { this._loadSkydiverIdJumps(jumps); } else { this._addJump(jumps, index + 1); } }, error: (err) => { this.errors = err; this.isSubmitting = false; }, }); } private _addX2Data(x2data: Array, jumps: Array, index: number = 0) { const file: JumpFile = { name: x2data[index].name, path: `/Volumes/Storage/Skydive/X2_Logs/${x2data[index].date!.substring(0, 4)}/`, type: JumpFileType.CSV, } as JumpFile; this._subscriptions.push( this._jumpsService .saveX2Data(jumps[index].slug!, file, x2data[index]) .pipe(take(1)) .subscribe({ next: (jump) => { jumps[index] = jump; console.log(`Le fichier ${file.name} a été ajouté au saut n°${jump.numero} !`, 'OK'); if (jumps.length == index + 1 || x2data.length == index + 1) { this._addKmlFile(jumps); } else { this._addX2Data(x2data, jumps, index + 1); } }, error: (err) => { this.errors = err; }, }), ); } private _addKmlFile(jumps: Array, index: number = 0) { if (jumps[index].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: jumps[index].x2data.name.substring(0, -4) + '.kml', path: `/Volumes/Storage/Skydive/X2_Kmls/${jumps[index].date.substring(0, 4)}/`, type: JumpFileType.KML, } as JumpFile; this._subscriptions.push( this._jumpsService .saveKml(jumps[index].slug, file) .pipe(take(1)) .subscribe({ next: (file) => { console.log( `Le fichier ${file.path}${file.name} a été ajouté au saut n°${jumps[index].numero} !`, 'OK', ); if (jumps.length == index + 1) { this._resetErrors(); this.jumpRefresh = !this.jumpRefresh; this._jumpTableService.updateJumpRefresh(this.jumpRefresh); this._lastjump$ = this._jumpsService.getLastJump(); this._lastjump = this._lastjump$.subscribe((jump) => { this.lastJump = jump; }); } else { this._addKmlFile(jumps, index + 1); } }, error: (err) => { this.errors = err; }, }), ); } } private _onEntry(jumps: Array): void { if (jumps.length) { this._addJump(jumps); } } /* private _onEntry(jumps: Array): void { const max: number = jumps.length; let refresh: boolean = false; jumps.forEach((jump: Jump, index: number) => { console.log('Ajout du saut', jump.numero); Object.assign(this.jump, jump); if (max == index+1) { refresh = true; } this._saveJump(jump, refresh); }); } private _saveJump(jump: Jump, refresh: boolean) { this._jumpsService.save(jump) .pipe(take(1)) .subscribe({ next: (jump) => { this._openSnackBar(`Le saut n°${jump.numero} a été ajouté !`, 'Annuler'); this._resetErrors(); if (refresh) { this.jumpRefresh = !this.jumpRefresh; this._jumpTableService.updateJumpRefresh(this.jumpRefresh); this._lastjump$ = this._jumpsService.getLastJump(); this._lastjump = this._lastjump$.subscribe((jump) => { this.lastJump = jump; }); } }, error: (err) => { this.errors = err; this.isSubmitting = false; } }); } */ private _openSnackBar(content: string, title: string) { const config: MatSnackBarConfig = { horizontalPosition: 'end', verticalPosition: 'bottom', duration: 4000, }; this.snackBar.open(content, title, config); } private _resetErrors(): void { this.errors = { errors: {} }; } }