first commit
This commit is contained in:
874
src/app/core/services/course.ts
Normal file
874
src/app/core/services/course.ts
Normal file
@@ -0,0 +1,874 @@
|
||||
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';
|
||||
|
||||
const USE_SERVER = true;
|
||||
const API_BASE = '/api/v1/courses';
|
||||
// Interface to match the API response structure for Course
|
||||
interface CourseApiResponse {
|
||||
id: number;
|
||||
type: string;
|
||||
numero: number;
|
||||
nom: string;
|
||||
dateDepartCourse: string;
|
||||
dateDebutParis: string;
|
||||
dateFinParis: string;
|
||||
reunionId: number; // API returns reunionId
|
||||
reunionCourse: number;
|
||||
particularite?: string;
|
||||
partants: number;
|
||||
distance: number;
|
||||
condition?: string;
|
||||
estTerminee: boolean;
|
||||
estAnnulee: boolean;
|
||||
statut: CourseStatut;
|
||||
nombreChevauxInscrits: number;
|
||||
createdBy: string;
|
||||
validatedBy: string | null;
|
||||
createdAt: string | null;
|
||||
updatedAt: string | null;
|
||||
nonPartants: string[];
|
||||
adeadHeat: boolean;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CourseService {
|
||||
private apiUrl = environment.apiBaseUrl + API_BASE;
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private paginatedHttp: PaginatedHttpService,
|
||||
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,
|
||||
usePaginationEndpoint: boolean = false
|
||||
): Observable<PagedResult<Course>> {
|
||||
if (USE_SERVER) {
|
||||
// If there's a search query, use the search endpoint
|
||||
if (params.search && params.search.trim()) {
|
||||
return this.search(params.search.trim()).pipe(
|
||||
map((courses) => {
|
||||
// Apply client-side sorting and pagination
|
||||
let filtered = [...courses];
|
||||
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
|
||||
const total = filtered.length;
|
||||
const start = (params.page - 1) * params.perPage;
|
||||
const pageData = filtered.slice(start, start + params.perPage);
|
||||
|
||||
const totalByType = filtered.reduce<Record<string, number>>((acc, c) => {
|
||||
const type = String(c.type);
|
||||
acc[type] = (acc[type] ?? 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
const totalRunning = filtered.filter(
|
||||
(c) => c.statut === CourseStatut.RUNNING || c.statut === 'RUNNING'
|
||||
).length;
|
||||
const totalClosed = filtered.filter(
|
||||
(c) => c.statut === CourseStatut.CLOSED || c.statut === 'CLOSED'
|
||||
).length;
|
||||
|
||||
return normalizePage<Course>(
|
||||
{
|
||||
data: pageData,
|
||||
meta: {
|
||||
total,
|
||||
totalRunning,
|
||||
totalClosed,
|
||||
totalByType,
|
||||
},
|
||||
},
|
||||
params.page,
|
||||
params.perPage
|
||||
);
|
||||
}),
|
||||
catchError((err) => {
|
||||
console.error('Error searching courses:', err);
|
||||
return of(
|
||||
normalizePage<Course>(
|
||||
{
|
||||
data: [],
|
||||
meta: {
|
||||
total: 0,
|
||||
totalRunning: 0,
|
||||
totalClosed: 0,
|
||||
totalByType: {},
|
||||
},
|
||||
},
|
||||
params.page,
|
||||
params.perPage
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (usePaginationEndpoint) {
|
||||
return this.paginatedHttp
|
||||
.fetch<CourseApiResponse>(this.apiUrl, params, {
|
||||
zeroBasedPageIndex: false,
|
||||
})
|
||||
.pipe(
|
||||
switchMap((pagedResult) => {
|
||||
// Handle empty data case
|
||||
if (!pagedResult.data || pagedResult.data.length === 0) {
|
||||
return of(
|
||||
normalizePage<Course>(
|
||||
{
|
||||
data: [],
|
||||
meta: {
|
||||
total: pagedResult.meta?.total ?? 0,
|
||||
totalRunning: 0,
|
||||
totalClosed: 0,
|
||||
totalByType: {},
|
||||
},
|
||||
},
|
||||
params.page,
|
||||
params.perPage
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Extract unique reunionIds
|
||||
const uniqueReunionIds = [
|
||||
...new Set(pagedResult.data.map((c) => String(c.reunionId))),
|
||||
];
|
||||
|
||||
// If no reunion IDs, we can't build valid Reunion objects – return empty page
|
||||
if (uniqueReunionIds.length === 0) {
|
||||
return of(
|
||||
normalizePage<Course>(
|
||||
{
|
||||
data: [],
|
||||
meta: {
|
||||
total: pagedResult.meta?.total ?? 0,
|
||||
totalRunning: 0,
|
||||
totalClosed: 0,
|
||||
totalByType: {},
|
||||
},
|
||||
},
|
||||
params.page,
|
||||
params.perPage
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
const transformedData: Course[] = pagedResult.data
|
||||
.map((apiCourse) => {
|
||||
const reunion = reunionMap.get(String(apiCourse.reunionId));
|
||||
if (!reunion) {
|
||||
// If we couldn't resolve the reunion, drop this course to keep typing sound
|
||||
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);
|
||||
|
||||
// Calculate meta stats
|
||||
const totalByType = transformedData.reduce<Record<string, number>>((acc, c) => {
|
||||
const type = String(c.type);
|
||||
acc[type] = (acc[type] ?? 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
const totalRunning = transformedData.filter(
|
||||
(c) => c.statut === CourseStatut.RUNNING || c.statut === 'RUNNING'
|
||||
).length;
|
||||
const totalClosed = transformedData.filter(
|
||||
(c) => c.statut === CourseStatut.CLOSED || c.statut === 'CLOSED'
|
||||
).length;
|
||||
|
||||
return normalizePage<Course>(
|
||||
{
|
||||
data: transformedData,
|
||||
meta: {
|
||||
total: pagedResult.meta?.total ?? transformedData.length,
|
||||
totalRunning,
|
||||
totalClosed,
|
||||
totalByType,
|
||||
},
|
||||
},
|
||||
params.page,
|
||||
params.perPage
|
||||
);
|
||||
})
|
||||
);
|
||||
}),
|
||||
catchError((err) => {
|
||||
console.error('Error fetching courses:', err);
|
||||
return of(
|
||||
normalizePage<Course>(
|
||||
{
|
||||
data: [],
|
||||
meta: {
|
||||
total: 0,
|
||||
totalRunning: 0,
|
||||
totalClosed: 0,
|
||||
totalByType: {},
|
||||
},
|
||||
},
|
||||
params.page,
|
||||
params.perPage
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Fetch all data and apply client-side pagination
|
||||
return this.http
|
||||
.get<CourseApiResponse[]>(this.apiUrl, { headers: this.getNgrokHeaders() })
|
||||
.pipe(
|
||||
switchMap((apiData) => {
|
||||
// Handle empty data case
|
||||
if (!apiData || apiData.length === 0) {
|
||||
return of(
|
||||
normalizePage<Course>(
|
||||
{
|
||||
data: [],
|
||||
meta: {
|
||||
total: 0,
|
||||
totalRunning: 0,
|
||||
totalClosed: 0,
|
||||
totalByType: {},
|
||||
},
|
||||
},
|
||||
params.page,
|
||||
params.perPage
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Extract unique reunionIds
|
||||
const uniqueReunionIds = [...new Set(apiData.map((c) => String(c.reunionId)))];
|
||||
|
||||
// Handle case where there are no unique IDs (shouldn't happen, but be safe)
|
||||
if (uniqueReunionIds.length === 0) {
|
||||
return of(
|
||||
normalizePage<Course>(
|
||||
{
|
||||
data: [],
|
||||
meta: {
|
||||
total: 0,
|
||||
totalRunning: 0,
|
||||
totalClosed: 0,
|
||||
totalByType: {},
|
||||
},
|
||||
},
|
||||
params.page,
|
||||
params.perPage
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
const transformedData: Course[] = 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);
|
||||
|
||||
// Apply client-side filtering, sorting, and pagination
|
||||
let filtered = this.applyClientFilters(transformedData, params);
|
||||
const total = filtered.length;
|
||||
const start = (params.page - 1) * params.perPage;
|
||||
const pageData = filtered.slice(start, start + params.perPage);
|
||||
|
||||
const totalByType = filtered.reduce<Record<string, number>>((acc, c) => {
|
||||
const type = String(c.type);
|
||||
acc[type] = (acc[type] ?? 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
const totalRunning = filtered.filter(
|
||||
(c) => c.statut === CourseStatut.RUNNING || c.statut === 'RUNNING'
|
||||
).length;
|
||||
const totalClosed = filtered.filter(
|
||||
(c) => c.statut === CourseStatut.CLOSED || c.statut === 'CLOSED'
|
||||
).length;
|
||||
|
||||
return normalizePage<Course>(
|
||||
{
|
||||
data: pageData,
|
||||
meta: {
|
||||
total,
|
||||
totalRunning,
|
||||
totalClosed,
|
||||
totalByType,
|
||||
},
|
||||
},
|
||||
params.page,
|
||||
params.perPage
|
||||
);
|
||||
})
|
||||
);
|
||||
}),
|
||||
catchError((err) => {
|
||||
console.error('Error fetching courses:', err);
|
||||
return of(
|
||||
normalizePage<Course>(
|
||||
{
|
||||
data: [],
|
||||
meta: {
|
||||
total: 0,
|
||||
totalRunning: 0,
|
||||
totalClosed: 0,
|
||||
totalByType: {},
|
||||
},
|
||||
},
|
||||
params.page,
|
||||
params.perPage
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If USE_SERVER is false, return empty result
|
||||
return of(
|
||||
normalizePage<Course>(
|
||||
{
|
||||
data: [],
|
||||
meta: {
|
||||
total: 0,
|
||||
totalRunning: 0,
|
||||
totalClosed: 0,
|
||||
totalByType: {},
|
||||
},
|
||||
},
|
||||
params.page,
|
||||
params.perPage
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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.reunionService.getById(String(apiCourse.reunionId)).pipe(
|
||||
map((reunion) => {
|
||||
if (!reunion) {
|
||||
return undefined;
|
||||
}
|
||||
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(),
|
||||
};
|
||||
})
|
||||
);
|
||||
}),
|
||||
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: Omit<Course, 'id' | 'nonPartants'>): Observable<Course> {
|
||||
if (USE_SERVER) {
|
||||
// Transform payload to API format (send reunionId instead of reunion object)
|
||||
const apiPayload: any = {
|
||||
type: payload.type,
|
||||
numero: payload.numero,
|
||||
nom: payload.nom,
|
||||
dateDepartCourse: payload.dateDepartCourse,
|
||||
dateDebutParis: payload.dateDebutParis,
|
||||
dateFinParis: payload.dateFinParis,
|
||||
reunionId: typeof payload.reunion === 'object' ? payload.reunion.id : payload.reunion,
|
||||
reunionCourse: payload.reunionCourse,
|
||||
particularite: payload.particularite,
|
||||
partants: payload.partants,
|
||||
distance: payload.distance,
|
||||
condition: payload.condition,
|
||||
statut: payload.statut,
|
||||
createdBy: payload.createdBy,
|
||||
validatedBy: payload.validatedBy,
|
||||
};
|
||||
|
||||
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.reunionService.getById(String(apiCourse.reunionId)).pipe(
|
||||
map((reunion) => {
|
||||
if (!reunion) {
|
||||
throw new Error('Reunion not found');
|
||||
}
|
||||
const item: Course = {
|
||||
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(),
|
||||
};
|
||||
return item;
|
||||
})
|
||||
);
|
||||
}),
|
||||
catchError((err) => {
|
||||
console.error('Error creating course:', err);
|
||||
throw err;
|
||||
})
|
||||
);
|
||||
}
|
||||
throw new Error('Server mode is required');
|
||||
}
|
||||
|
||||
update(id: string, payload: Partial<Course>): Observable<Course | undefined> {
|
||||
if (USE_SERVER) {
|
||||
// Transform payload to API format (send reunionId instead of reunion object)
|
||||
const apiPayload: any = { ...payload };
|
||||
if (payload.reunion) {
|
||||
apiPayload.reunionId =
|
||||
typeof payload.reunion === 'object' ? payload.reunion.id : payload.reunion;
|
||||
delete apiPayload.reunion;
|
||||
}
|
||||
|
||||
return this.http
|
||||
.put<CourseApiResponse>(`${this.apiUrl}/${id}`, apiPayload, {
|
||||
headers: this.getNgrokHeaders(),
|
||||
})
|
||||
.pipe(
|
||||
switchMap((apiCourse) => {
|
||||
// Fetch the reunion to build the full Course object
|
||||
return this.reunionService.getById(String(apiCourse.reunionId)).pipe(
|
||||
map((reunion) => {
|
||||
if (!reunion) {
|
||||
throw new Error('Reunion not found');
|
||||
}
|
||||
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(),
|
||||
};
|
||||
})
|
||||
);
|
||||
}),
|
||||
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[]) {
|
||||
console.warn('addNonPartant is deprecated. Use setNonPartants instead.');
|
||||
return this.setNonPartants(courseId, npList);
|
||||
}
|
||||
|
||||
setNonPartants(courseId: string, npList: string[]): Observable<Course | undefined> {
|
||||
if (USE_SERVER) {
|
||||
// Use PUT endpoint to replace the entire list
|
||||
return this.nonPartantService.replaceNonPartants(courseId, npList).pipe(
|
||||
switchMap((updatedNonPartants) => {
|
||||
// Fetch the updated course to return it
|
||||
return this.getById(courseId).pipe(
|
||||
map((course) => {
|
||||
if (course) {
|
||||
return {
|
||||
...course,
|
||||
nonPartants: updatedNonPartants,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
);
|
||||
}),
|
||||
catchError((err) => {
|
||||
console.error(`Error setting nonPartants for course ${courseId}:`, err);
|
||||
return of(undefined);
|
||||
})
|
||||
);
|
||||
}
|
||||
throw new Error('Server mode is required');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user