Files
pmu-plateforme-jeux-admin-plr/src/app/core/services/course.ts
OnlyPapy98 87c33f25cf save result
2025-12-31 10:24:22 +01:00

478 lines
18 KiB
TypeScript

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of, forkJoin } from 'rxjs';
import { map, catchError, switchMap } from 'rxjs/operators';
import { Course, CourseType, CourseStatut } from '../interfaces/course';
import { normalizePage } from '@shared/paging/normalize-page';
import { PaginatedHttpService } from '@shared/paging/paginated-http.service';
import { ListParams, PagedResult } from '@shared/paging/paging';
import { environment } from 'src/environments/environment.development';
import { Reunion } from '../interfaces/reunion';
import { ReunionService } from './reunion';
import { NonPartantService } from './non-partant';
import { ServicesUtils } from './services-utils';
import { HippodromeService } from './hippodrome';
const USE_SERVER = true;
const API_BASE = '/api/courses';
// Interface to match the API response structure for Course
export interface CourseApiResponse {
id: string;
hippodromeId: number;
reunionNumero: number;
reunionDate: string;
nom: string;
numero: number;
heureDepartPrevue: string;
discipline: string;
distanceMetres: number;
categorie: string;
nombrePartants: number;
statut: string;
annulee: boolean;
reporteeMemeJour: boolean;
reporteeAutreJour: boolean;
incidentTechnique: boolean;
nonPartants: Array<unknown>;
typesParisOuverts: Array<string>;
}
export interface NonApiRequest {
nonPartants: String[]
}
@Injectable({ providedIn: 'root' })
export class CourseService {
private apiUrl = environment.apiBaseUrl + API_BASE;
constructor(
private http: HttpClient,
private paginatedHttp: PaginatedHttpService,
private servivesUtil: ServicesUtils,
private hippodromeService: HippodromeService,
private reunionService: ReunionService, // Inject ReunionService
private nonPartantService: NonPartantService // Inject NonPartantService
) {}
// Helper method to get ngrok bypass headers
private getNgrokHeaders(): Record<string, string> {
const isNgrok =
environment.apiBaseUrl.includes('ngrok-free.app') ||
environment.apiBaseUrl.includes('ngrok.io') ||
environment.apiBaseUrl.includes('ngrok');
return isNgrok ? { 'ngrok-skip-browser-warning': 'true' } : {};
}
list(params: ListParams): Observable<PagedResult<Course>> {
const coursesList = this.http.get<PagedResult<CourseApiResponse>>(this.apiUrl, {
headers: this.getNgrokHeaders(),
params: this.servivesUtil.getParamsFromModel(params),
});
return coursesList.pipe(
switchMap((res) => {
const items = res.content ?? [];
// Transforme chaque course en Observable<Course>
const courseObservables = items.map((apiCourse) =>
this.hippodromeService.getById(String(apiCourse.hippodromeId)).pipe(
// Map le hippodrome récupéré dans l'objet Course final
map((hippodrome) => ({
id: apiCourse.id,
hippodrome: hippodrome ?? undefined,
reunionNumero: apiCourse.reunionNumero,
reunionDate: apiCourse.reunionDate,
nom: apiCourse.nom,
numero: apiCourse.numero,
heureDepartPrevue: apiCourse.heureDepartPrevue,
discipline: apiCourse.discipline,
distanceMetres: apiCourse.distanceMetres,
categorie: apiCourse.categorie,
nombrePartants: apiCourse.nombrePartants,
statut: apiCourse.statut,
annulee: apiCourse.annulee,
reporteeMemeJour: apiCourse.reporteeMemeJour,
reporteeAutreJour: apiCourse.reporteeAutreJour,
incidentTechnique: apiCourse.incidentTechnique,
nonPartants: apiCourse.nonPartants || [],
typesParisOuverts: apiCourse.typesParisOuverts || [],
}))
)
);
// ForkJoin pour attendre que tous les hippodromes soient résolus
return forkJoin(courseObservables).pipe(
map((courses) => {
const mapped: PagedResult<Course> = {
pageable: res.pageable,
totalPages: res.totalPages,
totalElements: res.totalElements,
content: courses,
};
return mapped;
})
);
}),
catchError((err) => {
console.error('Error fetching courses list:', err);
return of({ content: [], pageable: {pageNumber: 1, pageSize: 0, total: 0}, totalPages:1, totalElements:0 } as PagedResult<Course>);
})
);
}
private applyClientFilters(data: Course[], params: ListParams): Course[] {
let filtered = [...data];
// Search filter
// const q = (params.search ?? '').toLowerCase();
// if (q) {
// filtered = filtered.filter((c) => {
// const reunionName = c.reunion?.nom?.toLowerCase?.() ?? '';
// const hippodromeName = c.reunion?.hippodrome?.nom?.toLowerCase?.() ?? '';
// return (
// c.nom.toLowerCase().includes(q) ||
// c.type.toLowerCase().includes(q) ||
// reunionName.includes(q) ||
// hippodromeName.includes(q)
// );
// });
// }
// Sort
if (params.sortKey && params.sortDir) {
const { sortKey, sortDir } = params;
filtered.sort((a: any, b: any) => {
const getValue = (obj: any, path: string): any =>
path.split('.').reduce((o, key) => o?.[key], obj);
const va = getValue(a, sortKey);
const vb = getValue(b, sortKey);
const sa = va == null ? '' : String(va);
const sb = vb == null ? '' : String(vb);
const cmp = sa.localeCompare(sb, 'fr', { numeric: true });
return sortDir === 'asc' ? cmp : -cmp;
});
}
return filtered;
}
getById(id: string): Observable<Course | undefined> {
if (USE_SERVER) {
return this.http
.get<CourseApiResponse>(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() })
.pipe(
switchMap((apiCourse) => {
// Fetch the reunion (non-partants are already included in the API response)
return this.hippodromeService.getById(String(apiCourse.hippodromeId)).pipe(
map((hippodrome) => {
if (!hippodrome) {
return undefined;
}
return {
id: String(apiCourse.id),
hippodrome: hippodrome ?? undefined,
reunionNumero: apiCourse.reunionNumero,
reunionDate: apiCourse.reunionDate,
nom: apiCourse.nom,
numero: apiCourse.numero,
heureDepartPrevue: apiCourse.heureDepartPrevue,
discipline: apiCourse.discipline,
distanceMetres: apiCourse.distanceMetres,
categorie: apiCourse.categorie,
nombrePartants: apiCourse.nombrePartants,
statut: apiCourse.statut,
annulee: apiCourse.annulee,
reporteeMemeJour: apiCourse.reporteeMemeJour,
reporteeAutreJour: apiCourse.reporteeAutreJour,
incidentTechnique: apiCourse.incidentTechnique,
nonPartants: apiCourse.nonPartants || [],
typesParisOuverts: apiCourse.typesParisOuverts || []
};
})
);
}),
catchError((err) => {
console.error(`Error fetching course ${id}:`, err);
return of(undefined);
})
);
}
return of(undefined);
}
// getByReunionId(reunionId: string): Observable<Course[]> {
// if (USE_SERVER) {
// return this.http
// .get<CourseApiResponse[]>(`${this.apiUrl}/reunion/${reunionId}`, {
// headers: this.getNgrokHeaders(),
// })
// .pipe(
// switchMap((apiData) => {
// // Fetch the reunion once
// return this.reunionService.getById(reunionId).pipe(
// map((reunion) => {
// if (!reunion) {
// return [];
// }
// // Transform all courses with the same reunion
// return apiData.map((apiCourse) => ({
// id: String(apiCourse.id),
// type: apiCourse.type,
// numero: apiCourse.numero,
// nom: apiCourse.nom,
// dateDepartCourse: apiCourse.dateDepartCourse,
// dateDebutParis: apiCourse.dateDebutParis,
// dateFinParis: apiCourse.dateFinParis,
// reunion,
// reunionCourse: apiCourse.reunionCourse,
// particularite: apiCourse.particularite,
// partants: apiCourse.partants,
// distance: apiCourse.distance,
// condition: apiCourse.condition,
// statut: apiCourse.statut as CourseStatut,
// nonPartants: apiCourse.nonPartants || [],
// estTerminee: apiCourse.estTerminee,
// estAnnulee: apiCourse.estAnnulee,
// nombreChevauxInscrits: apiCourse.nombreChevauxInscrits,
// adeadHeat: apiCourse.adeadHeat,
// createdBy: apiCourse.createdBy,
// validatedBy: apiCourse.validatedBy,
// createdAt: apiCourse.createdAt || new Date().toISOString(),
// updatedAt: apiCourse.updatedAt || new Date().toISOString(),
// }));
// })
// );
// }),
// catchError((err) => {
// console.error(`Error fetching courses for reunion ${reunionId}:`, err);
// return of([]);
// })
// );
// }
// return of([]);
// }
// search(query: string): Observable<Course[]> {
// if (USE_SERVER) {
// return this.http
// .get<CourseApiResponse[]>(`${this.apiUrl}/search`, {
// params: { q: query.trim() },
// headers: this.getNgrokHeaders(),
// })
// .pipe(
// switchMap((apiData) => {
// // Extract unique reunionIds
// const uniqueReunionIds = [...new Set(apiData.map((c) => String(c.reunionId)))];
// // Fetch all reunions in parallel
// const reunionRequests = uniqueReunionIds.map((id) =>
// this.reunionService
// .getById(id)
// .pipe(catchError(() => of<Reunion | undefined>(undefined)))
// );
// return forkJoin(reunionRequests).pipe(
// map((reunions) => {
// // Create a map of reunionId -> Reunion
// const reunionMap = new Map<string, Reunion>();
// uniqueReunionIds.forEach((id, index) => {
// const reunion = reunions[index];
// if (reunion) {
// reunionMap.set(id, reunion);
// }
// });
// // Transform API data to Course objects
// return apiData
// .map((apiCourse) => {
// const reunion = reunionMap.get(String(apiCourse.reunionId));
// if (!reunion) {
// return null;
// }
// return {
// id: String(apiCourse.id),
// type: apiCourse.type,
// numero: apiCourse.numero,
// nom: apiCourse.nom,
// dateDepartCourse: apiCourse.dateDepartCourse,
// dateDebutParis: apiCourse.dateDebutParis,
// dateFinParis: apiCourse.dateFinParis,
// reunion,
// reunionCourse: apiCourse.reunionCourse,
// particularite: apiCourse.particularite,
// partants: apiCourse.partants,
// distance: apiCourse.distance,
// condition: apiCourse.condition,
// statut: apiCourse.statut as CourseStatut,
// nonPartants: apiCourse.nonPartants || [],
// estTerminee: apiCourse.estTerminee,
// estAnnulee: apiCourse.estAnnulee,
// nombreChevauxInscrits: apiCourse.nombreChevauxInscrits,
// adeadHeat: apiCourse.adeadHeat,
// createdBy: apiCourse.createdBy,
// validatedBy: apiCourse.validatedBy,
// createdAt: apiCourse.createdAt || new Date().toISOString(),
// updatedAt: apiCourse.updatedAt || new Date().toISOString(),
// } as Course;
// })
// .filter((c): c is Course => c !== null);
// })
// );
// }),
// catchError((err) => {
// console.error(`Error searching courses with query ${query}:`, err);
// return of([]);
// })
// );
// }
// return of([]);
// }
create(payload: Partial<CourseApiResponse>): Observable<Course> {
if (USE_SERVER) {
// Transform payload to API format (send reunionId instead of reunion object)
const apiPayload: Partial<CourseApiResponse> = payload;
return this.http
.post<CourseApiResponse>(this.apiUrl, apiPayload, { headers: this.getNgrokHeaders() })
.pipe(
switchMap((apiCourse) => {
// Fetch the reunion to build the full Course object
return this.hippodromeService.getById(String(apiCourse.hippodromeId)).pipe(
map((hippodrome) => {
if (!hippodrome) {
throw new Error('Hippodrome not found');
}
const item: Course = {
id: String(apiCourse.id),
hippodrome: hippodrome ?? undefined,
reunionNumero: apiCourse.reunionNumero,
reunionDate: apiCourse.reunionDate,
nom: apiCourse.nom,
numero: apiCourse.numero,
heureDepartPrevue: apiCourse.heureDepartPrevue,
discipline: apiCourse.discipline,
distanceMetres: apiCourse.distanceMetres,
categorie: apiCourse.categorie,
nombrePartants: apiCourse.nombrePartants,
statut: apiCourse.statut,
annulee: apiCourse.annulee,
reporteeMemeJour: apiCourse.reporteeMemeJour,
reporteeAutreJour: apiCourse.reporteeAutreJour,
incidentTechnique: apiCourse.incidentTechnique,
nonPartants: apiCourse.nonPartants || [],
typesParisOuverts: apiCourse.typesParisOuverts || []
};
return item;
})
);
}),
catchError((err) => {
console.error('Error creating course:', err);
throw err;
})
);
}
throw new Error('Server mode is required');
}
update(id: string, payload: Partial<CourseApiResponse>): Observable<Course | undefined> {
if (USE_SERVER) {
// Transform payload to API format (send reunionId instead of reunion object
return this.http
.put<CourseApiResponse>(`${this.apiUrl}/${id}`, payload, {
headers: this.getNgrokHeaders(),
})
.pipe(
switchMap((apiCourse) => {
// Fetch the reunion to build the full Course object
return this.hippodromeService.getById(String(apiCourse.hippodromeId)).pipe(
map((hippodrome) => {
if (!hippodrome) {
throw new Error('Reunion not found');
}
return {
id: String(apiCourse.id),
hippodrome: hippodrome ?? undefined,
reunionNumero: apiCourse.reunionNumero,
reunionDate: apiCourse.reunionDate,
nom: apiCourse.nom,
numero: apiCourse.numero,
heureDepartPrevue: apiCourse.heureDepartPrevue,
discipline: apiCourse.discipline,
distanceMetres: apiCourse.distanceMetres,
categorie: apiCourse.categorie,
nombrePartants: apiCourse.nombrePartants,
statut: apiCourse.statut,
annulee: apiCourse.annulee,
reporteeMemeJour: apiCourse.reporteeMemeJour,
reporteeAutreJour: apiCourse.reporteeAutreJour,
incidentTechnique: apiCourse.incidentTechnique,
nonPartants: apiCourse.nonPartants || [],
typesParisOuverts: apiCourse.typesParisOuverts || []
};
})
);
}),
catchError((err) => {
console.error(`Error updating course ${id}:`, err);
return of(undefined);
})
);
}
throw new Error('Server mode is required');
}
updateStatut(id: string, statut: CourseStatut): Observable<Course | undefined> {
if (USE_SERVER) {
return this.http
.patch<Course>(
`${this.apiUrl}/${id}/statut`,
{ statut },
{ headers: this.getNgrokHeaders() }
)
.pipe(
catchError((err) => {
console.error(`Error updating course statut ${id}:`, err);
return this.update(id, { statut });
})
);
}
return this.update(id, { statut });
}
delete(id: string): Observable<boolean> {
if (USE_SERVER) {
return this.http
.delete<void>(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() })
.pipe(
map(() => true),
catchError((err) => {
console.error(`Error deleting course ${id}:`, err);
return of(false);
})
);
}
throw new Error('Server mode is required');
}
addNonPartant(courseId: string, npList: string[]) {
const payload = {
nonPartants: [...npList]
}
console.warn('addNonPartant is deprecated. Use setNonPartants instead.');
return this.setNonPartants(courseId, payload);
}
setNonPartants(courseId: string, npList: NonApiRequest): Observable<CourseApiResponse | undefined> {
return this.nonPartantService.replaceNonPartants(courseId, npList).pipe(
map((updatedNonPartants) => updatedNonPartants),
catchError((err) => {
console.error(`Error setting nonPartants for course ${courseId}:`, err);
return of(undefined);
})
);
}
}