course creation

This commit is contained in:
OnlyPapy98
2025-12-19 18:00:50 +01:00
parent dde2e8aebf
commit 169b5ca412
39 changed files with 1250 additions and 2258 deletions

View File

@@ -213,7 +213,7 @@ export class AgentLimitService {
sortDir: 'asc',
} as any).pipe(
switchMap((result) => {
const limits = result.data;
const limits = result.content;
const previousDefault = limits.find((l) => l.isDefault && l.id !== newDefaultLimitId);
const operations: Observable<any>[] = [];

View File

@@ -451,7 +451,7 @@ export class AgentService {
sortDir: 'asc',
} as any).pipe(
switchMap((result) => {
const agents = result.data;
const agents = result.content;
if (agents.length === 0) {
return of(true);
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,16 +7,18 @@ 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 { ServicesUtils } from './services-utils';
const USE_SERVER = true;
const API_BASE = '/api/v1/hippodromes';
const API_BASE = '/api/hippodromes';
@Injectable({ providedIn: 'root' })
export class HippodromeService {
private apiUrl = environment.apiBaseUrl + API_BASE;
private store = signal<Hippodrome[]>([]);
constructor(private http: HttpClient, private paginatedHttp: PaginatedHttpService) {}
constructor(private http: HttpClient, private paginatedHttp: PaginatedHttpService, private servicesUtils:ServicesUtils) {}
// Helper method to get ngrok bypass headers
private getNgrokHeaders(): Record<string, string> {
@@ -30,326 +32,13 @@ export class HippodromeService {
// LISTE — supporte client & serveur
list(
params: ListParams,
usePaginationEndpoint: boolean = false
usePaginationEndpoint: boolean = true
): Observable<PagedResult<Hippodrome>> {
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(
switchMap((hippodromes) => {
// Fetch all reunions and courses to calculate counts
return forkJoin({
reunions: this.http
.get<any[]>(`${environment.apiBaseUrl}/api/v1/reunions`, {
headers: this.getNgrokHeaders(),
})
.pipe(
catchError(() => of([])),
map((data) => ({ data: data || [], meta: { total: (data || []).length } }))
),
courses: this.http
.get<any[]>(`${environment.apiBaseUrl}/api/v1/courses`, {
headers: this.getNgrokHeaders(),
})
.pipe(
catchError(() => of([])),
map((data) => ({ data: data || [], meta: { total: (data || []).length } }))
),
}).pipe(
map(({ reunions, courses }) => {
// Count reunions per hippodrome
const reunionCountMap = new Map<string, number>();
reunions.data.forEach((reunion: any) => {
const hippodromeId = String(reunion.hippodromeId || reunion.hippodrome?.id);
if (hippodromeId && hippodromeId !== 'undefined' && hippodromeId !== 'null') {
reunionCountMap.set(hippodromeId, (reunionCountMap.get(hippodromeId) || 0) + 1);
}
});
// Create a map of reunionId -> hippodromeId from reunions
const reunionToHippodromeMap = new Map<string, string>();
reunions.data.forEach((reunion: any) => {
const reunionId = String(reunion.id);
const hippodromeId = String(reunion.hippodromeId || reunion.hippodrome?.id);
if (
reunionId &&
reunionId !== 'undefined' &&
reunionId !== 'null' &&
hippodromeId &&
hippodromeId !== 'undefined' &&
hippodromeId !== 'null'
) {
reunionToHippodromeMap.set(reunionId, hippodromeId);
}
});
// Count courses per hippodrome using the reunion -> hippodrome mapping
const courseCountMap = new Map<string, number>();
courses.data.forEach((course: any) => {
const reunionId = String(course.reunionId || course.reunion?.id);
if (reunionId && reunionId !== 'undefined' && reunionId !== 'null') {
const hippodromeId = reunionToHippodromeMap.get(reunionId);
if (hippodromeId) {
courseCountMap.set(hippodromeId, (courseCountMap.get(hippodromeId) || 0) + 1);
}
}
});
// Add counts to hippodromes
const hippodromesWithCounts = hippodromes.map((h) => ({
...h,
reunionCount: reunionCountMap.get(String(h.id)) ?? 0,
courseCount: courseCountMap.get(String(h.id)) ?? 0,
}));
// Apply client-side sorting and pagination
let filtered = this.applyClientFilters(hippodromesWithCounts, {
...params,
search: '', // Already filtered by search endpoint
});
const total = filtered.length;
const start = (params.page - 1) * params.perPage;
const pageData = filtered.slice(start, start + params.perPage);
const uniqueCountries = new Set(filtered.map((h) => h.pays)).size;
const uniqueCities = new Set(filtered.map((h) => h.ville)).size;
const averageByCountry = filtered.length
? Math.round(filtered.length / uniqueCountries)
: 0;
const totalReunions = filtered.reduce((acc, h) => acc + (h.reunionCount ?? 0), 0);
const totalCourses = filtered.reduce((acc, h) => acc + (h.courseCount ?? 0), 0);
return normalizePage<Hippodrome>(
{
data: pageData,
meta: {
total,
uniqueCountries,
uniqueCities,
averageByCountry,
totalReunions,
totalCourses,
},
},
params.page,
params.perPage
);
})
);
}),
catchError((err) => {
console.error('Error searching hippodromes:', err);
return of(
normalizePage<Hippodrome>(
{
data: [],
meta: {
total: 0,
uniqueCountries: 0,
uniqueCities: 0,
averageByCountry: 0,
totalReunions: 0,
totalCourses: 0,
},
},
params.page,
params.perPage
)
);
})
);
}
if (usePaginationEndpoint) {
return this.paginatedHttp
.fetch<Hippodrome>(this.apiUrl, params, {
zeroBasedPageIndex: false,
buildSort: (key, dir) => (key && dir ? ['sort', `${key},${dir}`] : null),
mapClientSortKey: (k) => {
const alias: Record<string, string> = {
name: 'nom',
city: 'ville',
country: 'pays',
};
return k ? alias[k] ?? k : undefined;
},
})
.pipe(
catchError((err) => {
console.error('Error fetching hippodromes:', err);
return of(
normalizePage<Hippodrome>(
{
data: [],
meta: {
total: 0,
uniqueCountries: 0,
uniqueCities: 0,
averageByCountry: 0,
totalReunions: 0,
totalCourses: 0,
},
},
params.page,
params.perPage
)
);
})
);
} else {
// Fetch all data and apply client-side pagination
return this.http
.get<Hippodrome[]>(`${this.apiUrl}/actifs`, {
headers: this.getNgrokHeaders(),
})
.pipe(
switchMap((allData) => {
// Fetch all reunions and courses directly from API to calculate counts
// We fetch directly to avoid circular dependency with ReunionService and CourseService
return forkJoin({
reunions: this.http
.get<any[]>(`${environment.apiBaseUrl}/api/v1/reunions`, {
headers: this.getNgrokHeaders(),
})
.pipe(
catchError(() => of([])),
map((data) => ({ data, meta: { total: data.length } }))
),
courses: this.http
.get<any[]>(`${environment.apiBaseUrl}/api/v1/courses`, {
headers: this.getNgrokHeaders(),
})
.pipe(
catchError(() => of([])),
map((data) => ({ data, meta: { total: data.length } }))
),
}).pipe(
map(({ reunions, courses }) => {
// Count reunions per hippodrome
const reunionCountMap = new Map<string, number>();
reunions.data.forEach((reunion: any) => {
const hippodromeId = String(reunion.hippodromeId || reunion.hippodrome?.id);
if (hippodromeId && hippodromeId !== 'undefined' && hippodromeId !== 'null') {
reunionCountMap.set(
hippodromeId,
(reunionCountMap.get(hippodromeId) || 0) + 1
);
}
});
// Create a map of reunionId -> hippodromeId from reunions
const reunionToHippodromeMap = new Map<string, string>();
reunions.data.forEach((reunion: any) => {
const reunionId = String(reunion.id);
const hippodromeId = String(reunion.hippodromeId || reunion.hippodrome?.id);
if (
reunionId &&
reunionId !== 'undefined' &&
reunionId !== 'null' &&
hippodromeId &&
hippodromeId !== 'undefined' &&
hippodromeId !== 'null'
) {
reunionToHippodromeMap.set(reunionId, hippodromeId);
}
});
// Count courses per hippodrome using the reunion -> hippodrome mapping
const courseCountMap = new Map<string, number>();
courses.data.forEach((course: any) => {
const reunionId = String(course.reunionId || course.reunion?.id);
if (reunionId && reunionId !== 'undefined' && reunionId !== 'null') {
const hippodromeId = reunionToHippodromeMap.get(reunionId);
if (hippodromeId) {
courseCountMap.set(
hippodromeId,
(courseCountMap.get(hippodromeId) || 0) + 1
);
}
}
});
// Add counts to hippodromes
const hippodromesWithCounts = allData.map((h) => ({
...h,
reunionCount: reunionCountMap.get(String(h.id)) ?? 0,
courseCount: courseCountMap.get(String(h.id)) ?? 0,
}));
// Apply client-side filtering, sorting, and pagination
let filtered = this.applyClientFilters(hippodromesWithCounts, params);
const total = filtered.length;
const start = (params.page - 1) * params.perPage;
const pageData = filtered.slice(start, start + params.perPage);
const uniqueCountries = new Set(filtered.map((h) => h.pays)).size;
const uniqueCities = new Set(filtered.map((h) => h.ville)).size;
const averageByCountry = filtered.length
? Math.round(filtered.length / uniqueCountries)
: 0;
const totalReunions = filtered.reduce((acc, h) => acc + (h.reunionCount ?? 0), 0);
const totalCourses = filtered.reduce((acc, h) => acc + (h.courseCount ?? 0), 0);
return normalizePage<Hippodrome>(
{
data: pageData,
meta: {
total,
uniqueCountries,
uniqueCities,
averageByCountry,
totalReunions,
totalCourses,
},
},
params.page,
params.perPage
);
})
);
}),
catchError((err) => {
console.error('Error fetching hippodromes:', err);
return of(
normalizePage<Hippodrome>(
{
data: [],
meta: {
total: 0,
uniqueCountries: 0,
uniqueCities: 0,
averageByCountry: 0,
totalReunions: 0,
totalCourses: 0,
},
},
params.page,
params.perPage
)
);
})
);
}
}
// Mock mode disabled - return empty result
return of(
normalizePage<Hippodrome>(
{
data: [],
meta: {
total: 0,
uniqueCountries: 0,
uniqueCities: 0,
averageByCountry: 0,
totalReunions: 0,
totalCourses: 0,
},
},
params.page,
params.perPage
)
);
const hippodromeList = this.http.get<PagedResult<Hippodrome>>(this.apiUrl, {
headers: this.getNgrokHeaders(),
params: this.servicesUtils.getParamsFromModel(params)
})
return hippodromeList;
}
private applyClientFilters(data: Hippodrome[], params: ListParams): Hippodrome[] {

View File

@@ -1,76 +1,76 @@
import { Injectable, signal } from '@angular/core';
import { Observable, of } from 'rxjs';
import { CourseReportDetail, CourseReportDetailRow, CourseReportSummary } from '../interfaces/report';
import { REPORT_SUMMARIES_MOCK, REPORT_DETAILS_MOCK } from '../mocks/report.mocks';
import { normalizePage } from '@shared/paging/normalize-page';
import { ListParams, PagedResult, SortDir } from '@shared/paging/paging';
// import { Injectable, signal } from '@angular/core';
// import { Observable, of } from 'rxjs';
// import { CourseReportDetail, CourseReportDetailRow, CourseReportSummary } from '../interfaces/report';
// import { REPORT_SUMMARIES_MOCK, REPORT_DETAILS_MOCK } from '../mocks/report.mocks';
// import { normalizePage } from '@shared/paging/normalize-page';
// import { ListParams, PagedResult, SortDir } from '@shared/paging/paging';
@Injectable({ providedIn: 'root' })
export class ReportService {
private summaries = signal<CourseReportSummary[]>([...REPORT_SUMMARIES_MOCK]);
// @Injectable({ providedIn: 'root' })
// export class ReportService {
// private summaries = signal<CourseReportSummary[]>([...REPORT_SUMMARIES_MOCK]);
list(params: ListParams): Observable<PagedResult<CourseReportSummary>> {
let data = [...this.summaries()];
const q = (params.search ?? '').toLowerCase();
if (q) {
data = data.filter((r) =>
[
r.course.nom,
r.course.type,
r.course.reunion?.hippodrome?.nom,
String(r.course.numero),
]
.filter(Boolean)
.map((s) => String(s).toLowerCase())
.some((s) => s.includes(q))
);
}
if (params.sortKey && params.sortDir) {
const { sortKey, sortDir } = params as { sortKey: string; sortDir: SortDir };
const get = (o: any, k: string) => k.split('.').reduce((a, b) => a?.[b], o);
data = [...data].sort((a, b) => String(get(a, sortKey) ?? '').localeCompare(String(get(b, sortKey) ?? ''), 'fr', { numeric: true }) * (sortDir === 'asc' ? 1 : -1));
}
const start = (params.page - 1) * params.perPage;
const pageData = data.slice(start, start + params.perPage);
return of(normalizePage<CourseReportSummary>({ data: pageData, meta: { total: data.length } }, params.page, params.perPage));
}
// list(params: ListParams): Observable<PagedResult<CourseReportSummary>> {
// let data = [...this.summaries()];
// const q = (params.search ?? '').toLowerCase();
// if (q) {
// data = data.filter((r) =>
// [
// r.course.nom,
// r.course.type,
// r.course.reunion?.hippodrome?.nom,
// String(r.course.numero),
// ]
// .filter(Boolean)
// .map((s) => String(s).toLowerCase())
// .some((s) => s.includes(q))
// );
// }
// if (params.sortKey && params.sortDir) {
// const { sortKey, sortDir } = params as { sortKey: string; sortDir: SortDir };
// const get = (o: any, k: string) => k.split('.').reduce((a, b) => a?.[b], o);
// data = [...data].sort((a, b) => String(get(a, sortKey) ?? '').localeCompare(String(get(b, sortKey) ?? ''), 'fr', { numeric: true }) * (sortDir === 'asc' ? 1 : -1));
// }
// const start = (params.page - 1) * params.perPage;
// const pageData = data.slice(start, start + params.perPage);
// return of(normalizePage<CourseReportSummary>({ data: pageData, meta: { total: data.length } }, params.page, params.perPage));
// }
getDetail(courseId: string): Observable<CourseReportDetail | undefined> {
const summary = this.summaries().find((s) => s.id === courseId);
if (!summary) return of(undefined);
const rows = REPORT_DETAILS_MOCK.get(courseId) ?? [];
return of({ summary, rows });
}
// getDetail(courseId: string): Observable<CourseReportDetail | undefined> {
// const summary = this.summaries().find((s) => s.id === courseId);
// if (!summary) return of(undefined);
// const rows = REPORT_DETAILS_MOCK.get(courseId) ?? [];
// return of({ summary, rows });
// }
// === Actions ===
validate(courseId: string): Observable<CourseReportSummary | undefined> {
let updated: CourseReportSummary | undefined;
this.summaries.set(
this.summaries().map((s) => (s.id === courseId ? ((updated = { ...s, statut: 'Validé', confirmed: false }), updated) : s))
);
return of(updated);
}
// // === Actions ===
// validate(courseId: string): Observable<CourseReportSummary | undefined> {
// let updated: CourseReportSummary | undefined;
// this.summaries.set(
// this.summaries().map((s) => (s.id === courseId ? ((updated = { ...s, statut: 'Validé', confirmed: false }), updated) : s))
// );
// return of(updated);
// }
confirm(courseId: string): Observable<CourseReportSummary | undefined> {
let updated: CourseReportSummary | undefined;
this.summaries.set(
this.summaries().map((s) => (s.id === courseId ? ((updated = { ...s, statut: 'Validé', confirmed: true }), updated) : s))
);
return of(updated);
}
// confirm(courseId: string): Observable<CourseReportSummary | undefined> {
// let updated: CourseReportSummary | undefined;
// this.summaries.set(
// this.summaries().map((s) => (s.id === courseId ? ((updated = { ...s, statut: 'Validé', confirmed: true }), updated) : s))
// );
// return of(updated);
// }
resetStatus(courseId: string): Observable<CourseReportSummary | undefined> {
let updated: CourseReportSummary | undefined;
this.summaries.set(
this.summaries().map((s) => (s.id === courseId ? ((updated = { ...s, statut: 'Non Validé', confirmed: false }), updated) : s))
);
return of(updated);
}
// resetStatus(courseId: string): Observable<CourseReportSummary | undefined> {
// let updated: CourseReportSummary | undefined;
// this.summaries.set(
// this.summaries().map((s) => (s.id === courseId ? ((updated = { ...s, statut: 'Non Validé', confirmed: false }), updated) : s))
// );
// return of(updated);
// }
modifyRows(courseId: string, rows: CourseReportDetailRow[]): Observable<boolean> {
REPORT_DETAILS_MOCK.set(courseId, rows);
return of(true);
}
}
// modifyRows(courseId: string, rows: CourseReportDetailRow[]): Observable<boolean> {
// REPORT_DETAILS_MOCK.set(courseId, rows);
// return of(true);
// }
// }

View File

@@ -25,7 +25,7 @@ interface ReunionApiResponse {
}
const USE_SERVER = true;
const API_BASE = '/api/v1/reunions';
const API_BASE = '/api/reunions';
@Injectable({ providedIn: 'root' })
export class ReunionService {
@@ -49,7 +49,7 @@ export class ReunionService {
list(
params: ListParams,
usePaginationEndpoint: boolean = false
usePaginationEndpoint: boolean = true
): Observable<PagedResult<Reunion>> {
if (USE_SERVER) {
if (usePaginationEndpoint) {
@@ -61,30 +61,28 @@ export class ReunionService {
.pipe(
switchMap((pagedResult) => {
// Handle empty data case
if (!pagedResult.data || pagedResult.data.length === 0) {
if (!pagedResult.content || pagedResult.content.length === 0) {
return of({
...pagedResult,
data: [],
meta: {
...pagedResult.meta,
uniqueHippodromes: 0,
content: [],
pageable: {
...pagedResult.pageable,
},
});
}
});
}
// Extract unique hippodrome IDs from the paginated data
const uniqueHippodromeIds = [
...new Set(pagedResult.data.map((r) => String(r.hippodromeId))),
...new Set(pagedResult.content.map((r) => String(r.hippodromeId))),
];
// Handle case where there are no unique IDs
if (uniqueHippodromeIds.length === 0) {
return of({
...pagedResult,
data: [],
meta: {
...pagedResult.meta,
uniqueHippodromes: 0,
content: [],
pageable: {
...pagedResult.pageable,
},
});
}
@@ -93,12 +91,12 @@ export class ReunionService {
const hippodromeRequests = uniqueHippodromeIds.map((id) =>
this.hippodromeService
.getById(id)
.pipe(catchError(() => of<Hippodrome | undefined>(undefined)))
.pipe(catchError(() => of<Hippodrome>()))
);
// Fetch courses to calculate counts per reunion
const coursesRequest = this.http
.get<any[]>(`${environment.apiBaseUrl}/api/v1/courses`, {
.get<any[]>(`${environment.apiBaseUrl}/api/courses`, {
headers: this.getNgrokHeaders(),
})
.pipe(
@@ -133,7 +131,7 @@ export class ReunionService {
});
// Transform API responses to Reunion objects
const transformedData: Reunion[] = pagedResult.data
const transformedData: Reunion[] = pagedResult.content
.map((apiReunion) => {
const hippodrome = hippodromeMap.get(String(apiReunion.hippodromeId));
if (!hippodrome) {
@@ -155,19 +153,10 @@ export class ReunionService {
} as Reunion;
})
.filter((r): r is Reunion => r !== null && r !== undefined);
// Calculate unique hippodromes count
const uniqueHippodromes = new Set(transformedData.map((r) => r.hippodrome.id))
.size;
return {
...pagedResult,
data: transformedData,
meta: {
...pagedResult.meta,
uniqueHippodromes,
},
};
return {
...pagedResult,
content: transformedData,
}
})
);
}),

View File

@@ -0,0 +1,16 @@
import { HttpParams } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { ListParams } from "@shared/paging/paging";
@Injectable({providedIn: 'root'})
export class ServicesUtils{
getParamsFromModel(params:ListParams):HttpParams{
let httpParams = new HttpParams();
Object.entries(params).forEach(([key, value])=>{
if(params != null && params!=undefined){
httpParams.set(key, String(value))
}
})
return httpParams;
}
}