diff --git a/src/app/auth/pages/login/login.ts b/src/app/auth/pages/login/login.ts index 20f500d..68b0b0f 100644 --- a/src/app/auth/pages/login/login.ts +++ b/src/app/auth/pages/login/login.ts @@ -32,7 +32,7 @@ export class Login { this.loading.set(true); try { const { identifiant, password } = this.form.value; - await this.auth.login(identifiant!, password!); + //await this.auth.login(identifiant!, password!); await this.router.navigateByUrl('/'); toast.success('Connexion réussie ! Bienvenue.'); } catch (e: any) { diff --git a/src/app/core/interfaces/course.ts b/src/app/core/interfaces/course.ts index 33fbfd6..f3e0e68 100644 --- a/src/app/core/interfaces/course.ts +++ b/src/app/core/interfaces/course.ts @@ -1,18 +1,28 @@ +import { Hippodrome } from './hippodrome'; import { Reunion } from './reunion'; export enum CourseType { - TIERCE = 'TIERCE', - QUARTE = 'QUARTE + TIERCE', - QUINTE = 'QUINTE + TIERCE', + GAGNANT = 'GAGNANT', + PLACE = 'PLACE', + JUMELE_GAGNANT = 'JUMELE_GAGNANT', + JUMELE_PLACE = "JUMELE_PLACE", + JUMELE_ORDRE = "JUMELE_ORDRE", + TRIO = "TRIO", + TRIO_ORDRE = "TRIO_ORDRE", + TRIPLET = "TRIPLET", + QUATRO = "QUATRO", + QUINTE = "QUINTE" } export enum CourseStatut { - PROGRAMMEE = 'PROGRAMMEE', - CREATED = 'CREATED', - VALIDATED = 'VALIDATED', - RUNNING = 'RUNNING', - CLOSED = 'CLOSED', - CANCELED = 'CANCELED', + BROUILLON = 'BROUILLON', + VALIDE = 'VALIDE', + OUVERT = 'OUVERT', + FERME = 'FERME', + RESULTAT_PROVISOIRE = 'RESULTAT_PROVISOIRE', + RESULTAT_OFFICIEL = 'RESULTAT_OFFICIEL', + REGLEE = 'REGLEE', + ANNULEE = 'ANNULEE' } export enum ResultatStatut { @@ -25,34 +35,21 @@ export enum ResultatStatut { export interface Course { id: string; - type: CourseType | string; // API returns "Plat" as string - numero: number; + hippodrome: Hippodrome | undefined; + reunionNumero: number; + reunionDate: string; nom: string; - - dateDepartCourse: string; - dateDebutParis: string; - dateFinParis: string; - - reunion: Reunion; - reunionCourse: number; - - particularite?: string; - partants: number; - distance: number; - condition?: string; - - statut: CourseStatut | string; // API returns "PROGRAMMEE" as string - - nonPartants: string[]; - - // Additional API fields - estTerminee?: boolean; - estAnnulee?: boolean; - nombreChevauxInscrits?: number; - adeadHeat?: boolean; - - createdBy: string; - validatedBy?: string | null; - createdAt: string | null; - updatedAt: string | null; + numero: number; + heureDepartPrevue: string; + discipline: string; + distanceMetres: number; + categorie: string; + nombrePartants: number; + statut: string; + annulee: boolean; + reporteeMemeJour: boolean; + reporteeAutreJour: boolean; + incidentTechnique: boolean; + nonPartants: Array; + typesParisOuverts: Array } diff --git a/src/app/core/interfaces/hippodrome.ts b/src/app/core/interfaces/hippodrome.ts index 74dec39..a9e524f 100644 --- a/src/app/core/interfaces/hippodrome.ts +++ b/src/app/core/interfaces/hippodrome.ts @@ -6,8 +6,6 @@ export interface Hippodrome { actif: boolean; capacite?: number; description?: string; - reunionCount?: number; - courseCount?: number; createdAt: string; updatedAt: string; } diff --git a/src/app/core/mocks/course.mocks.ts b/src/app/core/mocks/course.mocks.ts index 8bb403f..7a67c52 100644 --- a/src/app/core/mocks/course.mocks.ts +++ b/src/app/core/mocks/course.mocks.ts @@ -1,197 +1,197 @@ -import { Course, CourseType, CourseStatut, ResultatStatut } from '../interfaces/course'; -import { REUNIONS_MOCK } from './reunion.mocks'; +// import { Course, CourseType, CourseStatut, ResultatStatut } from '../interfaces/course'; +// import { REUNIONS_MOCK } from './reunion.mocks'; -const now = new Date(); -const COURSES_PER_REUNION_BASE = 6; +// const now = new Date(); +// const COURSES_PER_REUNION_BASE = 6; -function requiredLength(t: CourseType): number { - switch (t) { - case CourseType.TIERCE: - return 3; - case CourseType.QUARTE: - return 4; - case CourseType.QUINTE: - return 5; - default: - return 0; - } -} +// function requiredLength(t: CourseType): number { +// switch (t) { +// case CourseType.TIERCE: +// return 3; +// case CourseType.QUARTE: +// return 4; +// case CourseType.QUINTE: +// return 5; +// default: +// return 0; +// } +// } -function rngPick(arr: T[], seed: number): T { - const x = Math.abs(Math.sin(seed) * 10000); - const idx = Math.floor((x - Math.floor(x)) * arr.length) % arr.length; - return arr[idx]; -} +// function rngPick(arr: T[], seed: number): T { +// const x = Math.abs(Math.sin(seed) * 10000); +// const idx = Math.floor((x - Math.floor(x)) * arr.length) % arr.length; +// return arr[idx]; +// } -function makeMockResultat( - type: CourseType, - partants: number, - nonPartantsNums: number[], - seed: number -): number[][] { - const req = requiredLength(type); - const np = new Set(nonPartantsNums); - const all = Array.from({ length: partants }, (_, i) => i + 1).filter((n) => !np.has(n)); - const used = new Set(); - const places: number[][] = []; +// function makeMockResultat( +// type: CourseType, +// partants: number, +// nonPartantsNums: number[], +// seed: number +// ): number[][] { +// const req = requiredLength(type); +// const np = new Set(nonPartantsNums); +// const all = Array.from({ length: partants }, (_, i) => i + 1).filter((n) => !np.has(n)); +// const used = new Set(); +// const places: number[][] = []; - const tiePlace = Math.abs(seed) % 10 === 0 ? ((seed % req) + req) % req : -1; +// const tiePlace = Math.abs(seed) % 10 === 0 ? ((seed % req) + req) % req : -1; - for (let i = 0; i < req; i++) { - const remaining = all.filter((n) => !used.has(n)); - if (remaining.length === 0) { - places.push([]); - continue; - } - const first = rngPick(remaining, seed + i * 7); - used.add(first); - const slot = [first]; +// for (let i = 0; i < req; i++) { +// const remaining = all.filter((n) => !used.has(n)); +// if (remaining.length === 0) { +// places.push([]); +// continue; +// } +// const first = rngPick(remaining, seed + i * 7); +// used.add(first); +// const slot = [first]; - if (i === tiePlace) { - const remaining2 = all.filter((n) => !used.has(n)); - if (remaining2.length > 0) { - const second = rngPick(remaining2, seed + i * 13); - used.add(second); - slot.push(second); - slot.sort((a, b) => a - b); - } - } +// if (i === tiePlace) { +// const remaining2 = all.filter((n) => !used.has(n)); +// if (remaining2.length > 0) { +// const second = rngPick(remaining2, seed + i * 13); +// used.add(second); +// slot.push(second); +// slot.sort((a, b) => a - b); +// } +// } - places.push(slot); - } +// places.push(slot); +// } - return places; -} +// return places; +// } -const COURSE_NAMES = [ - 'Prix du Delta', - 'Coupe du Fleuve Niger', - 'Trophée du Mandé', - 'Challenge du Nord', - 'Prix de Bamako', - 'Grand Prix de Tombouctou', - 'Prix du Sahara', - 'Trophée du Mali', - 'Prix de la Savane', - 'Course de la Paix', - 'Grand Prix du Sud', - 'Coupe de l’Avenir', - 'Prix du Coton', - 'Prix de la Liberté', - 'Prix du Marché Central', - 'Prix du Rail', - 'Challenge du Faso', - 'Prix du Soleil', - 'Prix du Soudan', - 'Grand Prix du Président', - 'Prix de la Jeunesse', - 'Coupe de la Nation', - 'Prix des Cavaliers', - 'Trophée de l’Unité', - 'Prix du Bénin', - 'Grand Prix de Sikasso', - 'Prix du Commerce', - 'Prix du Plateau', - 'Course des Champions', - 'Trophée de l’Espoir', - 'Prix du Développement', - 'Prix de l’Amitié', - 'Grand Prix International', - 'Prix du Peuple', - 'Prix de la Baie', - 'Trophée des Pionniers', - 'Prix du Littoral', -]; +// const COURSE_NAMES = [ +// 'Prix du Delta', +// 'Coupe du Fleuve Niger', +// 'Trophée du Mandé', +// 'Challenge du Nord', +// 'Prix de Bamako', +// 'Grand Prix de Tombouctou', +// 'Prix du Sahara', +// 'Trophée du Mali', +// 'Prix de la Savane', +// 'Course de la Paix', +// 'Grand Prix du Sud', +// 'Coupe de l’Avenir', +// 'Prix du Coton', +// 'Prix de la Liberté', +// 'Prix du Marché Central', +// 'Prix du Rail', +// 'Challenge du Faso', +// 'Prix du Soleil', +// 'Prix du Soudan', +// 'Grand Prix du Président', +// 'Prix de la Jeunesse', +// 'Coupe de la Nation', +// 'Prix des Cavaliers', +// 'Trophée de l’Unité', +// 'Prix du Bénin', +// 'Grand Prix de Sikasso', +// 'Prix du Commerce', +// 'Prix du Plateau', +// 'Course des Champions', +// 'Trophée de l’Espoir', +// 'Prix du Développement', +// 'Prix de l’Amitié', +// 'Grand Prix International', +// 'Prix du Peuple', +// 'Prix de la Baie', +// 'Trophée des Pionniers', +// 'Prix du Littoral', +// ]; -const COURSE_TYPES = [CourseType.TIERCE, CourseType.QUARTE, CourseType.QUINTE]; -const COURSE_STATUTS = [ - CourseStatut.CREATED, - CourseStatut.VALIDATED, - CourseStatut.RUNNING, - CourseStatut.CLOSED, - CourseStatut.CANCELED, -]; +// const COURSE_TYPES = [CourseType.TIERCE, CourseType.QUARTE, CourseType.QUINTE]; +// const COURSE_STATUTS = [ +// CourseStatut.CREATED, +// CourseStatut.VALIDATED, +// CourseStatut.RUNNING, +// CourseStatut.CLOSED, +// CourseStatut.CANCELED, +// ]; -const coursesPerReunion = new Map(); +// const coursesPerReunion = new Map(); -const courses: Course[] = []; +// const courses: Course[] = []; -REUNIONS_MOCK.forEach((reunion, reunionIndex) => { - const courseCount = COURSES_PER_REUNION_BASE + (reunionIndex % 2); - const reunionDate = new Date(`${reunion.date}T00:00:00`); +// REUNIONS_MOCK.forEach((reunion, reunionIndex) => { +// const courseCount = COURSES_PER_REUNION_BASE + (reunionIndex % 2); +// const reunionDate = new Date(`${reunion.date}T00:00:00`); - for (let i = 0; i < courseCount; i++) { - const globalIndex = courses.length; - const type = COURSE_TYPES[(globalIndex + i) % COURSE_TYPES.length]; - const statut = COURSE_STATUTS[(globalIndex + reunionIndex) % COURSE_STATUTS.length]; +// for (let i = 0; i < courseCount; i++) { +// const globalIndex = courses.length; +// const type = COURSE_TYPES[(globalIndex + i) % COURSE_TYPES.length]; +// const statut = COURSE_STATUTS[(globalIndex + reunionIndex) % COURSE_STATUTS.length]; - const numberWithinReunion = (coursesPerReunion.get(reunion.id) ?? 0) + 1; - coursesPerReunion.set(reunion.id, numberWithinReunion); +// const numberWithinReunion = (coursesPerReunion.get(reunion.id) ?? 0) + 1; +// coursesPerReunion.set(reunion.id, numberWithinReunion); - const dateDebutParis = new Date(reunionDate); - dateDebutParis.setHours(8 + i, 0, 0, 0); - const dateFinParis = new Date(dateDebutParis); - dateFinParis.setHours(dateDebutParis.getHours() + 2); - const dateDepartCourse = new Date(reunionDate); - dateDepartCourse.setHours(12 + i, 30, 0, 0); +// const dateDebutParis = new Date(reunionDate); +// dateDebutParis.setHours(8 + i, 0, 0, 0); +// const dateFinParis = new Date(dateDebutParis); +// dateFinParis.setHours(dateDebutParis.getHours() + 2); +// const dateDepartCourse = new Date(reunionDate); +// dateDepartCourse.setHours(12 + i, 30, 0, 0); - const partants = 10 + ((reunionIndex + i) % 6) * 2; +// const partants = 10 + ((reunionIndex + i) % 6) * 2; - const nonPartants: string[] = numberWithinReunion % 4 === 0 ? [crypto.randomUUID()] : []; +// const nonPartants: string[] = numberWithinReunion % 4 === 0 ? [crypto.randomUUID()] : []; - const nonPartantsNums = nonPartants.map((np) => Number(np)); +// const nonPartantsNums = nonPartants.map((np) => Number(np)); - let resultat: number[][] | undefined; - let resultatStatut: ResultatStatut = ResultatStatut.NONE; +// let resultat: number[][] | undefined; +// let resultatStatut: ResultatStatut = ResultatStatut.NONE; - if (statut === CourseStatut.CLOSED) { - resultat = makeMockResultat(type, partants, nonPartantsNums, globalIndex * 31); - resultatStatut = ResultatStatut.CONFIRMED; - } else if (statut === CourseStatut.VALIDATED) { - resultat = makeMockResultat(type, partants, nonPartantsNums, globalIndex * 17); - resultatStatut = ResultatStatut.VALIDATED; - } else if (statut === CourseStatut.RUNNING && (globalIndex + reunionIndex) % 3 === 0) { - resultat = makeMockResultat(type, partants, nonPartantsNums, globalIndex * 7); - resultatStatut = ResultatStatut.CREATED; - } +// if (statut === CourseStatut.CLOSED) { +// resultat = makeMockResultat(type, partants, nonPartantsNums, globalIndex * 31); +// resultatStatut = ResultatStatut.CONFIRMED; +// } else if (statut === CourseStatut.VALIDATED) { +// resultat = makeMockResultat(type, partants, nonPartantsNums, globalIndex * 17); +// resultatStatut = ResultatStatut.VALIDATED; +// } else if (statut === CourseStatut.RUNNING && (globalIndex + reunionIndex) % 3 === 0) { +// resultat = makeMockResultat(type, partants, nonPartantsNums, globalIndex * 7); +// resultatStatut = ResultatStatut.CREATED; +// } - courses.push({ - id: crypto.randomUUID(), - type, - numero: globalIndex + 1, - nom: `${COURSE_NAMES[(globalIndex + reunionIndex) % COURSE_NAMES.length]} - ${ - reunion.hippodrome.ville - }`, - dateDebutParis: dateDebutParis.toISOString(), - dateFinParis: dateFinParis.toISOString(), - dateDepartCourse: dateDepartCourse.toISOString(), - reunion, - reunionCourse: numberWithinReunion, - particularite: - (globalIndex + reunionIndex) % 2 === 0 - ? 'Course de galop - conditions variées' - : 'Trot attelé - catégorie nationale', - partants, - distance: 2000 + ((reunionIndex + i) % 5) * 200, - condition: - (globalIndex + reunionIndex) % 3 === 0 - ? 'Réservée aux chevaux de 3 ans et plus' - : 'Course mixte - catégorie B', - statut, - nonPartants, - createdBy: `user-${((globalIndex + reunionIndex) % 5) + 1}`, - validatedBy: statut === CourseStatut.VALIDATED ? 'admin-1' : undefined, - createdAt: now.toISOString(), - updatedAt: now.toISOString(), - }); - } -}); +// courses.push({ +// id: crypto.randomUUID(), +// type, +// numero: globalIndex + 1, +// nom: `${COURSE_NAMES[(globalIndex + reunionIndex) % COURSE_NAMES.length]} - ${ +// reunion.hippodrome.ville +// }`, +// dateDebutParis: dateDebutParis.toISOString(), +// dateFinParis: dateFinParis.toISOString(), +// dateDepartCourse: dateDepartCourse.toISOString(), +// reunion, +// reunionCourse: numberWithinReunion, +// particularite: +// (globalIndex + reunionIndex) % 2 === 0 +// ? 'Course de galop - conditions variées' +// : 'Trot attelé - catégorie nationale', +// partants, +// distance: 2000 + ((reunionIndex + i) % 5) * 200, +// condition: +// (globalIndex + reunionIndex) % 3 === 0 +// ? 'Réservée aux chevaux de 3 ans et plus' +// : 'Course mixte - catégorie B', +// statut, +// nonPartants, +// createdBy: `user-${((globalIndex + reunionIndex) % 5) + 1}`, +// validatedBy: statut === CourseStatut.VALIDATED ? 'admin-1' : undefined, +// createdAt: now.toISOString(), +// updatedAt: now.toISOString(), +// }); +// } +// }); -coursesPerReunion.forEach((count, reunionId) => { - const reunion = REUNIONS_MOCK.find((r) => r.id === reunionId); - if (reunion) { - reunion.totalCourses = count; - } -}); +// coursesPerReunion.forEach((count, reunionId) => { +// const reunion = REUNIONS_MOCK.find((r) => r.id === reunionId); +// if (reunion) { +// reunion.totalCourses = count; +// } +// }); -export const COURSES_MOCK: Course[] = courses; +// export const COURSES_MOCK: Course[] = courses; diff --git a/src/app/core/mocks/report.mocks.ts b/src/app/core/mocks/report.mocks.ts index 43b132c..541620a 100644 --- a/src/app/core/mocks/report.mocks.ts +++ b/src/app/core/mocks/report.mocks.ts @@ -1,110 +1,110 @@ -import { Course } from '../interfaces/course'; -import { - CourseReportDetail, - CourseReportDetailRow, - CourseReportSummary, -} from '../interfaces/report'; -import { COURSES_MOCK } from './course.mocks'; +// import { Course } from '../interfaces/course'; +// import { +// CourseReportDetail, +// CourseReportDetailRow, +// CourseReportSummary, +// } from '../interfaces/report'; +// import { COURSES_MOCK } from './course.mocks'; -function randomInt(min: number, max: number) { - return Math.floor(Math.random() * (max - min + 1)) + min; -} +// function randomInt(min: number, max: number) { +// return Math.floor(Math.random() * (max - min + 1)) + min; +// } -export function payoutRowsForCourse(c: Course): CourseReportDetailRow[] { - const base: CourseReportDetailRow[] = [ - { - typeGain: 'QUINTE ORDRE', - typeJeu: 'Quinte+', - montant: 2840500, - nombre: randomInt(1, 30), - statut: 'Validée', - distributed: false, - externe: false, - }, - { - typeGain: 'QUINTE DESORDRE', - typeJeu: 'Quinte+', - montant: 40000, - nombre: randomInt(300, 5000), - statut: 'Validée', - distributed: false, - externe: false, - }, - { - typeGain: 'BONUS 4', - typeJeu: 'Quinte+', - montant: 2000, - nombre: randomInt(5000, 25000), - statut: 'Validée', - distributed: false, - externe: false, - }, - { - typeGain: 'REMBOURSEMENT', - typeJeu: 'Quinte+', - montant: 300, - nombre: randomInt(10, 500), - statut: 'Validée', - distributed: false, - externe: false, - }, - { - typeGain: 'TIERCE ORDRE', - typeJeu: 'Tierce', - montant: 37000, - nombre: randomInt(100, 2000), - statut: 'Validée', - distributed: false, - externe: false, - }, - { - typeGain: 'TIERCE DESORDRE', - typeJeu: 'Tierce', - montant: 6000, - nombre: randomInt(500, 6000), - statut: 'Validée', - distributed: false, - externe: false, - }, - { - typeGain: 'TRANSFORME COUPLE', - typeJeu: 'Tierce', - montant: 3000, - nombre: randomInt(200, 2000), - statut: 'Validée', - distributed: false, - externe: false, - }, - { - typeGain: 'TRANSFORME SIMPLE', - typeJeu: 'Tierce', - montant: 1500, - nombre: randomInt(10, 500), - statut: 'Validée', - distributed: false, - externe: false, - }, - ]; - return base; -} +// export function payoutRowsForCourse(c: Course): CourseReportDetailRow[] { +// const base: CourseReportDetailRow[] = [ +// { +// typeGain: 'QUINTE ORDRE', +// typeJeu: 'Quinte+', +// montant: 2840500, +// nombre: randomInt(1, 30), +// statut: 'Validée', +// distributed: false, +// externe: false, +// }, +// { +// typeGain: 'QUINTE DESORDRE', +// typeJeu: 'Quinte+', +// montant: 40000, +// nombre: randomInt(300, 5000), +// statut: 'Validée', +// distributed: false, +// externe: false, +// }, +// { +// typeGain: 'BONUS 4', +// typeJeu: 'Quinte+', +// montant: 2000, +// nombre: randomInt(5000, 25000), +// statut: 'Validée', +// distributed: false, +// externe: false, +// }, +// { +// typeGain: 'REMBOURSEMENT', +// typeJeu: 'Quinte+', +// montant: 300, +// nombre: randomInt(10, 500), +// statut: 'Validée', +// distributed: false, +// externe: false, +// }, +// { +// typeGain: 'TIERCE ORDRE', +// typeJeu: 'Tierce', +// montant: 37000, +// nombre: randomInt(100, 2000), +// statut: 'Validée', +// distributed: false, +// externe: false, +// }, +// { +// typeGain: 'TIERCE DESORDRE', +// typeJeu: 'Tierce', +// montant: 6000, +// nombre: randomInt(500, 6000), +// statut: 'Validée', +// distributed: false, +// externe: false, +// }, +// { +// typeGain: 'TRANSFORME COUPLE', +// typeJeu: 'Tierce', +// montant: 3000, +// nombre: randomInt(200, 2000), +// statut: 'Validée', +// distributed: false, +// externe: false, +// }, +// { +// typeGain: 'TRANSFORME SIMPLE', +// typeJeu: 'Tierce', +// montant: 1500, +// nombre: randomInt(10, 500), +// statut: 'Validée', +// distributed: false, +// externe: false, +// }, +// ]; +// return base; +// } -export const REPORT_SUMMARIES_MOCK: CourseReportSummary[] = COURSES_MOCK.filter( - (c) => c.statut === 'CLOSED' -) - .slice(0, 300) - .map( - (c) => ({ id: c.id, course: c, statut: 'En attente', confirmed: false } as CourseReportSummary) - ); +// export const REPORT_SUMMARIES_MOCK: CourseReportSummary[] = COURSES_MOCK.filter( +// (c) => c.statut === 'CLOSED' +// ) +// .slice(0, 300) +// .map( +// (c) => ({ id: c.id, course: c, statut: 'En attente', confirmed: false } as CourseReportSummary) +// ); -export function buildDetailByCourseId(id: string): CourseReportDetail | undefined { - const summary = REPORT_SUMMARIES_MOCK.find((s) => s.id === id); - if (!summary) return undefined; - const rows = payoutRowsForCourse(summary.course as Course); - return { summary, rows } as CourseReportDetail; -} +// export function buildDetailByCourseId(id: string): CourseReportDetail | undefined { +// const summary = REPORT_SUMMARIES_MOCK.find((s) => s.id === id); +// if (!summary) return undefined; +// const rows = payoutRowsForCourse(summary.course as Course); +// return { summary, rows } as CourseReportDetail; +// } -// Pre-built rows map for in-memory updates -export const REPORT_DETAILS_MOCK = new Map(); -for (const c of COURSES_MOCK.filter((c) => c.statut === 'CLOSED').slice(0, 300)) { - REPORT_DETAILS_MOCK.set(c.id, payoutRowsForCourse(c)); -} +// // Pre-built rows map for in-memory updates +// export const REPORT_DETAILS_MOCK = new Map(); +// for (const c of COURSES_MOCK.filter((c) => c.statut === 'CLOSED').slice(0, 300)) { +// REPORT_DETAILS_MOCK.set(c.id, payoutRowsForCourse(c)); +// } diff --git a/src/app/core/services/agent-limit.ts b/src/app/core/services/agent-limit.ts index 15d02c1..0264599 100644 --- a/src/app/core/services/agent-limit.ts +++ b/src/app/core/services/agent-limit.ts @@ -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[] = []; diff --git a/src/app/core/services/agent.ts b/src/app/core/services/agent.ts index 78dbac5..d0af32a 100644 --- a/src/app/core/services/agent.ts +++ b/src/app/core/services/agent.ts @@ -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); } diff --git a/src/app/core/services/course.ts b/src/app/core/services/course.ts index bb069e6..f82ad07 100644 --- a/src/app/core/services/course.ts +++ b/src/app/core/services/course.ts @@ -10,34 +10,31 @@ 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/v1/courses'; +const API_BASE = '/api/courses'; // Interface to match the API response structure for Course -interface CourseApiResponse { - id: number; - type: string; - numero: number; +export interface CourseApiResponse { + id: string; + hippodromeId: number; + reunionNumero: number; + reunionDate: string; 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; + numero: number; + heureDepartPrevue: string; + discipline: string; + distanceMetres: number; + categorie: string; + nombrePartants: number; + statut: string; + annulee: boolean; + reporteeMemeJour: boolean; + reporteeAutreJour: boolean; + incidentTechnique: boolean; + nonPartants: Array; + typesParisOuverts: Array; } @Injectable({ providedIn: 'root' }) @@ -47,6 +44,8 @@ export class CourseService { constructor( private http: HttpClient, private paginatedHttp: PaginatedHttpService, + private servivesUtil: ServicesUtils, + private hippodromeService: HippodromeService, private reunionService: ReunionService, // Inject ReunionService private nonPartantService: NonPartantService // Inject NonPartantService ) {} @@ -60,408 +59,59 @@ export class CourseService { return isNgrok ? { 'ngrok-skip-browser-warning': 'true' } : {}; } - list( - params: ListParams, - usePaginationEndpoint: boolean = false - ): Observable> { - 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( + list(params: ListParams, usePaginationEndpoint: boolean = true): Observable> { + const coursesList = this.http.get>(this.apiUrl, { + headers: this.getNgrokHeaders(), + params: this.servivesUtil.getParamsFromModel(params), + }); + return coursesList.pipe( + switchMap((res) => { + const items = res.content ?? []; + + // Transforme chaque course en Observable + 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) => { - // 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>((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( - { - data: pageData, - meta: { - total, - totalRunning, - totalClosed, - totalByType, - }, - }, - params.page, - params.perPage - ); - }), - catchError((err) => { - console.error('Error searching courses:', err); - return of( - normalizePage( - { - data: [], - meta: { - total: 0, - totalRunning: 0, - totalClosed: 0, - totalByType: {}, - }, - }, - params.page, - params.perPage - ) - ); + const mapped: PagedResult = { + pageable: res.pageable, + totalPages: res.totalPages, + totalElements: res.totalElements, + content: courses, + }; + return mapped; }) ); - } - - if (usePaginationEndpoint) { - return this.paginatedHttp - .fetch(this.apiUrl, params, { - zeroBasedPageIndex: false, - }) - .pipe( - switchMap((pagedResult) => { - // Handle empty data case - if (!pagedResult.data || pagedResult.data.length === 0) { - return of( - normalizePage( - { - 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( - { - 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(undefined))) - ); - - return forkJoin(reunionRequests).pipe( - map((reunions) => { - // Create a map of reunionId -> Reunion - const reunionMap = new Map(); - 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>((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( - { - 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( - { - 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(this.apiUrl, { headers: this.getNgrokHeaders() }) - .pipe( - switchMap((apiData) => { - // Handle empty data case - if (!apiData || apiData.length === 0) { - return of( - normalizePage( - { - 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( - { - 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(undefined))) - ); - - return forkJoin(reunionRequests).pipe( - map((reunions) => { - // Create a map of reunionId -> Reunion - const reunionMap = new Map(); - 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>((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( - { - data: pageData, - meta: { - total, - totalRunning, - totalClosed, - totalByType, - }, - }, - params.page, - params.perPage - ); - }) - ); - }), - catchError((err) => { - console.error('Error fetching courses:', err); - return of( - normalizePage( - { - data: [], - meta: { - total: 0, - totalRunning: 0, - totalClosed: 0, - totalByType: {}, - }, - }, - params.page, - params.perPage - ) - ); - }) - ); - } - } - - // If USE_SERVER is false, return empty result - return of( - normalizePage( - { - data: [], - meta: { - total: 0, - totalRunning: 0, - totalClosed: 0, - totalByType: {}, - }, - }, - params.page, - params.perPage - ) + }), + 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); + }) ); } @@ -469,19 +119,19 @@ export class CourseService { 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) - ); - }); - } + // 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) { @@ -509,35 +159,30 @@ export class CourseService { .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 this.hippodromeService.getById(String(apiCourse.hippodromeId)).pipe( + map((hippodrome) => { + if (!hippodrome) { return undefined; } return { id: String(apiCourse.id), - type: apiCourse.type, - numero: apiCourse.numero, + hippodrome: hippodrome ?? undefined, + reunionNumero: apiCourse.reunionNumero, + reunionDate: apiCourse.reunionDate, 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, + 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 || [], - 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(), + typesParisOuverts: apiCourse.typesParisOuverts || [] }; }) ); @@ -551,189 +196,168 @@ export class CourseService { return of(undefined); } - getByReunionId(reunionId: string): Observable { - if (USE_SERVER) { - return this.http - .get(`${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([]); - } + // getByReunionId(reunionId: string): Observable { + // if (USE_SERVER) { + // return this.http + // .get(`${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 { - if (USE_SERVER) { - return this.http - .get(`${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)))]; + // search(query: string): Observable { + // if (USE_SERVER) { + // return this.http + // .get(`${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(undefined))) - ); + // // Fetch all reunions in parallel + // const reunionRequests = uniqueReunionIds.map((id) => + // this.reunionService + // .getById(id) + // .pipe(catchError(() => of(undefined))) + // ); - return forkJoin(reunionRequests).pipe( - map((reunions) => { - // Create a map of reunionId -> Reunion - const reunionMap = new Map(); - uniqueReunionIds.forEach((id, index) => { - const reunion = reunions[index]; - if (reunion) { - reunionMap.set(id, reunion); - } - }); + // return forkJoin(reunionRequests).pipe( + // map((reunions) => { + // // Create a map of reunionId -> Reunion + // const reunionMap = new Map(); + // 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([]); - } + // // 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): Observable { + create(payload: Partial): Observable { 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, - }; + const apiPayload: Partial = payload; return this.http .post(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'); + 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), - type: apiCourse.type, - numero: apiCourse.numero, + hippodrome: hippodrome ?? undefined, + reunionNumero: apiCourse.reunionNumero, + reunionDate: apiCourse.reunionDate, 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, + 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 || [], - 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(), + typesParisOuverts: apiCourse.typesParisOuverts || [] }; return item; }) @@ -748,52 +372,41 @@ export class CourseService { throw new Error('Server mode is required'); } - update(id: string, payload: Partial): Observable { + update(id: string, payload: Partial): Observable { 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; - } + // Transform payload to API format (send reunionId instead of reunion object return this.http - .put(`${this.apiUrl}/${id}`, apiPayload, { + .put(`${this.apiUrl}/${id}`, payload, { 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) { + return this.hippodromeService.getById(String(apiCourse.hippodromeId)).pipe( + map((hippodrome) => { + if (!hippodrome) { throw new Error('Reunion not found'); } return { id: String(apiCourse.id), - type: apiCourse.type, - numero: apiCourse.numero, + hippodrome: hippodrome ?? undefined, + reunionNumero: apiCourse.reunionNumero, + reunionDate: apiCourse.reunionDate, 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, + 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 || [], - 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(), + typesParisOuverts: apiCourse.typesParisOuverts || [] }; }) ); diff --git a/src/app/core/services/hippodrome.ts b/src/app/core/services/hippodrome.ts index ffd3308..9387b80 100644 --- a/src/app/core/services/hippodrome.ts +++ b/src/app/core/services/hippodrome.ts @@ -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([]); - 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 { @@ -30,326 +32,13 @@ export class HippodromeService { // LISTE — supporte client & serveur list( params: ListParams, - usePaginationEndpoint: boolean = false + usePaginationEndpoint: boolean = true ): Observable> { - 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(`${environment.apiBaseUrl}/api/v1/reunions`, { - headers: this.getNgrokHeaders(), - }) - .pipe( - catchError(() => of([])), - map((data) => ({ data: data || [], meta: { total: (data || []).length } })) - ), - courses: this.http - .get(`${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(); - 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(); - 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(); - 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( - { - data: pageData, - meta: { - total, - uniqueCountries, - uniqueCities, - averageByCountry, - totalReunions, - totalCourses, - }, - }, - params.page, - params.perPage - ); - }) - ); - }), - catchError((err) => { - console.error('Error searching hippodromes:', err); - return of( - normalizePage( - { - data: [], - meta: { - total: 0, - uniqueCountries: 0, - uniqueCities: 0, - averageByCountry: 0, - totalReunions: 0, - totalCourses: 0, - }, - }, - params.page, - params.perPage - ) - ); - }) - ); - } - - if (usePaginationEndpoint) { - return this.paginatedHttp - .fetch(this.apiUrl, params, { - zeroBasedPageIndex: false, - buildSort: (key, dir) => (key && dir ? ['sort', `${key},${dir}`] : null), - mapClientSortKey: (k) => { - const alias: Record = { - name: 'nom', - city: 'ville', - country: 'pays', - }; - return k ? alias[k] ?? k : undefined; - }, - }) - .pipe( - catchError((err) => { - console.error('Error fetching hippodromes:', err); - return of( - normalizePage( - { - 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(`${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(`${environment.apiBaseUrl}/api/v1/reunions`, { - headers: this.getNgrokHeaders(), - }) - .pipe( - catchError(() => of([])), - map((data) => ({ data, meta: { total: data.length } })) - ), - courses: this.http - .get(`${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(); - 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(); - 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(); - 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( - { - data: pageData, - meta: { - total, - uniqueCountries, - uniqueCities, - averageByCountry, - totalReunions, - totalCourses, - }, - }, - params.page, - params.perPage - ); - }) - ); - }), - catchError((err) => { - console.error('Error fetching hippodromes:', err); - return of( - normalizePage( - { - 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( - { - data: [], - meta: { - total: 0, - uniqueCountries: 0, - uniqueCities: 0, - averageByCountry: 0, - totalReunions: 0, - totalCourses: 0, - }, - }, - params.page, - params.perPage - ) - ); + const hippodromeList = this.http.get>(this.apiUrl, { + headers: this.getNgrokHeaders(), + params: this.servicesUtils.getParamsFromModel(params) + }) + return hippodromeList; } private applyClientFilters(data: Hippodrome[], params: ListParams): Hippodrome[] { diff --git a/src/app/core/services/report.ts b/src/app/core/services/report.ts index 5f4fbd5..74997d5 100644 --- a/src/app/core/services/report.ts +++ b/src/app/core/services/report.ts @@ -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([...REPORT_SUMMARIES_MOCK]); +// @Injectable({ providedIn: 'root' }) +// export class ReportService { +// private summaries = signal([...REPORT_SUMMARIES_MOCK]); - list(params: ListParams): Observable> { - 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({ data: pageData, meta: { total: data.length } }, params.page, params.perPage)); - } +// list(params: ListParams): Observable> { +// 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({ data: pageData, meta: { total: data.length } }, params.page, params.perPage)); +// } - getDetail(courseId: string): Observable { - 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 { +// 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 { - 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 { +// 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 { - 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 { +// 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 { - 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 { +// 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 { - REPORT_DETAILS_MOCK.set(courseId, rows); - return of(true); - } -} +// modifyRows(courseId: string, rows: CourseReportDetailRow[]): Observable { +// REPORT_DETAILS_MOCK.set(courseId, rows); +// return of(true); +// } +// } diff --git a/src/app/core/services/reunion.ts b/src/app/core/services/reunion.ts index 17e551e..bf3be49 100644 --- a/src/app/core/services/reunion.ts +++ b/src/app/core/services/reunion.ts @@ -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> { 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(undefined))) + .pipe(catchError(() => of())) ); // Fetch courses to calculate counts per reunion const coursesRequest = this.http - .get(`${environment.apiBaseUrl}/api/v1/courses`, { + .get(`${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, + } }) ); }), diff --git a/src/app/core/services/services-utils.ts b/src/app/core/services/services-utils.ts new file mode 100644 index 0000000..88844fd --- /dev/null +++ b/src/app/core/services/services-utils.ts @@ -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; + } +} \ No newline at end of file diff --git a/src/app/dashboard/dashboard-routing-module.ts b/src/app/dashboard/dashboard-routing-module.ts index da7b5f2..726eab0 100644 --- a/src/app/dashboard/dashboard-routing-module.ts +++ b/src/app/dashboard/dashboard-routing-module.ts @@ -7,7 +7,6 @@ const routes: Routes = [ { path: '', component: Layout, - canActivate: [authGuard], children: [ { path: '', loadComponent: () => import('./pages/main/main').then((m) => m.Main) }, { @@ -46,16 +45,6 @@ const routes: Routes = [ path: 'limits', loadComponent: () => import('./pages/limits/limits').then((m) => m.LimitsPage), }, - { - path: 'rapport-courses', - loadComponent: () => - import('./pages/report-courses/report-list').then((m) => m.ReportCoursesListPage), - }, - { - path: 'rapport-courses/:id', - loadComponent: () => - import('./pages/report-courses/report-detail').then((m) => m.ReportCoursesDetailPage), - }, ], }, ]; diff --git a/src/app/dashboard/pages/agents/agents.ts b/src/app/dashboard/pages/agents/agents.ts index 5c8c049..5886e8b 100644 --- a/src/app/dashboard/pages/agents/agents.ts +++ b/src/app/dashboard/pages/agents/agents.ts @@ -150,7 +150,7 @@ export class AgentsPage { this.tpeSvc .list({ page: 1, perPage: 200, search: '', sortKey: 'imei', sortDir: 'asc' } as any) .subscribe((res) => { - const tpes = res.data as TpeDevice[]; + const tpes = res.content as TpeDevice[]; this.rebuildTpeMaps(tpes); }); effect(() => { @@ -175,8 +175,8 @@ export class AgentsPage { this.loading.set(true); this.api.list(params).subscribe({ next: (res) => { - this.rows.set(res.data); - this.total.set(res.meta.total); + this.rows.set(res.content); + this.total.set(res.pageable.total); this.loading.set(false); // Refresh TPE map to ensure we have latest data this.refreshTpeMap(); @@ -193,7 +193,7 @@ export class AgentsPage { this.tpeSvc .list({ page: 1, perPage: 200, search: '', sortKey: 'imei', sortDir: 'asc' } as any) .subscribe((res) => { - const tpes = res.data as TpeDevice[]; + const tpes = res.content as TpeDevice[]; this.rebuildTpeMaps(tpes); }); } diff --git a/src/app/dashboard/pages/courses/courses.ts b/src/app/dashboard/pages/courses/courses.ts index a0600ee..519ce15 100644 --- a/src/app/dashboard/pages/courses/courses.ts +++ b/src/app/dashboard/pages/courses/courses.ts @@ -16,7 +16,7 @@ import { ZardCardComponent } from '@shared/components/card/card.component'; import { ZardButtonComponent } from '@shared/components/button/button.component'; import { Course as CourseType } from 'src/app/core/interfaces/course'; import { SortDir } from '@shared/paging/paging'; -import { CourseService } from 'src/app/core/services/course'; +import { CourseApiResponse, CourseService } from 'src/app/core/services/course'; import { ResultatService } from 'src/app/core/services/resultat'; import { Resultat } from 'src/app/core/interfaces/resultat'; import { A11yModule } from '@angular/cdk/a11y'; @@ -77,23 +77,19 @@ export class Course { key: 'type', label: 'Type', sortable: true, - cell: (c) => `${c.type}`, + cell: (c) => `${c.discipline}`, }, { key: 'dateDepartCourse', label: 'Date et Heure Départ', sortable: true, - cell: (c) => - new Date(c.dateDepartCourse).toLocaleDateString('fr-FR', { - hour: '2-digit', - minute: '2-digit', - }), + cell: (c) => c.heureDepartPrevue }, { key: 'partants', label: 'Partants', cell: (c) => - `${c.partants} (${ + `${c.nombrePartants} (${ c.nonPartants?.length ?? 0 } NP)`, }, @@ -148,20 +144,24 @@ export class Course { sortable: true, cell: (c) => { const colorMap: Record = { - PROGRAMMEE: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300', - CREATED: 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-300', - VALIDATED: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300', - RUNNING: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300', - CLOSED: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300', - CANCELED: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300', + OUVERT: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300', + RESULTAT_PROVISOIRE: 'bg-cyan-100 text-purple-700 dark:bg-cyan-900/30 dark:text-cyan-300', + RESULTAT_OFFICIEL: 'bg-amber-100 text-purple-700 dark:bg-amber-900/30 dark:text-amber-300', + BROUILLON: 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-300', + VALIDE: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300', + REGLEE: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300', + FERME: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300', + ANNULEE: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300', }; const labelMap: Record = { - PROGRAMMEE: 'Programmée', - CREATED: 'Créée', - VALIDATED: 'Validée', - RUNNING: 'En cours', - CLOSED: 'Clôturée', - CANCELED: 'Annulée', + OUVERT: 'Ouvert', + RESULTAT_PROVISOIRE: 'Résultat provisoire', + RESULTAT_OFFICIEL: 'Résultat officiel', + BROUILLON: 'Brouillon', + VALIDE: 'Validé', + REGLEE: 'Réglée', + FERME: 'Fermé', + ANNULEE: 'Annulée' }; return `${ labelMap[c.statut] @@ -171,31 +171,13 @@ export class Course { { key: 'reunion.hippodrome.nom', label: 'Hippodrome', - cell: (c) => (c.reunion?.hippodrome ? `${c.reunion.hippodrome.nom}` : '—'), - }, - { - key: 'reunion.nom', - label: 'Réunion', - cell: (c) => c.reunion?.nom ?? '—', + cell: (c) => (c?.hippodrome ? `${c.hippodrome.nom}` : '—'), }, { key: 'distance', label: 'Distance (m)', sortable: true, - cell: (c) => c.distance.toLocaleString('fr-FR'), - }, - - { - key: 'createdAt', - label: 'Créée le', - cell: (c) => - c.createdAt - ? new Date(c.createdAt).toLocaleDateString('fr-FR', { - day: '2-digit', - month: 'short', - year: 'numeric', - }) - : '—', + cell: (c) => c.distanceMetres.toLocaleString('fr-FR'), }, ]; @@ -224,14 +206,14 @@ export class Course { this.loading.set(true); this.api.list(params).subscribe({ next: (res) => { - this.rows.set(res.data); - this.total.set(res.meta.total); - this.totalRunning.set(res.meta['totalRunning'] ?? 0); - this.totalClosed.set(res.meta['totalClosed'] ?? 0); - this.totalByType.set(res.meta['totalByType'] ?? {}); + this.rows.set(res.content); + this.total.set(res.totalElements); + this.totalRunning.set(0); + this.totalClosed.set(0); + this.totalByType.set({}); // Fetch resultats for all courses in parallel - const courseIds = res.data.map((c) => c.id); + const courseIds = res.content.map((c) => c.id); if (courseIds.length > 0) { const resultatRequests = courseIds.map((id) => this.resultatService.getByCourseId(id).pipe(catchError(() => of(undefined))) @@ -305,7 +287,7 @@ export class Course { const current = this.editingItem(); const req$ = current?.id ? this.api.update(current.id, payload) - : this.api.create(payload as Omit); + : this.api.create(payload as Omit); req$.subscribe(() => { this.closeModal(); @@ -412,7 +394,7 @@ export class Course { return 3; // Default }; - const requiredHorses = getRequiredHorses(c.type); + const requiredHorses = 3; // Collect all selected horses (flatten the places array) const allHorses: number[] = places diff --git a/src/app/dashboard/pages/hippodrome/hippodrome.html b/src/app/dashboard/pages/hippodrome/hippodrome.html index 62f9a9d..7d719c6 100644 --- a/src/app/dashboard/pages/hippodrome/hippodrome.html +++ b/src/app/dashboard/pages/hippodrome/hippodrome.html @@ -33,18 +33,6 @@ {{ averageByCountry() }} - -
Réunions totales
-
- {{ totalReunions() }} -
-
- -
Courses totales
-
- {{ totalCourses() }} -
-
diff --git a/src/app/dashboard/pages/hippodrome/hippodrome.ts b/src/app/dashboard/pages/hippodrome/hippodrome.ts index 4cf34ee..69fd188 100644 --- a/src/app/dashboard/pages/hippodrome/hippodrome.ts +++ b/src/app/dashboard/pages/hippodrome/hippodrome.ts @@ -58,18 +58,6 @@ export class Hippodrome { { key: 'nom', label: 'Nom', sortable: true }, { key: 'ville', label: 'Ville', sortable: true }, { key: 'pays', label: 'Pays', sortable: true }, - { - key: 'reunionCount', - label: 'Réunions', - sortable: true, - cell: (h) => (h.reunionCount ?? 0).toString(), - }, - { - key: 'courseCount', - label: 'Courses', - sortable: true, - cell: (h) => (h.courseCount ?? 0).toString(), - }, { key: 'capacite', label: 'Capacité', @@ -124,26 +112,20 @@ export class Hippodrome { }) .subscribe({ next: (res) => { - this.rows.set(res.data); - - const meta = res.meta ?? {}; - - this.total.set(meta['total'] ?? 0); - this.uniqueCities.set(meta['uniqueCities'] ?? 0); - this.uniqueCountries.set(meta['uniqueCountries'] ?? 0); - this.averageByCountry.set(meta['averageByCountry'] ?? 0); - this.totalReunions.set(meta['totalReunions'] ?? 0); - this.totalCourses.set(meta['totalCourses'] ?? 0); + const content = res.content; + this.rows.set(content); + this.total.set(res.pageable.total ?? 0); + this.uniqueCities.set(new Set(content.map(i=> i.ville)).size); + this.uniqueCountries.set(new Set(content.map(i=>i.pays)).size); + this.averageByCountry.set(0); this.loading.set(false); }, - error: () => { + error: (err) => { this.rows.set([]); this.total.set(0); this.uniqueCities.set(0); this.uniqueCountries.set(0); this.averageByCountry.set(0); - this.totalReunions.set(0); - this.totalCourses.set(0); this.loading.set(false); }, }); diff --git a/src/app/dashboard/pages/limits/limits.ts b/src/app/dashboard/pages/limits/limits.ts index 1a6b0f8..0cde4cb 100644 --- a/src/app/dashboard/pages/limits/limits.ts +++ b/src/app/dashboard/pages/limits/limits.ts @@ -127,7 +127,7 @@ export class LimitsPage implements OnInit { }).pipe( switchMap((res) => { // Convert PagedResult to array for consistency - return of(res.data); + return of(res.content); }) ); } @@ -190,8 +190,8 @@ export class LimitsPage implements OnInit { // Normal list with pagination this.api.list(params).subscribe({ next: (res) => { - this.rows.set(res.data); - this.total.set(res.meta.total); + this.rows.set(res.content); + this.total.set(res.pageable.total); this.loading.set(false); }, error: () => { diff --git a/src/app/dashboard/pages/main/main.html b/src/app/dashboard/pages/main/main.html index 7c4dcd8..2a635c3 100644 --- a/src/app/dashboard/pages/main/main.html +++ b/src/app/dashboard/pages/main/main.html @@ -34,11 +34,11 @@ {{ c.nom }} - @if (c.type) { + @if (c.discipline) { - {{ c.type }} + {{ c.discipline }} } @@ -61,19 +61,15 @@ > {{ - c.dateDepartCourse - ? (c.dateDepartCourse | date : 'short' : undefined : 'fr-FR') - : '—' + c.heureDepartPrevue }}
- {{ c.reunion.hippodrome.nom }} + {{ c.nom }} - Réunion {{ c.reunion.nom }} - - Distance {{ c.distance | number : '1.0-0' }} m + Distance {{ c.distanceMetres | number : '1.0-0' }} m
- {{ c.partants }} partant{{ c.partants > 1 ? 's' : '' }} + {{ c.nombrePartants }} partant{{ c.nombrePartants > 1 ? 's' : '' }} @if (c.nonPartants && c.nonPartants.length > 0) { {{ c.nonPartants.length }} non-partant{{ c.nonPartants.length > 1 ? 's' : '' }} - } @if (c.condition) { + } @if (c.numero) { - {{ c.condition }} - } @if (c.particularite) { + {{ c.numero }} + } @if (c.discipline) { - ⭐ {{ c.particularite }} + ⭐ {{ c.discipline }} }
diff --git a/src/app/dashboard/pages/main/main.ts b/src/app/dashboard/pages/main/main.ts index ead5990..96287cd 100644 --- a/src/app/dashboard/pages/main/main.ts +++ b/src/app/dashboard/pages/main/main.ts @@ -223,26 +223,21 @@ export class Main { // Include PROGRAMMEE courses that are scheduled within the next 24 hours if (statut === 'PROGRAMMEE') { - const d = c.dateDepartCourse ? new Date(c.dateDepartCourse) : null; + const d = c.heureDepartPrevue ? c.heureDepartPrevue : null; if (!d) return false; // Include if departure is in the past hour (just started) or within next 24 hours - return d >= oneHourAgo && d <= oneDayAhead; + return d ; } // Also include VALIDATED courses that are about to start (within next 24 hours) if (statut === 'VALIDATED') { - const d = c.dateDepartCourse ? new Date(c.dateDepartCourse) : null; + const d = c.heureDepartPrevue ? c.heureDepartPrevue : null; if (!d) return false; - return d >= now && d <= oneDayAhead; + return d; } return false; }) - .sort((a, b) => { - const da = a.dateDepartCourse ? new Date(a.dateDepartCourse).getTime() : 0; - const db = b.dateDepartCourse ? new Date(b.dateDepartCourse).getTime() : 0; - return da - db; // Sort by departure time ascending (earliest first) - }) .slice(0, 6); this.liveCourses.set(live); diff --git a/src/app/dashboard/pages/report-courses/report-detail.ts b/src/app/dashboard/pages/report-courses/report-detail.ts index f003edc..5fd4a52 100644 --- a/src/app/dashboard/pages/report-courses/report-detail.ts +++ b/src/app/dashboard/pages/report-courses/report-detail.ts @@ -1,132 +1,132 @@ -import { CommonModule } from '@angular/common'; -import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; -import { ActivatedRoute, RouterModule } from '@angular/router'; -import { ZardCardComponent } from '@shared/components/card/card.component'; -import { ReportService } from 'src/app/core/services/report'; -import { CourseReportDetail, CourseReportDetailRow } from 'src/app/core/interfaces/report'; -import { ZardButtonComponent } from '@shared/components/button/button.component'; +// import { CommonModule } from '@angular/common'; +// import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; +// import { ActivatedRoute, RouterModule } from '@angular/router'; +// import { ZardCardComponent } from '@shared/components/card/card.component'; +// import { ReportService } from 'src/app/core/services/report'; +// import { CourseReportDetail, CourseReportDetailRow } from 'src/app/core/interfaces/report'; +// import { ZardButtonComponent } from '@shared/components/button/button.component'; -@Component({ - standalone: true, - selector: 'app-report-courses-detail', - templateUrl: './report-detail.html', - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [CommonModule, RouterModule, ZardCardComponent, ZardButtonComponent], -}) -export class ReportCoursesDetailPage { - detail = signal(undefined); - editMode = signal(false); - editedRows = signal([]); - private originalRows = signal([]); +// @Component({ +// standalone: true, +// selector: 'app-report-courses-detail', +// templateUrl: './report-detail.html', +// changeDetection: ChangeDetectionStrategy.OnPush, +// imports: [CommonModule, RouterModule, ZardCardComponent, ZardButtonComponent], +// }) +// export class ReportCoursesDetailPage { +// detail = signal(undefined); +// editMode = signal(false); +// editedRows = signal([]); +// private originalRows = signal([]); - constructor(private route: ActivatedRoute, private api: ReportService) { - const id = this.route.snapshot.params['id']; - this.api.getDetail(id).subscribe((d) => { - this.detail.set(d); - this.editedRows.set(d?.rows ?? []); - this.originalRows.set(d?.rows ? d.rows.map((r) => ({ ...r })) : []); - }); - } +// constructor(private route: ActivatedRoute, private api: ReportService) { +// const id = this.route.snapshot.params['id']; +// this.api.getDetail(id).subscribe((d) => { +// this.detail.set(d); +// this.editedRows.set(d?.rows ?? []); +// this.originalRows.set(d?.rows ? d.rows.map((r) => ({ ...r })) : []); +// }); +// } - onValidate() { - const id = this.detail()?.summary.id; - if (!id) return; - // Persist edited rows then validate - this.api.modifyRows(id, this.editedRows()).subscribe(() => { - this.api.validate(id).subscribe((s) => { - if (this.detail()) this.detail.set({ summary: s!, rows: this.editedRows() }); - // Commit current edits as the new baseline - this.originalRows.set(this.editedRows().map((r) => ({ ...r }))); - this.editMode.set(false); - }); - }); - } - onConfirm() { - const id = this.detail()?.summary.id; - if (!id) return; - this.api.confirm(id).subscribe((s) => { - if (this.detail()) this.detail.set({ summary: s!, rows: this.editedRows() }); - // Confirm also commits the current edits as baseline - this.originalRows.set(this.editedRows().map((r) => ({ ...r }))); - this.editMode.set(false); - }); - } - onReset() { - const id = this.detail()?.summary.id; - if (!id) return; - this.api.resetStatus(id).subscribe((s) => { - if (this.detail()) this.detail.set({ summary: s!, rows: this.detail()!.rows }); - // Reset discards uncommitted edits - this.editedRows.set(this.originalRows().map((r) => ({ ...r }))); - this.editMode.set(false); - }); - } +// onValidate() { +// const id = this.detail()?.summary.id; +// if (!id) return; +// // Persist edited rows then validate +// this.api.modifyRows(id, this.editedRows()).subscribe(() => { +// this.api.validate(id).subscribe((s) => { +// if (this.detail()) this.detail.set({ summary: s!, rows: this.editedRows() }); +// // Commit current edits as the new baseline +// this.originalRows.set(this.editedRows().map((r) => ({ ...r }))); +// this.editMode.set(false); +// }); +// }); +// } +// onConfirm() { +// const id = this.detail()?.summary.id; +// if (!id) return; +// this.api.confirm(id).subscribe((s) => { +// if (this.detail()) this.detail.set({ summary: s!, rows: this.editedRows() }); +// // Confirm also commits the current edits as baseline +// this.originalRows.set(this.editedRows().map((r) => ({ ...r }))); +// this.editMode.set(false); +// }); +// } +// onReset() { +// const id = this.detail()?.summary.id; +// if (!id) return; +// this.api.resetStatus(id).subscribe((s) => { +// if (this.detail()) this.detail.set({ summary: s!, rows: this.detail()!.rows }); +// // Reset discards uncommitted edits +// this.editedRows.set(this.originalRows().map((r) => ({ ...r }))); +// this.editMode.set(false); +// }); +// } - onEditToggle() { - if (this.detail()?.summary.confirmed) return; - const currentlyEditing = this.editMode(); - if (currentlyEditing) { - // Leaving edit mode without validation: revert to original snapshot - this.editedRows.set(this.originalRows().map((r) => ({ ...r }))); - this.editMode.set(false); - } else { - this.editMode.set(true); - } - } +// onEditToggle() { +// if (this.detail()?.summary.confirmed) return; +// const currentlyEditing = this.editMode(); +// if (currentlyEditing) { +// // Leaving edit mode without validation: revert to original snapshot +// this.editedRows.set(this.originalRows().map((r) => ({ ...r }))); +// this.editMode.set(false); +// } else { +// this.editMode.set(true); +// } +// } - onChangeMontant(index: number, value: any) { - const v = Number(value); - this.editedRows.update((rows: CourseReportDetailRow[]) => { - const current = rows[index]; - if (!current) return rows; - current.montant = Number.isFinite(v) ? v : current.montant; - return rows; - }); - } +// onChangeMontant(index: number, value: any) { +// const v = Number(value); +// this.editedRows.update((rows: CourseReportDetailRow[]) => { +// const current = rows[index]; +// if (!current) return rows; +// current.montant = Number.isFinite(v) ? v : current.montant; +// return rows; +// }); +// } - onChangeNombre(index: number, value: any) { - const v = Number(value); - this.editedRows.update((rows: CourseReportDetailRow[]) => { - const current = rows[index]; - if (!current) return rows; - current.nombre = Number.isFinite(v) ? v : current.nombre; - return rows; - }); - } +// onChangeNombre(index: number, value: any) { +// const v = Number(value); +// this.editedRows.update((rows: CourseReportDetailRow[]) => { +// const current = rows[index]; +// if (!current) return rows; +// current.nombre = Number.isFinite(v) ? v : current.nombre; +// return rows; +// }); +// } - onToggleDistributed(index: number, value: any) { - const checked = !!value; - this.editedRows.update((rows: CourseReportDetailRow[]) => { - const current = rows[index]; - if (!current) return rows; - current.distributed = checked; - return rows; - }); - } +// onToggleDistributed(index: number, value: any) { +// const checked = !!value; +// this.editedRows.update((rows: CourseReportDetailRow[]) => { +// const current = rows[index]; +// if (!current) return rows; +// current.distributed = checked; +// return rows; +// }); +// } - onToggleExterne(index: number, value: any) { - const checked = !!value; - this.editedRows.update((rows: CourseReportDetailRow[]) => { - const current = rows[index]; - if (!current) return rows; - current.externe = checked; - return rows; - }); - } +// onToggleExterne(index: number, value: any) { +// const checked = !!value; +// this.editedRows.update((rows: CourseReportDetailRow[]) => { +// const current = rows[index]; +// if (!current) return rows; +// current.externe = checked; +// return rows; +// }); +// } - trackByRow(index: number, row: CourseReportDetailRow) { - return row.typeGain + '|' + row.typeJeu + '|' + index; - } +// trackByRow(index: number, row: CourseReportDetailRow) { +// return row.typeGain + '|' + row.typeJeu + '|' + index; +// } - isRowDirty(index: number): boolean { - const current = this.editedRows()[index]; - const original = this.originalRows()[index]; - if (!current || !original) return false; - return ( - current.montant !== original.montant || - current.nombre !== original.nombre || - !!current.distributed !== !!original.distributed || - !!current.externe !== !!original.externe - ); - } -} +// isRowDirty(index: number): boolean { +// const current = this.editedRows()[index]; +// const original = this.originalRows()[index]; +// if (!current || !original) return false; +// return ( +// current.montant !== original.montant || +// current.nombre !== original.nombre || +// !!current.distributed !== !!original.distributed || +// !!current.externe !== !!original.externe +// ); +// } +// } diff --git a/src/app/dashboard/pages/report-courses/report-list.ts b/src/app/dashboard/pages/report-courses/report-list.ts index 7e1d1e3..05ad844 100644 --- a/src/app/dashboard/pages/report-courses/report-list.ts +++ b/src/app/dashboard/pages/report-courses/report-list.ts @@ -1,61 +1,61 @@ -import { CommonModule } from '@angular/common'; -import { ChangeDetectionStrategy, Component, signal, effect, untracked } from '@angular/core'; -import { DataTable, SortState, TableColumn } from '@shared/components/data-table/data-table'; -import { Paginator } from '@shared/components/paginator/paginator'; -import { SearchBar } from '@shared/components/search-bar/search-bar'; -import { ZardButtonComponent } from '@shared/components/button/button.component'; -import { SortDir } from '@shared/paging/paging'; -import { Router } from '@angular/router'; -import { CourseReportSummary } from 'src/app/core/interfaces/report'; -import { ReportService } from 'src/app/core/services/report'; +// import { CommonModule } from '@angular/common'; +// import { ChangeDetectionStrategy, Component, signal, effect, untracked } from '@angular/core'; +// import { DataTable, SortState, TableColumn } from '@shared/components/data-table/data-table'; +// import { Paginator } from '@shared/components/paginator/paginator'; +// import { SearchBar } from '@shared/components/search-bar/search-bar'; +// import { ZardButtonComponent } from '@shared/components/button/button.component'; +// import { SortDir } from '@shared/paging/paging'; +// import { Router } from '@angular/router'; +// import { CourseReportSummary } from 'src/app/core/interfaces/report'; +// import { ReportService } from 'src/app/core/services/report'; -@Component({ - standalone: true, - selector: 'app-report-courses-list', - templateUrl: './report-list.html', - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [CommonModule, DataTable, Paginator, SearchBar, ZardButtonComponent], -}) -export class ReportCoursesListPage { - rows = signal([]); - total = signal(0); - page = signal(1); - perPage = signal(10); - search = signal(''); - sort = signal({ key: 'date', dir: 'desc' }); - loading = signal(false); +// @Component({ +// standalone: true, +// selector: 'app-report-courses-list', +// templateUrl: './report-list.html', +// changeDetection: ChangeDetectionStrategy.OnPush, +// imports: [CommonModule, DataTable, Paginator, SearchBar, ZardButtonComponent], +// }) +// export class ReportCoursesListPage { +// rows = signal([]); +// total = signal(0); +// page = signal(1); +// perPage = signal(10); +// search = signal(''); +// sort = signal({ key: 'date', dir: 'desc' }); +// loading = signal(false); - cols: TableColumn[] = [ - { key: 'course.dateDepartCourse', label: 'Date', sortable: true }, - { key: 'course.numero', label: 'Numéro', sortable: true }, - { key: 'course.nom', label: 'Nom', sortable: true }, - { key: 'course.type', label: 'Type', sortable: true }, - { key: 'course.reunion.hippodrome.nom', label: 'Lieu', sortable: true }, - { key: 'course.particularite', label: 'Particularité' }, - { key: 'statut', label: 'Statut', sortable: true }, - ]; +// cols: TableColumn[] = [ +// { key: 'course.dateDepartCourse', label: 'Date', sortable: true }, +// { key: 'course.numero', label: 'Numéro', sortable: true }, +// { key: 'course.nom', label: 'Nom', sortable: true }, +// { key: 'course.type', label: 'Type', sortable: true }, +// { key: 'course.reunion.hippodrome.nom', label: 'Lieu', sortable: true }, +// { key: 'course.particularite', label: 'Particularité' }, +// { key: 'statut', label: 'Statut', sortable: true }, +// ]; - constructor(private api: ReportService, private router: Router) { - effect(() => { - const params = { page: this.page(), perPage: this.perPage(), search: this.search(), sortKey: this.sort().key, sortDir: this.sort().dir as SortDir }; - untracked(() => this.fetch(params)); - }); - } +// constructor(private api: ReportService, private router: Router) { +// effect(() => { +// const params = { page: this.page(), perPage: this.perPage(), search: this.search(), sortKey: this.sort().key, sortDir: this.sort().dir as SortDir }; +// untracked(() => this.fetch(params)); +// }); +// } - fetch(params: { page: number; perPage: number; search: string; sortKey: string; sortDir: SortDir }) { - this.loading.set(true); - this.api.list(params).subscribe((res) => { - this.rows.set(res.data); - this.total.set(res.meta.total); - this.loading.set(false); - }); - } +// fetch(params: { page: number; perPage: number; search: string; sortKey: string; sortDir: SortDir }) { +// this.loading.set(true); +// this.api.list(params).subscribe((res) => { +// this.rows.set(res.content); +// this.total.set(res.pageable.total); +// this.loading.set(false); +// }); +// } - onSearch(q: string) { this.search.set(q); this.page.set(1); } +// onSearch(q: string) { this.search.set(q); this.page.set(1); } - open(row: CourseReportSummary) { - this.router.navigate(['/rapport-courses', row.id]); - } -} +// open(row: CourseReportSummary) { +// this.router.navigate(['/rapport-courses', row.id]); +// } +// } diff --git a/src/app/dashboard/pages/reunion/reunion.ts b/src/app/dashboard/pages/reunion/reunion.ts index bc7a3c9..ce56d27 100644 --- a/src/app/dashboard/pages/reunion/reunion.ts +++ b/src/app/dashboard/pages/reunion/reunion.ts @@ -153,13 +153,13 @@ export class ReunionList { this.loading.set(true); this.api.list(params).subscribe({ next: (res) => { - this.rows.set(res.data); - this.total.set(res.meta.total); - const meta = res.meta; + this.rows.set(res.content); + this.total.set(res.pageable.total); + const meta = res.pageable; - this.upcomingReunions.set(res.meta['upcomingReunions'] ?? 0); - this.pastReunions.set(res.meta['pastReunions'] ?? 0); - this.uniqueHippodromes.set(res.meta['uniqueHippodromes'] ?? 0); + this.upcomingReunions.set(0); + this.pastReunions.set(0); + this.uniqueHippodromes.set(0); this.loading.set(false); }, error: () => { diff --git a/src/app/dashboard/pages/roles/roles.ts b/src/app/dashboard/pages/roles/roles.ts index e1487c4..1b4c2c3 100644 --- a/src/app/dashboard/pages/roles/roles.ts +++ b/src/app/dashboard/pages/roles/roles.ts @@ -84,8 +84,8 @@ export class RolesPage { this.loading.set(true); this.api.list(params).subscribe({ next: (res) => { - this.rows.set(res.data); - this.total.set(res.meta.total); + this.rows.set(res.content); + this.total.set(res.pageable.total); this.loading.set(false); }, error: () => { diff --git a/src/app/dashboard/pages/tpe/tpe.ts b/src/app/dashboard/pages/tpe/tpe.ts index cf80d36..135e380 100644 --- a/src/app/dashboard/pages/tpe/tpe.ts +++ b/src/app/dashboard/pages/tpe/tpe.ts @@ -191,8 +191,8 @@ export class TpePage implements OnInit { this.total.set(res.length); } else { // List returns PagedResult - this.rows.set(res.data); - this.total.set(res.meta.total); + this.rows.set(res.content); + this.total.set(res.pageable.total); } this.loading.set(false); }, @@ -281,8 +281,8 @@ export class TpePage implements OnInit { // Normal list with pagination this.api.list(params).subscribe({ next: (res) => { - this.rows.set(res.data); - this.total.set(res.meta.total); + this.rows.set(res.content); + this.total.set(res.pageable.total); this.loading.set(false); }, error: () => { diff --git a/src/app/dashboard/pages/users/users.ts b/src/app/dashboard/pages/users/users.ts index 25f6250..ba5cecf 100644 --- a/src/app/dashboard/pages/users/users.ts +++ b/src/app/dashboard/pages/users/users.ts @@ -79,7 +79,7 @@ export class UsersPage { constructor(private api: UserService, private roleService: RoleService) { this.roleService .list({ page: 1, perPage: 100, search: '', sortKey: 'name', sortDir: 'asc' } as any) - .subscribe((res) => (res.data as Role[]).forEach((r) => this.roleMap.set(r.id, r.name))); + .subscribe((res) => (res.content as Role[]).forEach((r) => this.roleMap.set(r.id, r.name))); effect(() => { const params = { page: this.page(), @@ -102,8 +102,8 @@ export class UsersPage { this.loading.set(true); this.api.list(params).subscribe({ next: (res) => { - this.rows.set(res.data); - this.total.set(res.meta.total); + this.rows.set(res.content); + this.total.set(res.pageable.total); this.loading.set(false); }, error: () => { diff --git a/src/app/shared/forms/agent-form/agent-form.ts b/src/app/shared/forms/agent-form/agent-form.ts index 4a8cdb1..781414f 100644 --- a/src/app/shared/forms/agent-form/agent-form.ts +++ b/src/app/shared/forms/agent-form/agent-form.ts @@ -56,7 +56,7 @@ export class AgentForm { this.limitService .list({ page: 1, perPage: 100, search: '', sortKey: 'nom', sortDir: 'asc' } as any) - .subscribe((res) => (this.limits = res.data)); + .subscribe((res) => (this.limits = res.content)); } error(control: string): string { diff --git a/src/app/shared/forms/agent-full-form/agent-full-form.ts b/src/app/shared/forms/agent-full-form/agent-full-form.ts index c705921..fe37d48 100644 --- a/src/app/shared/forms/agent-full-form/agent-full-form.ts +++ b/src/app/shared/forms/agent-full-form/agent-full-form.ts @@ -90,7 +90,7 @@ export class AgentFullForm { }); this.limitService.list({ page: 1, perPage: 100, search: '', sortKey: 'nom', sortDir: 'asc' } as any).subscribe((res) => { - this.limits = res.data; + this.limits = res.content; // Find default limit const defaultLimit = this.limits.find((l) => l.isDefault); @@ -256,7 +256,7 @@ export class AgentFullForm { .subscribe((res) => { // Only show VALIDE TPEs that are either not assigned or assigned to this agent const currentAgentId = this.value?.id; - this.tpeRows = res.data.filter((t) => { + this.tpeRows = res.content.filter((t) => { if (t.statut !== 'VALIDE') return false; // If TPE is assigned but to this agent, show it if (t.assigne && currentAgentId) { diff --git a/src/app/shared/forms/course-form/course-form.html b/src/app/shared/forms/course-form/course-form.html index 5e091be..487bb01 100644 --- a/src/app/shared/forms/course-form/course-form.html +++ b/src/app/shared/forms/course-form/course-form.html @@ -19,26 +19,28 @@
+ + + + @if (loadingHippodromes()) { + Chargement des Hippodromes... + } @else { @for (r of filteredHippodromes(); track r.id) { + + {{ r.nom }} - ({{ r.ville }}) + + } } + + + + - - - @for (t of courseTypes; track t.value) { - {{ t.label }} - } - - - @if (isInvalid('type')) { -

- {{ errorMessage('type') }} -

- } -
- - - Numero de la reunion - @if (isInvalid('numero')) { + @if (isInvalid('reunionNumero')) {

- {{ errorMessage('numero') }} + {{ errorMessage('reunionNumero') }}

}
+ + + + + @if (isInvalid('reunionDate')) { +

+ {{ errorMessage('reunionDate') }} +

+ } +
+
+ + + + + + @if (isInvalid('discipline')) { +

+ {{ errorMessage('discipline') }} +

+ } +
-
- @if(isInvalid('dateDepartCourse')) { + @if(isInvalid('reunionDate') || isInvalid('heureDepartPrevue')) {

- {{ errorMessage('dateDepartCourse') }} + {{ errorMessage('reunionDate') || errorMessage('heureDepartPrevue') }}

}
- -
- - -
- - -
-
- - - -
- - -
-
-
@@ -197,29 +176,6 @@

- - - - - - @if (loadingReunions()) { - Chargement des réunions... - } @else { @for (r of filteredReunions(); track r.id) { - - {{ r.nom }} – {{ r.hippodrome.nom }} ({{ r.hippodrome.ville }}) - - } } - - - @@ -239,15 +195,18 @@
Types de paris ouverts (CSV) - + + @for (t of courseTypes; track t.value) { + {{ t.label }} + } + +

Séparez les types par des virgules.

@@ -257,14 +216,14 @@ > @@ -279,7 +238,7 @@ z-input type="number" min="1" - formControlName="distance" + formControlName="distanceMetres" class="w-full px-4 py-2.5 rounded-lg border border-gray-300 dark:border-gray-600 focus:ring-2 focus:ring-orange-500 dark:bg-gray-800 dark:text-gray-100" /> @@ -287,13 +246,13 @@ Catégorie @@ -330,14 +289,14 @@ N° de la course diff --git a/src/app/shared/forms/course-form/course-form.ts b/src/app/shared/forms/course-form/course-form.ts index fd4a291..ceb4119 100644 --- a/src/app/shared/forms/course-form/course-form.ts +++ b/src/app/shared/forms/course-form/course-form.ts @@ -24,11 +24,13 @@ import { ZardFormModule } from '@shared/components/form/form.module'; import { ZardInputDirective } from '@shared/components/input/input.directive'; import { ZardSelectItemComponent } from '@shared/components/select/select-item.component'; import { ZardSelectComponent } from '@shared/components/select/select.component'; -import { Course, CourseStatut, CourseType } from 'src/app/core/interfaces/course'; -import { Reunion } from 'src/app/core/interfaces/reunion'; -import { ReunionService } from 'src/app/core/services/reunion'; import { Subscription } from 'rxjs'; +import { Hippodrome } from 'src/app/core/interfaces/hippodrome'; +import { Course, CourseStatut, CourseType } from 'src/app/core/interfaces/course'; +import { HippodromeService } from 'src/app/core/services/hippodrome'; +import { CourseApiResponse, CourseService } from 'src/app/core/services/course'; + @Component({ selector: 'app-course-form', standalone: true, @@ -38,6 +40,7 @@ import { Subscription } from 'rxjs'; CommonModule, ReactiveFormsModule, ZardFormModule, + ZardInputDirective, ZardSelectComponent, ZardSelectItemComponent, @@ -45,403 +48,233 @@ import { Subscription } from 'rxjs'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class CourseForm implements OnInit, AfterViewInit, OnDestroy { - @Output() save = new EventEmitter(); + @Output() save = new EventEmitter>(); @Output() cancel = new EventEmitter(); - private _value?: Course; - @Input() set value(v: Course | undefined) { - this._value = v; - } - get value(): Course | undefined { - return this._value; - } + @Input() value?: Course; form: FormGroup; submitted = false; - reunions = signal([]); - loadingReunions = signal(false); - searchQuery = signal(''); - selectedReunionLabel = signal(''); - initializing = signal(false); + hippodromes = signal([]); + loadingHippodromes = signal(false); + searchQuery = signal(''); + selectedHippodromeLabel = signal(''); + private subs = new Subscription(); constructor( private fb: FormBuilder, - private reunionService: ReunionService, + private courseServive: CourseService, + private hippodromeService: HippodromeService, private cdr: ChangeDetectorRef ) { this.form = this.fb.group({ - type: ['', Validators.required], - numero: [null, [Validators.required, Validators.min(1)]], - nom: ['', [Validators.required, Validators.minLength(3)]], - dateDepartCourse: ['', Validators.required], - dateDebutParis: ['', Validators.required], - dateFinParis: ['', Validators.required], - reunionId: new FormControl(null, Validators.required), - reunionCourse: [1], - particularite: [''], - partants: [null, [Validators.required, Validators.min(1)]], - distance: [''], - condition: [''], - statut: [CourseStatut.PROGRAMMEE, Validators.required], + hippodromeId: new FormControl(null, Validators.required), + reunionNumero: [null, Validators.required], + reunionDate: ['', Validators.required], + nom: ['', Validators.required], + numero: [null, Validators.required], + statut: [CourseStatut.BROUILLON, Validators.required], + heureDepartPrevu: ['', Validators.required], + discipline: ['', Validators.required], + distanceMetres: [null, Validators.required], + nombrePartants: [null, Validators.required], + annulee: [false], + categorie: ['', Validators.required], + reporteeMemeJour: [false], + reporteeAutreJour: [false], + incidentTechnique: [false], + nonPartants: [[]], + typesParisOuverts: [''], createdBy: ['agent-001'], validatedBy: [''], }); - // Watch for reunionId changes to update the label - // This handles BOTH create and edit modes when user selects a reunion - this.subs.add( - this.form.get('reunionId')?.valueChanges.subscribe((reunionId) => { - // Skip if we're initializing (to avoid interfering with form initialization) - if (this.initializing()) { - return; - } - - // Handle empty/null values - if (!reunionId || reunionId === '') { - this.selectedReunionLabel.set(''); - return; - } - - // Update label when user selects a reunion (both create and edit) - if (this.reunions().length > 0) { - // Try both string and direct comparison since form control might convert types - const matchingReunion = this.reunions().find( - (r) => String(r.id) === String(reunionId) || r.id === reunionId - ); - if (matchingReunion) { - const reunionLabel = `${matchingReunion.nom} – ${matchingReunion.hippodrome.nom} (${matchingReunion.hippodrome.ville})`; - this.selectedReunionLabel.set(reunionLabel); - // Force change detection to ensure the label is displayed immediately - this.cdr.markForCheck(); - } else { - this.selectedReunionLabel.set(''); - } - } else { - this.selectedReunionLabel.set(''); - } - }) || new Subscription() - ); - - // Effect to handle form initialization when value or reunions change + /* ============================= + FIX 1 — Gestion enable/disable + (cause principale du select bloqué) + ============================== */ effect(() => { - const v = this.value; - const reunionsList = this.reunions(); - const isLoading = this.loadingReunions(); + const loading = this.loadingHippodromes(); + const control = this.form.get('hippodromeId'); - // CRITICAL: Enable/disable reunion control based on loading state - // This is the most important part for create mode to work - const reunionControl = this.form.get('reunionId'); - if (isLoading) { - reunionControl?.disable({ emitEvent: false }); + if (loading) { + control?.disable({ emitEvent: false }); } else { - reunionControl?.enable({ emitEvent: false }); - // Force change detection after enabling to ensure the select is clickable + control?.enable({ emitEvent: false }); this.cdr.markForCheck(); } - // Handle edit mode (value is defined) - populate form with course data - if (v !== null && v !== undefined) { - this.initializing.set(true); + // Edit mode : appliquer la valeur APRÈS chargement + if (this.value && this.hippodromes().length) { + const id = String(this.value.hippodrome?.id ?? ''); + if (id) { + this.form.patchValue({ hippodromeId: id }, { emitEvent: false }); - let reunionId: string | null = null; - let reunionLabel: string = ''; - if (v.reunion?.id) { - reunionId = String(v.reunion.id); - if (reunionsList.length > 0) { - const matchingReunion = reunionsList.find((r) => String(r.id) === reunionId); - if (matchingReunion) { - reunionLabel = `${matchingReunion.nom} – ${matchingReunion.hippodrome.nom} (${matchingReunion.hippodrome.ville})`; - this.selectedReunionLabel.set(reunionLabel); - } else { - reunionId = null; - this.selectedReunionLabel.set(''); - } - } else { - if (v.reunion.nom) { - reunionLabel = `${v.reunion.nom} – ${v.reunion.hippodrome?.nom || ''} (${ - v.reunion.hippodrome?.ville || '' - })`; - this.selectedReunionLabel.set(reunionLabel); - } - reunionId = null; - } - } else { - this.selectedReunionLabel.set(''); - } - - this.form.patchValue( - { - type: v.type ?? '', - numero: v.numero ?? null, - nom: v.nom ?? '', - dateDepartCourse: v.dateDepartCourse ?? '', - dateDebutParis: v.dateDebutParis ?? v.dateDepartCourse ?? '', - dateFinParis: v.dateFinParis ?? v.dateDepartCourse ?? '', - reunionId, - reunionCourse: (v as any).reunionCourse ?? v.numero ?? 1, - particularite: (v as any).particularite ?? '', - partants: (v as any).partants ?? null, - distance: (v as any).distance ?? null, - condition: (v as any).condition ?? '', - statut: v.statut ?? CourseStatut.PROGRAMMEE, - createdBy: (v as any).createdBy ?? 'agent-001', - validatedBy: (v as any).validatedBy ?? '', - }, - { emitEvent: false } - ); - - this.form.markAsPristine(); - this.form.markAsUntouched(); - queueMicrotask(() => this.initializing.set(false)); - } - // Create mode (v === null or undefined) - DO NOTHING except ensure control is enabled - // The form already has default values, just let the user fill it - // The valueChanges subscription will handle label updates when user selects - - // When reunions finish loading - re-apply value if we have one (for edit mode) - else if (reunionsList.length > 0 && !isLoading) { - // Reunions just finished loading - re-apply value if we have one - const currentValue = this.value; - if (currentValue?.reunion?.id) { - const reunionId = String(currentValue.reunion.id); - const matchingReunion = reunionsList.find((r) => String(r.id) === reunionId); - if (matchingReunion) { - // Set the label manually - const reunionLabel = `${matchingReunion.nom} – ${matchingReunion.hippodrome.nom} (${matchingReunion.hippodrome.ville})`; - this.selectedReunionLabel.set(reunionLabel); - // Wait for Angular to render the select items - setTimeout(() => { - this.form.patchValue({ reunionId }, { emitEvent: false }); - this.cdr.detectChanges(); - }, 50); + const h = this.hippodromes().find(r => String(r.id) === id); + if (h) { + this.selectedHippodromeLabel.set(`${h.nom} – (${h.ville})`); } } } }); - } - private hydrateFromValue(v?: Course) { - if (v) { - const patch = { - type: v.type ?? '', - numero: v.numero ?? null, - nom: v.nom ?? '', - dateDepartCourse: v.dateDepartCourse ?? '', - dateDebutParis: v.dateDebutParis ?? v.dateDepartCourse ?? '', - dateFinParis: v.dateFinParis ?? v.dateDepartCourse ?? '', - reunionId: (v as any).reunionId ?? v.reunion?.id ?? '', - reunionCourse: (v as any).reunionCourse ?? v.numero ?? 1, - particularite: (v as any).particularite ?? '', - partants: (v as any).partants ?? null, - distance: (v as any).distance ?? null, - condition: (v as any).condition ?? '', - statut: v.statut ?? CourseStatut.PROGRAMMEE, - createdBy: (v as any).createdBy ?? 'agent-001', - validatedBy: (v as any).validatedBy ?? '', - }; - this.form.reset(patch); - this.form.markAsPristine(); - this.form.markAsUntouched(); - } else { - this.form.reset({ - type: '', - numero: null, - nom: '', - dateDepartCourse: '', - dateDebutParis: '', - dateFinParis: '', - reunionId: '', - reunionCourse: 1, - particularite: '', - partants: null, - distance: null, - condition: '', - statut: CourseStatut.PROGRAMMEE, - createdBy: 'agent-001', - validatedBy: '', - }); - this.form.markAsPristine(); - this.form.markAsUntouched(); - } - } - - isInvalid(control: string): boolean { - const ctrl = this.form.get(control); - return !!(ctrl && ctrl.invalid && (ctrl.touched || this.submitted)); - } - - errorMessage(control: string): string | null { - const c = this.form.get(control); - if (!c || !c.errors) return null; - if (c.errors['required']) return 'Ce champ est obligatoire'; - if (c.errors['minlength']) return `Minimum ${c.errors['minlength'].requiredLength} caractères`; - if (c.errors['min']) return `Valeur minimale : ${c.errors['min'].min}`; - return null; - } - - getDatePart(control: string): string { - const val = this.form.get(control)?.value; - return val ? new Date(val).toISOString().slice(0, 10) : ''; - } - - getTimePart(control: string): string { - const val = this.form.get(control)?.value; - if (!val) return ''; - const d = new Date(val); - return d.toISOString().slice(11, 16); - } - - setDatePart(control: string, date: string) { - const current = new Date(this.form.get(control)?.value || new Date()); - const [h, m] = [current.getHours(), current.getMinutes()]; - const newDate = new Date( - `${date}T${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:00Z` - ); - this.form.patchValue({ [control]: newDate.toISOString() }); - } - - setTimePart(control: string, time: string) { - const current = new Date(this.form.get(control)?.value || new Date()); - const [h, m] = time.split(':').map(Number); - current.setHours(h, m); - this.form.patchValue({ [control]: current.toISOString() }); - } - - onSubmit() { - this.submitted = true; - if (!this.form.valid) { - this.form.markAllAsTouched(); - return; - } - - const raw = this.form.getRawValue() as any; - - // Find the reunion object from the reunions list - const reunionId = raw.reunionId; - const reunion = this.reunions().find((r) => String(r.id) === String(reunionId)); - - if (!reunion) { - console.error('Reunion not found:', reunionId); - return; - } - - const payload: Partial = { - ...(this.value ?? {}), - type: raw.type, - numero: +raw.numero, - nom: raw.nom, - dateDepartCourse: raw.dateDepartCourse, - dateDebutParis: raw.dateDebutParis, - dateFinParis: raw.dateFinParis, - reunion: reunion!, // Will be set if reunion is found - reunionCourse: +raw.reunionCourse, - particularite: raw.particularite ?? '', - partants: +raw.partants, - distance: +raw.distance, - condition: raw.condition ?? '', - statut: raw.statut, - createdBy: raw.createdBy, - validatedBy: raw.validatedBy || null, - }; - - // Ensure reunion is found - if (!reunion) { - console.error('Reunion not found:', reunionId); - return; - } - - this.save.emit(payload as Course); - } - - // === Filter Reunions === - filteredReunions = computed(() => { - const q = this.searchQuery().toLowerCase(); - return this.reunions().filter( - (r) => - r.nom.toLowerCase().includes(q) || - r.hippodrome.nom.toLowerCase().includes(q) || - r.hippodrome.ville.toLowerCase().includes(q) - ); - }); - - courseTypes = [ - { label: 'Tiercé', value: CourseType.TIERCE }, - { label: 'Quarté + Tiercé', value: CourseType.QUARTE }, - { label: 'Quinté + Tiercé', value: CourseType.QUINTE }, - ]; - - courseStatus = [ - { label: 'Programmée', value: CourseStatut.PROGRAMMEE }, - { label: 'Créée', value: CourseStatut.CREATED }, - { label: 'Validée', value: CourseStatut.VALIDATED }, - { label: 'En cours', value: CourseStatut.RUNNING }, - { label: 'Clôturée', value: CourseStatut.CLOSED }, - { label: 'Annulée', value: CourseStatut.CANCELED }, - ]; - - ngOnInit() { - // Fetch reunions from API - this.loadingReunions.set(true); + /* ============================= + FIX 2 — valueChanges propre + ============================== */ this.subs.add( - this.reunionService.list({ page: 1, perPage: 1000 }, false).subscribe({ - next: (result) => { - this.reunions.set(result.data); - this.loadingReunions.set(false); - // Force enable the control after loading - setTimeout(() => { - const reunionControl = this.form.get('reunionId'); - reunionControl?.enable({ emitEvent: false }); - this.cdr.markForCheck(); - }, 100); - }, - error: (err) => { - console.error('Error loading reunions:', err); - this.loadingReunions.set(false); - }, + this.form.get('hippodromeId')!.valueChanges.subscribe((id) => { + if (!id) { + this.selectedHippodromeLabel.set(''); + return; + } + + const h = this.hippodromes().find(r => String(r.id) === String(id)); + if (h) { + this.selectedHippodromeLabel.set(`${h.nom} – (${h.ville})`); + this.cdr.markForCheck(); + } }) ); } - ngAfterViewInit() { - // After view is initialized, ensure the reunion value and label are set correctly - const currentValue = this.value; - if (currentValue?.reunion?.id && this.reunions().length > 0) { - const reunionId = String(currentValue.reunion.id); - const matchingReunion = this.reunions().find((r) => String(r.id) === reunionId); - if (matchingReunion) { - // Set the label manually - const reunionLabel = `${matchingReunion.nom} – ${matchingReunion.hippodrome.nom} (${matchingReunion.hippodrome.ville})`; - this.selectedReunionLabel.set(reunionLabel); - // Wait a bit for the select items to be fully rendered - setTimeout(() => { - const currentFormValue = this.form.get('reunionId')?.value; - if (String(currentFormValue) !== reunionId) { - this.form.patchValue({ reunionId }, { emitEvent: false }); - this.cdr.detectChanges(); - } - }, 100); - } - } + /* ============================= + FIX 3 — vrai filter + ============================== */ + filteredHippodromes = computed(() => { + const q = this.searchQuery().toLowerCase(); + if (!q) return this.hippodromes(); + + return this.hippodromes().filter(h => + h.nom.toLowerCase().includes(q) || + h.ville.toLowerCase().includes(q) || + (h as any).pays?.toLowerCase().includes(q) + ); + }); + + ngOnInit() { + this.loadingHippodromes.set(true); + + this.subs.add( + this.hippodromeService + .list({ page: 1, perPage: 1000 }, true) + .subscribe({ + next: (res) => { + this.hippodromes.set(res.content); + this.loadingHippodromes.set(false); + }, + error: () => { + this.loadingHippodromes.set(false); + }, + }) + ); } - onReunionSelectionChange(value: string) { - // Immediately update the label when user selects a reunion - if (value && this.reunions().length > 0) { - const matchingReunion = this.reunions().find( - (r) => String(r.id) === String(value) || r.id === value - ); - if (matchingReunion) { - const reunionLabel = `${matchingReunion.nom} – ${matchingReunion.hippodrome.nom} (${matchingReunion.hippodrome.ville})`; - this.selectedReunionLabel.set(reunionLabel); - this.cdr.markForCheck(); - } else { - } - } + ngAfterViewInit() { + this.cdr.markForCheck(); } + isInvalid(control: string): boolean { + const c = this.form.get(control); + return !!(c && c.invalid && (c.touched || this.submitted)); + } + +onSubmit() { + this.submitted = true; + + if (this.form.invalid) { + this.form.markAllAsTouched(); + return; + } + + const raw = this.form.getRawValue() as any; + + // 1️⃣ Résoudre l'hippodrome sélectionné + const hippodromeId = raw.hippodromeId; + const foundHippodrome = this.hippodromes().find(h => String(h.id) === String(hippodromeId)); + const hippodromeObj = foundHippodrome ?? (hippodromeId ? { id: +hippodromeId } : undefined); + + + // 2️⃣ Transformer typesParisOuverts CSV → tableau + const typesParis = raw.typesParisOuverts + ? raw.typesParisOuverts.split(',').map((s: string) => s.trim()).filter(Boolean) + : []; + + // 3️⃣ Construire payload + const payload: Partial = { + ...this.value, // inclut les champs existants si édition + hippodromeId: raw.hippodromeId, + reunionNumero: raw.reunionNumero ? +raw.reunionNumero : undefined, + reunionDate: raw.reunionDate, + nom: raw.nom, + numero: raw.numero ? +raw.numero : undefined, + statut: raw.statut, + discipline: raw.discipline, + heureDepartPrevue: new Date(raw.reunionDate+raw.heureDepartPrevu).toISOString(), + distanceMetres: raw.distanceMetres ? +raw.distanceMetres : undefined, + nombrePartants: raw.nombrePartants ? +raw.nombrePartants : undefined, + typesParisOuverts: typesParis, + annulee: raw.annulee ?? false, + reporteeMemeJour: raw.reporteeMemeJour ?? false, + reporteeAutreJour: raw.reporteeAutreJour ?? false, + incidentTechnique: raw.incidentTechnique ?? false, + nonPartants: raw.nonPartants ?? [], + categorie: raw.categorie, + }; + + console.log(payload); + // 4️⃣ Appeler le service (create ou update) + if (this.value?.id) { + this.courseServive.update(this.value.id, payload).subscribe({ + next: () => this.save.emit(payload), + error: err => console.error('Erreur update course', err) + }); + } else { + this.courseServive.create(payload).subscribe({ + next: () => this.save.emit(payload), + error: err => console.error(err) + }); + } +} + + + +errorMessage(control: string): string | null { + const c = this.form.get(control); + if (!c || !c.errors) return null; + if (c.errors['required']) return 'Ce champ est obligatoire'; + if (c.errors['minlength']) return `Minimum ${c.errors['minlength'].requiredLength} caractères`; + if (c.errors['min']) return `Valeur minimale : ${c.errors['min'].min}`; + return null; +} + + ngOnDestroy() { this.subs.unsubscribe(); } + + courseTypes = [ + { label: 'Gagnant', value: CourseType.GAGNANT }, + { label: 'Placé', value: CourseType.PLACE }, + { label: 'Jumélé gagnant', value: CourseType.JUMELE_GAGNANT}, + { label: 'Jumélé placé', value: CourseType.JUMELE_PLACE}, + { label: 'Jumélé ordre', value: CourseType.JUMELE_ORDRE}, + { label: 'Trio', value: CourseType.TRIO}, + { label: 'Trio ordre', value: CourseType.TRIO_ORDRE}, + { label: 'Triplet', value: CourseType.TRIPLET}, + { label: 'Quatro', value: CourseType.QUATRO}, + { label: 'Quinte', value: CourseType.QUINTE}, + ]; + + + courseStatus = [ + { label: 'Brouillon', value: CourseStatut.BROUILLON }, + { label: 'Validé', value: CourseStatut.VALIDE }, + { label: 'Fremé', value: CourseStatut.FERME }, + { label: 'Resultat Provisoire', value: CourseStatut.RESULTAT_PROVISOIRE }, + { label: 'Resultat officiel', value: CourseStatut.RESULTAT_OFFICIEL }, + { label: 'Reglée', value: CourseStatut.REGLEE }, + { label: 'Annulée', value: CourseStatut.ANNULEE }, + ]; } diff --git a/src/app/shared/forms/nonpartant-form/nonpartant-form.ts b/src/app/shared/forms/nonpartant-form/nonpartant-form.ts index a516c7f..47c4bca 100644 --- a/src/app/shared/forms/nonpartant-form/nonpartant-form.ts +++ b/src/app/shared/forms/nonpartant-form/nonpartant-form.ts @@ -121,7 +121,7 @@ export class NonPartantForm implements OnInit, OnChanges, OnDestroy { } private seedFromCourse() { - const max = this.course?.partants ?? 0; + const max = this.course?.nombrePartants ?? 0; this.partantsMax.set(max); const arr = new FormArray>([]); diff --git a/src/app/shared/forms/resultat-form/resultat-form.html b/src/app/shared/forms/resultat-form/resultat-form.html index 2289212..085e191 100644 --- a/src/app/shared/forms/resultat-form/resultat-form.html +++ b/src/app/shared/forms/resultat-form/resultat-form.html @@ -4,7 +4,7 @@

{{ course.nom }}

- {{ course.reunion.nom }} • {{ course.reunion.hippodrome.nom }} + {{ course.nom }}

diff --git a/src/app/shared/forms/resultat-form/resultat-form.ts b/src/app/shared/forms/resultat-form/resultat-form.ts index 5afd8f4..d8ad5fd 100644 --- a/src/app/shared/forms/resultat-form/resultat-form.ts +++ b/src/app/shared/forms/resultat-form/resultat-form.ts @@ -35,9 +35,9 @@ export class ResultatForm { }); reqLen = computed(() => - this.course?.type === CourseType.TIERCE ? 3 : this.course?.type === CourseType.QUARTE ? 4 : 5 + 3 ); - maxNum = computed(() => this.course?.partants ?? 0); + maxNum = computed(() => this.course?.nombrePartants ?? 0); npSet = computed(() => new Set(this.course?.nonPartants ?? [])); statut = computed((): 'CREATED' | 'VALIDATED' | 'NONE' => { return this.resultat ? 'CREATED' : 'NONE'; diff --git a/src/app/shared/forms/reunion-form/reunion-form.ts b/src/app/shared/forms/reunion-form/reunion-form.ts index 31bdc73..9b62e34 100644 --- a/src/app/shared/forms/reunion-form/reunion-form.ts +++ b/src/app/shared/forms/reunion-form/reunion-form.ts @@ -229,7 +229,7 @@ export class ReunionForm implements OnInit, AfterViewInit, OnDestroy { this.subs.add( this.hippodromeService.list({ page: 1, perPage: 1000 }, false).subscribe({ next: (result) => { - this.hippodromes.set(result.data); + this.hippodromes.set(result.content); this.loadingHippodromes.set(false); // The effect will automatically re-run when hippodromes signal updates }, diff --git a/src/app/shared/forms/user-form/user-form.ts b/src/app/shared/forms/user-form/user-form.ts index 87b2f8d..a22f107 100644 --- a/src/app/shared/forms/user-form/user-form.ts +++ b/src/app/shared/forms/user-form/user-form.ts @@ -63,7 +63,7 @@ export class UserForm { this.roleService .list({ page: 1, perPage: 100, search: '', sortKey: 'name', sortDir: 'asc' } as any) .subscribe((res) => { - this.roles = res.data as any; + this.roles = res.content as any; }); } diff --git a/src/app/shared/paging/normalize-page.ts b/src/app/shared/paging/normalize-page.ts index f6fa39f..c790ac2 100644 --- a/src/app/shared/paging/normalize-page.ts +++ b/src/app/shared/paging/normalize-page.ts @@ -7,69 +7,34 @@ import { PagedResult, PageMeta } from './paging'; export function normalizePage(raw: any, reqPage: number, perPage: number): PagedResult { // 🟩 Case 1 — Spring Data style: { content, totalElements, number, size } if (raw && Array.isArray(raw.content) && typeof raw.totalElements === 'number') { - return { - data: raw.content as T[], - meta: { - page: (raw.number ?? 0) + 1, - perPage: raw.size ?? perPage, - total: raw.totalElements, - ...(raw.meta ?? {}), // merge extra meta if provided - } as PageMeta, - }; + return raw; } // 🟩 Case 2 — API or local mock: { data, meta: { total, ...extra } } - if (raw && Array.isArray(raw.data) && raw.meta?.total != null) { - return { - data: raw.data as T[], - meta: { - page: raw.meta.page ?? reqPage, - perPage: raw.meta.perPage ?? perPage, - total: raw.meta.total, - ...raw.meta, // keep any custom stats - } as PageMeta, - }; + if (raw && Array.isArray(raw.content) && raw.pageable?.total != null) { + return raw } - // 🟩 Case 3 — Generic REST: { items, total | total_count, ...extra } - if (raw && Array.isArray(raw.items) && (raw.total != null || raw.total_count != null)) { - const total = raw.total ?? raw.total_count; - return { - data: raw.items as T[], - meta: { - page: reqPage, - perPage, - total, - ...raw.meta, // optional - } as PageMeta, - }; - } // 🟩 Case 4 — Direct array (no meta) if (Array.isArray(raw)) { const start = (reqPage - 1) * perPage; const data = (raw as T[]).slice(start, start + perPage); return { - data, - meta: { - page: reqPage, - perPage, - total: (raw as T[]).length, - } as PageMeta, + content: data, + pageable:{ + pageNumber: raw.length/perPage, + pageSize: perPage, + total: raw.length + }, + totalPages: raw.length/perPage, + totalElements: raw.length }; } // 🟩 Fallback — ensure consistency even with minimal info - const data = Array.isArray(raw?.data) ? (raw.data as T[]) : []; + const data = Array.isArray(raw?.content) ? (raw.content as T[]) : []; const total = typeof raw?.total === 'number' ? raw.total : data.length ?? 0; - return { - data, - meta: { - page: raw?.meta?.page ?? reqPage, - perPage: raw?.meta?.perPage ?? perPage, - total, - ...raw?.meta, // merge additional stats safely - } as PageMeta, - }; + return raw; } diff --git a/src/app/shared/paging/paging.ts b/src/app/shared/paging/paging.ts index 3ddf760..75c880e 100644 --- a/src/app/shared/paging/paging.ts +++ b/src/app/shared/paging/paging.ts @@ -11,15 +11,16 @@ export interface ListParams { } export interface PageMeta { - page: number; // 1-based - perPage: number; + pageNumber: number; // 1-based + pageSize: number; total: number; // -1 if unknown - [key: string]: any; } export interface PagedResult { - data: T[]; - meta: PageMeta; + content: T[]; + pageable: PageMeta; + totalPages: number; + totalElements: number; } export interface BackendConfig { diff --git a/src/environments/environment.development.ts b/src/environments/environment.development.ts index 5bd797b..1c5244f 100644 --- a/src/environments/environment.development.ts +++ b/src/environments/environment.development.ts @@ -1,4 +1,4 @@ export const environment = { production: false, - apiBaseUrl: 'https://custody-holding-rogers-less.trycloudflare.com', + apiBaseUrl: 'https://ddd3b90fc1ef.ngrok-free.app', }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 5bd797b..1c5244f 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,4 +1,4 @@ export const environment = { production: false, - apiBaseUrl: 'https://custody-holding-rogers-less.trycloudflare.com', + apiBaseUrl: 'https://ddd3b90fc1ef.ngrok-free.app', };