be5f5775ef
- ng update @angular/core@20 @angular/cli@20 @angular/material@20 @angular-eslint@20 @angular/google-maps@20 - Remove ng-chartist (abandoned, incompatible with Angular 20): replace <x-chartist> with <app-bars-chart> in dropzones-bar and jumps-by-month - Migrate all constructor injection to inject() function (ng generate @angular/core:inject, 67 files) - TypeScript 5.5 → 5.9.3 - DOCUMENT import moved from @angular/common to @angular/core (automatic migration) - tsconfig moduleResolution updated to "bundler"
555 lines
20 KiB
TypeScript
555 lines
20 KiB
TypeScript
import {
|
|
AfterContentChecked,
|
|
Component,
|
|
EventEmitter,
|
|
Input,
|
|
Output,
|
|
OnChanges,
|
|
OnDestroy,
|
|
ViewChild,
|
|
inject,
|
|
} from '@angular/core';
|
|
import { CommonModule, DecimalPipe, DatePipe } from '@angular/common';
|
|
import { UntypedFormBuilder, UntypedFormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
import { Router, RouterLink } from '@angular/router';
|
|
import { MatButtonModule } from '@angular/material/button';
|
|
import { MatCardModule } from '@angular/material/card';
|
|
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 { MatMenuModule } from '@angular/material/menu';
|
|
import { MatNativeDateModule } from '@angular/material/core';
|
|
import { MatPaginator, MatPaginatorIntl, MatPaginatorModule } from '@angular/material/paginator';
|
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
|
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 { FrenchPaginator } from '@components/shared/french-paginator.component';
|
|
import {
|
|
ColumnDefinition,
|
|
Errors,
|
|
Jump,
|
|
JumpByDay,
|
|
JumpFile,
|
|
JumpFileType,
|
|
JumpList,
|
|
JumpListConfig,
|
|
jumpColumns,
|
|
} from '@models';
|
|
import { JumpsService, JumpTableService } from '@services';
|
|
import {
|
|
JumpAddDialogComponent,
|
|
JumpDeleteDialogComponent,
|
|
JumpEditDialogComponent,
|
|
JumpViewDialogComponent,
|
|
} from '@components/logbook/dialogs';
|
|
|
|
@Component({
|
|
imports: [
|
|
CommonModule,
|
|
DecimalPipe,
|
|
DatePipe,
|
|
RouterLink,
|
|
FormsModule,
|
|
ReactiveFormsModule,
|
|
NgxSkeletonLoaderModule,
|
|
MatButtonModule,
|
|
MatCardModule,
|
|
MatDialogModule,
|
|
MatFormFieldModule,
|
|
MatIconModule,
|
|
MatInputModule,
|
|
MatNativeDateModule,
|
|
MatMenuModule,
|
|
MatPaginatorModule,
|
|
MatProgressBarModule,
|
|
MatProgressSpinnerModule,
|
|
MatSnackBarModule,
|
|
MatSortModule,
|
|
MatTableModule,
|
|
JumpAddDialogComponent,
|
|
JumpDeleteDialogComponent,
|
|
JumpEditDialogComponent,
|
|
JumpViewDialogComponent,
|
|
],
|
|
selector: 'app-jump-table',
|
|
styleUrl: './jump-table.component.scss',
|
|
templateUrl: './jump-table.component.html',
|
|
providers: [{ provide: MatPaginatorIntl, useClass: FrenchPaginator }],
|
|
})
|
|
export class JumpTableComponent implements OnChanges, OnDestroy, AfterContentChecked {
|
|
private router = inject(Router);
|
|
private _jumpsService = inject(JumpsService);
|
|
private _jumpTableService = inject(JumpTableService);
|
|
private dialog = inject(MatDialog);
|
|
private fb = inject(UntypedFormBuilder);
|
|
private snackBar = inject(MatSnackBar);
|
|
|
|
private _jumps: Subscription = new Subscription();
|
|
private _subscriptions: Array<Subscription> = [];
|
|
//private _jump: Subscription = new Subscription();
|
|
private _query: JumpListConfig = { type: 'all', filters: {} };
|
|
//private _lastjump: Jump = {} as Jump;
|
|
private _currentPage = 1;
|
|
public errors!: Errors;
|
|
public jump: Jump = {} as Jump;
|
|
public jumps!: MatTableDataSource<Jump>;
|
|
public jumpsCount = 0;
|
|
/* public displayedColumns: string[] = [
|
|
'numero', 'date', 'lieu', 'oaci', 'aeronef',
|
|
'imat', 'hauteur', 'voile', 'taille', 'categorie',
|
|
'module', 'participants', 'programme', 'accessoires',
|
|
'zone', 'files', 'actions'
|
|
];*/
|
|
public displayedColumns: string[] = [
|
|
'numero',
|
|
'date',
|
|
'lieu',
|
|
'aeronef',
|
|
'hauteur',
|
|
'voile',
|
|
'categorie',
|
|
'participants',
|
|
'zone',
|
|
'accessoires',
|
|
'files',
|
|
'actions',
|
|
];
|
|
//public displayedColumns: string[] = jumpColumns.map((col) => col.key)
|
|
public columnsSchema: Array<ColumnDefinition> = jumpColumns;
|
|
public searchForm: UntypedFormGroup;
|
|
public loading = false;
|
|
public isSubmitting = false;
|
|
public isDeleting = false;
|
|
public tableRefresh = false;
|
|
public jumpRefresh = false;
|
|
//public valid: any = {};
|
|
|
|
@ViewChild(MatSort) sort!: MatSort;
|
|
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
|
|
|
constructor() {
|
|
this._resetErrors();
|
|
this.searchForm = this.fb.group({
|
|
//numero: ['', Validators.required]
|
|
numero: '',
|
|
});
|
|
}
|
|
|
|
@Input() title = '';
|
|
@Input() limit = 0;
|
|
@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;
|
|
}
|
|
@Output() countChange: EventEmitter<number> = new EventEmitter();
|
|
|
|
ngOnChanges() {
|
|
this.loading = true;
|
|
this._query = this.config;
|
|
this.runQuery();
|
|
//console.log('ngOnChanges event in jump-table component');
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
this._jumps.unsubscribe();
|
|
this._subscriptions.forEach((subscription) => {
|
|
subscription.unsubscribe();
|
|
});
|
|
}
|
|
|
|
ngAfterContentChecked() {
|
|
if (this.jumpRefresh != this._jumpTableService.jumpRefresh) {
|
|
this.jumpRefresh = this._jumpTableService.jumpRefresh;
|
|
this.runQuery();
|
|
//console.log('table has been refreshed in jump-table component');
|
|
}
|
|
}
|
|
|
|
runQuery() {
|
|
/*
|
|
const jump$: Observable<Jump> = this._jumpsService.getLastJump();
|
|
this._jump = jump$.subscribe((jump) => {
|
|
this._lastjump = jump;
|
|
});
|
|
*/
|
|
|
|
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;
|
|
}
|
|
/*
|
|
this._query.filters.numeroRangeStart = 1;
|
|
this._query.filters.numeroRangeEnd = 0;
|
|
this._query.filters.tailleRangeStart = 135;
|
|
this._query.filters.tailleRangeEnd = 230;
|
|
this._query.filters.participantsRangeStart = 1;
|
|
this._query.filters.participantsRangeEnd = 10;
|
|
this._query.filters.hauteurRangeStart = 0;
|
|
this._query.filters.hauteurRangeEnd = 8000;
|
|
*/
|
|
|
|
const jumps$: Observable<JumpList> = this._jumpsService.query(this._query);
|
|
this._jumps = jumps$.subscribe({
|
|
next: (data: JumpList) => {
|
|
//this.updateJumps(data.jumps);
|
|
//this.linkSkydiverIdJumps();
|
|
//this.getKmlJumpFiles(data.jumps);
|
|
this.loading = false;
|
|
this.jumps = new MatTableDataSource<Jump>(data.jumps);
|
|
this.jumpsCount = data.jumpsCount;
|
|
this.countChange.emit(this.jumpsCount);
|
|
this.paginator.length = data.jumpsCount;
|
|
this.jumps.paginator = this.paginator;
|
|
this.jumps.sort = this.sort;
|
|
this._resetErrors();
|
|
},
|
|
error: (err) => {
|
|
this.errors = err;
|
|
},
|
|
});
|
|
}
|
|
|
|
linkSkydiverIdJumps() {
|
|
const config: JumpListConfig = { type: 'all', filters: {} } as JumpListConfig;
|
|
config.filters.limit = 200;
|
|
config.filters.offset = 0;
|
|
config.filters.dateRangeStart = '2024-01-01 00:00:00';
|
|
//const year = new Date().getFullYear();
|
|
//config.filters.dateRangeEnd = `${year}-12-31 23:59:59`;
|
|
config.filters.dateRangeEnd = '2024-12-31 23:59:59';
|
|
//console.log(config.filters);
|
|
const subscription: Subscription = this._jumpsService
|
|
.getAllByDay(config)
|
|
.pipe(take(1))
|
|
.subscribe({
|
|
next: (days) => {
|
|
this._resetErrors();
|
|
//console.log('getAllByDay : ', days);
|
|
days.forEach((day) => {
|
|
this.loadSkydiverIdJumps(day, config);
|
|
});
|
|
},
|
|
error: (err) => {
|
|
this.errors = err;
|
|
},
|
|
});
|
|
this._subscriptions.push(subscription);
|
|
}
|
|
|
|
loadSkydiverIdJumps(day: JumpByDay, config: JumpListConfig) {
|
|
config.filters.dateRangeStart = `${day.jumps[0].date!.substring(0, 10)} 00:00:00`;
|
|
config.filters.dateRangeEnd = `${day.jumps[0].date!.substring(0, 10)} 23:59:59`;
|
|
const subscription: Subscription = this._jumpsService
|
|
.getAllFromSkydiverIdApi(config)
|
|
.pipe(take(1))
|
|
.subscribe({
|
|
next: (data) => {
|
|
if (data.items.length) {
|
|
const diff = day.count - data.items.length;
|
|
if (diff < 0) {
|
|
console.error(
|
|
`${diff} ${diff > 1 ? 'données' : 'donnée'} altimètre de trop le ${day.jumps[0].date!.substring(0, 10)}`,
|
|
);
|
|
} else {
|
|
if (diff > 0) {
|
|
console.error(
|
|
`${diff}/${day.count} ${diff > 1 ? 'données altimètre manquantes' : 'donnée altimètre manquante'} le ${day.jumps[0].date!.substring(0, 10)}`,
|
|
day.jumps,
|
|
data.items,
|
|
);
|
|
}
|
|
console.log(
|
|
`${day.count} ${day.count > 1 ? 'sauts' : 'saut'} le ${data.items[0].date!.substring(0, 10)}`,
|
|
day.jumps.sort((a, b) => b.numero! - a.numero!),
|
|
data.items,
|
|
);
|
|
data.items.map((jump, index) => {
|
|
const file: JumpFile = {
|
|
name: jump.name,
|
|
path: `/Volumes/Storage/Skydive/X2_Logs/${day.jumps[index].date!.substring(0, 4)}/`,
|
|
type: JumpFileType.CSV,
|
|
} as JumpFile;
|
|
this._subscriptions.push(
|
|
this._jumpsService
|
|
.saveX2Data(day.jumps[index].slug!, file, jump)
|
|
.pipe(take(1))
|
|
.subscribe({
|
|
next: (jump) => {
|
|
this._resetErrors();
|
|
console.log(
|
|
`Le fichier ${file.name} a été ajouté au saut n°${jump.numero} !`,
|
|
'OK',
|
|
);
|
|
},
|
|
error: (err) => {
|
|
this.errors = err;
|
|
},
|
|
}),
|
|
);
|
|
return jump;
|
|
});
|
|
}
|
|
//console.log(`${day.count} ${day.count > 1 ? 'sauts' : 'saut'} le ${day.jumps[0].date!.substring(0, 10)}`);
|
|
//console.log(`${data.items.length} ${data.items.length > 1 ? 'sauts' : 'saut'} le ${data.items[0].date!.substring(0, 10)}`, data);
|
|
} else {
|
|
console.info('Aucune données altimètre pour le ', day.jumps[0].date!.substring(0, 10));
|
|
}
|
|
},
|
|
error: (err) => {
|
|
console.error('getAllFromSkydiverIdApi error');
|
|
this.errors = err;
|
|
},
|
|
});
|
|
this._subscriptions.push(subscription);
|
|
}
|
|
|
|
updateJumps(jumps: Array<Jump>) {
|
|
jumps.map((jump) => {
|
|
jump.files = [];
|
|
if (jump.dossier !== '' && jump.video !== '') {
|
|
const file: JumpFile = {
|
|
name: jump.video!,
|
|
path: jump.dossier!,
|
|
type: JumpFileType.VIDEO,
|
|
} as JumpFile;
|
|
this._subscriptions.push(
|
|
this._jumpsService
|
|
.saveFile(jump.slug, file)
|
|
.pipe(take(1))
|
|
.subscribe({
|
|
next: (file) => {
|
|
this._resetErrors();
|
|
jump.files.push(file);
|
|
//console.log(`Le fichier ${file.name} a été ajouté au saut n°${jump.numero} !`, 'OK');
|
|
},
|
|
error: (err) => {
|
|
this.errors = err;
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
return jump;
|
|
});
|
|
}
|
|
|
|
getKmlJumpFiles(jumps: Array<Jump>) {
|
|
jumps.map((jump) => {
|
|
if (jump.x2data !== null) {
|
|
//console.log(`Le fichier ${jump.x2data.name} existe pour le saut n°${jump.numero} !`, `https://skydiver.id/api/jump/${jump.x2data.id}/kml`);
|
|
const file: JumpFile = {
|
|
name: jump.x2data.name.substring(0, -4) + '.kml',
|
|
path: `/X2_Kmls/${jump.date.substring(0, 4)}/`,
|
|
type: JumpFileType.KML,
|
|
} as JumpFile;
|
|
this._subscriptions.push(
|
|
this._jumpsService
|
|
.saveKml(jump.slug, file)
|
|
.pipe(take(1))
|
|
.subscribe({
|
|
next: (file) => {
|
|
this._resetErrors();
|
|
console.log(
|
|
`Le fichier ${file.path}${file.name} a été ajouté au saut n°${jump.numero} !`,
|
|
'OK',
|
|
);
|
|
},
|
|
error: (err) => {
|
|
this.errors = err;
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
return jump;
|
|
});
|
|
}
|
|
|
|
deleteJump(slug: string) {
|
|
this.isDeleting = true;
|
|
this._subscriptions.push(
|
|
this._jumpsService
|
|
.destroy(slug)
|
|
.pipe(take(1))
|
|
.subscribe(() => {
|
|
this.router.navigateByUrl('/');
|
|
}),
|
|
);
|
|
}
|
|
|
|
openDeleteDialog(jump: Jump): void {
|
|
const dialogRef = this.dialog.open(JumpDeleteDialogComponent, {
|
|
width: '60vw',
|
|
data: jump,
|
|
});
|
|
this._subscriptions.push(
|
|
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: '70vw',
|
|
data: jump,
|
|
});
|
|
this._subscriptions.push(
|
|
dialogRef
|
|
.afterClosed()
|
|
.pipe(take(1))
|
|
.subscribe((result) => {
|
|
if (result != undefined) {
|
|
this._onEdit(result);
|
|
}
|
|
}),
|
|
);
|
|
}
|
|
|
|
openViewDialog(jump: Jump): void {
|
|
const dialogRef = this.dialog.open(JumpViewDialogComponent, {
|
|
width: '60vw',
|
|
data: jump,
|
|
});
|
|
dialogRef.afterClosed();
|
|
}
|
|
|
|
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 _onDelete(slug: string): void {
|
|
try {
|
|
this.isDeleting = true;
|
|
this._subscriptions.push(
|
|
this._jumpsService
|
|
.destroy(slug)
|
|
.pipe(take(1))
|
|
.subscribe({
|
|
next: () => {
|
|
this._resetErrors();
|
|
this._openSnackBar(`Le saut a été supprimé !`, 'OK');
|
|
this.runQuery();
|
|
this.tableRefresh = !this.tableRefresh;
|
|
this._jumpTableService.updateTableRefresh(this.tableRefresh);
|
|
},
|
|
error: (err) => {
|
|
this.errors = err;
|
|
},
|
|
}),
|
|
);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
private _onEdit(jump: Jump): void {
|
|
try {
|
|
this.isSubmitting = true;
|
|
this._subscriptions.push(
|
|
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();
|
|
this.tableRefresh = !this.tableRefresh;
|
|
this._jumpTableService.updateTableRefresh(this.tableRefresh);
|
|
},
|
|
error: (err) => {
|
|
this.errors = err;
|
|
this.isSubmitting = false;
|
|
},
|
|
}),
|
|
);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
private _openSnackBar(content: string, title: string) {
|
|
this.snackBar.open(content, title, {
|
|
horizontalPosition: <MatSnackBarHorizontalPosition>'end',
|
|
verticalPosition: <MatSnackBarVerticalPosition>'bottom',
|
|
duration: 4000,
|
|
});
|
|
}
|
|
|
|
private _resetErrors(): void {
|
|
this.errors = { errors: {} };
|
|
}
|
|
|
|
/*
|
|
disableSubmit(id: number) {
|
|
if (this.valid[id]) {
|
|
return Object.values(this.valid[id]).some((item) => item === false)
|
|
}
|
|
return false
|
|
}
|
|
|
|
inputHandler(e: any, id: number, key: string) {
|
|
if (!this.valid[id]) {
|
|
this.valid[id] = {}
|
|
}
|
|
this.valid[id][key] = e.target.validity.valid
|
|
}
|
|
*/
|
|
|
|
/*
|
|
isAllSelected() {
|
|
return this.jumps.data.every((item) => item.isSelected)
|
|
}
|
|
|
|
isAnySelected() {
|
|
return this.jumps.data.some((item) => item.isSelected)
|
|
}
|
|
|
|
selectAll(event: any) {
|
|
this.jumps.data = this.jumps.data.map((item) => ({
|
|
...item,
|
|
isSelected: event.checked,
|
|
}))
|
|
}
|
|
*/
|
|
}
|