first step for plr game platform

This commit is contained in:
OnlyPapy98
2025-12-29 13:56:18 +01:00
parent 169b5ca412
commit ed79cae77d
40 changed files with 620 additions and 373 deletions

View File

@@ -1,5 +1,13 @@
import { Course } from './course';
export enum ResultatStatut {
PROVISOIRE,
OFFICIEL,
ANNULE,
EN_ATTENTE
}
export interface Resultat {
id: string;
course: Course;
@@ -8,7 +16,7 @@ export interface Resultat {
* The backend returns an array of strings/numbers (cheval numbers);
* in the UI we normalize them to plain numbers.
*/
ordreArrivee: number[];
ordreArrivee: string;
/**
* Chevaux en dead-heat (ex aequo), represented by their numbers.
*/
@@ -26,33 +34,24 @@ export interface Resultat {
// API response structure (course may be just an ID in some cases)
export interface ResultatApiResponse {
id: string | number;
course: Course | string | number;
/**
* In the raw API this is an array of strings/numbers.
*/
ordreArrivee: (string | number)[];
chevauxDeadHeat: (string | number)[];
totalMises: number;
masseAPartager: number;
prelevementsLegaux: number;
montantRembourse: number;
montantCagnotte: number;
adeadHeat: boolean;
courseId: string | number;
ordreArrivee: string;
courseNom: string;
courseNumero: number;
reunionNumero:number;
hippodromeNom: string;
statut: ResultatStatut;
datePublication?: string;
dateAnnulation?: string;
dateValidation?: string;
createdAt?: string;
updatedAt?: string;
}
// POST payload structure
export interface CreateResultatPayload {
course: {
id: string | number;
};
ordreArrivee: string[];
chevauxDeadHeat?: (string | number)[];
totalMises?: number;
masseAPartager?: number;
prelevementsLegaux?: number;
montantRembourse?: number;
montantCagnotte?: number;
adeadHeat?: boolean;
courseId: number;
statut: ResultatStatut;
ordreArrivee: string;
notes?: string
}

View File

@@ -108,7 +108,7 @@ export class AgentLimitService {
let httpParams = new HttpParams();
if (params) {
if (params.page) httpParams = httpParams.set('page', params.page.toString());
if (params.perPage) httpParams = httpParams.set('perPage', params.perPage.toString());
if (params.size) httpParams = httpParams.set('size', params.size.toString());
if (params.search) httpParams = httpParams.set('search', params.search);
if (params.sortKey) httpParams = httpParams.set('sortKey', params.sortKey);
if (params.sortDir) httpParams = httpParams.set('sortDir', params.sortDir);
@@ -127,7 +127,7 @@ export class AgentLimitService {
return normalizePage<AgentLimit>(
{ data: limits, meta: { total: limits.length } },
params.page || 1,
params.perPage || 10
params.size || 10
);
}
// Otherwise return all as single page
@@ -207,7 +207,7 @@ export class AgentLimitService {
// First, find the previous default limit
return this.list({
page: 1,
perPage: 1000,
size: 1000,
search: '',
sortKey: 'code',
sortDir: 'asc',

View File

@@ -274,7 +274,7 @@ export class AgentService {
let httpParams = new HttpParams();
if (params) {
if (params.page) httpParams = httpParams.set('page', params.page.toString());
if (params.perPage) httpParams = httpParams.set('perPage', params.perPage.toString());
if (params.size) httpParams = httpParams.set('perPage', params.size.toString());
if (params.search) httpParams = httpParams.set('search', params.search);
if (params.sortKey) httpParams = httpParams.set('sortKey', params.sortKey);
if (params.sortDir) httpParams = httpParams.set('sortDir', params.sortDir);
@@ -296,7 +296,7 @@ export class AgentService {
return normalizePage<Agent>(
{ data: agents, meta: { total: agents.length } },
params.page || 1,
params.perPage || 10
params.size || 10
);
}
// Otherwise return all as single page
@@ -445,7 +445,7 @@ export class AgentService {
// Get all agents first
return this.list({
page: 1,
perPage: 10000,
size: 10000,
search: '',
sortKey: 'code',
sortDir: 'asc',

View File

@@ -59,7 +59,7 @@ export class CourseService {
return isNgrok ? { 'ngrok-skip-browser-warning': 'true' } : {};
}
list(params: ListParams, usePaginationEndpoint: boolean = true): Observable<PagedResult<Course>> {
list(params: ListParams): Observable<PagedResult<Course>> {
const coursesList = this.http.get<PagedResult<CourseApiResponse>>(this.apiUrl, {
headers: this.getNgrokHeaders(),
params: this.servivesUtil.getParamsFromModel(params),

View File

@@ -1,6 +1,8 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of, forkJoin } from 'rxjs';
import { PaginatedHttpService } from '@shared/paging/paginated-http.service';
import { ListParams, PagedResult } from '@shared/paging/paging';
import { map, catchError, switchMap } from 'rxjs/operators';
import { Resultat, ResultatApiResponse, CreateResultatPayload } from '../interfaces/resultat';
import { Course } from '../interfaces/course';
@@ -8,13 +10,20 @@ import { CourseService } from './course';
import { environment } from 'src/environments/environment.development';
const USE_SERVER = true;
const API_BASE = '/api/v1/resultat';
const API_BASE = '/api/resultats';
@Injectable({ providedIn: 'root' })
export class ResultatService {
private apiUrl = environment.apiBaseUrl + API_BASE;
constructor(private http: HttpClient, private courseService: CourseService) {}
constructor(private http: HttpClient, private courseService: CourseService, private pager: PaginatedHttpService) {}
// Fetch raw paginated resultats from the backend and normalize paging
listRawPaged(params: ListParams): Observable<PagedResult<ResultatApiResponse>> {
const url = this.apiUrl;
// Delegate to shared paginated HTTP helper (Spring-style defaults)
return this.pager.fetch<ResultatApiResponse>(url, params);
}
private getNgrokHeaders(): Record<string, string> {
const isNgrok =
@@ -31,11 +40,8 @@ export class ResultatService {
.get<ResultatApiResponse>(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() })
.pipe(
switchMap((apiResultat) => {
// Fetch the full course object if course is just an ID
const courseId =
typeof apiResultat.course === 'object' && 'id' in apiResultat.course
? String(apiResultat.course.id)
: String(apiResultat.course);
// New API uses `courseId` explicitly
const courseId = String((apiResultat as any).courseId ?? '');
return this.courseService.getById(courseId).pipe(
map((course) => {
@@ -62,16 +68,8 @@ export class ResultatService {
.get<ResultatApiResponse[]>(this.apiUrl, { headers: this.getNgrokHeaders() })
.pipe(
switchMap((apiResultats) => {
// Fetch all unique course IDs
const courseIds = [
...new Set(
apiResultats.map((r) =>
typeof r.course === 'object' && 'id' in r.course
? String(r.course.id)
: String(r.course)
)
),
];
// Fetch all unique course IDs (API uses courseId)
const courseIds = [...new Set(apiResultats.map((r) => String((r as any).courseId ?? '')))].filter(Boolean);
// Fetch all courses in parallel
const courseRequests = courseIds.map((id) =>
this.courseService
@@ -91,10 +89,7 @@ export class ResultatService {
return apiResultats
.map((apiResultat) => {
const courseId =
typeof apiResultat.course === 'object' && 'id' in apiResultat.course
? String(apiResultat.course.id)
: String(apiResultat.course);
const courseId = String((apiResultat as any).courseId ?? '');
const course = courseMap.get(courseId);
if (!course) {
return null;
@@ -118,11 +113,23 @@ export class ResultatService {
return of([]);
}
// GET raw API responses (ResultatApiResponse[])
listRaw(): Observable<ResultatApiResponse[]> {
if (USE_SERVER) {
return this.http
.get<ResultatApiResponse[]>(this.apiUrl, { headers: this.getNgrokHeaders() })
.pipe(
catchError((err) => {
console.error('Error fetching raw resultats:', err);
return of([] as ResultatApiResponse[]);
})
);
}
return of([] as ResultatApiResponse[]);
}
// GET /api/v1/resultat/course/{courseId}
getByCourseId(courseId: string): Observable<Resultat | undefined> {
if (!USE_SERVER) {
return of(undefined);
}
return this.http
.get<any>(`${this.apiUrl}/course/${courseId}`, {
@@ -169,28 +176,25 @@ export class ResultatService {
// POST /api/v1/resultat
create(payload: CreateResultatPayload): Observable<Resultat> {
if (USE_SERVER) {
return this.http
.post<ResultatApiResponse>(this.apiUrl, payload, { headers: this.getNgrokHeaders() })
.pipe(
switchMap((apiResultat) => {
const courseId = String(payload.course.id);
return this.courseService.getById(courseId).pipe(
map((course) => {
if (!course) {
throw new Error('Course not found');
}
return this.transformApiResponse(apiResultat, course);
})
);
}),
catchError((err) => {
console.error('Error creating resultat:', err);
throw err;
})
);
}
throw new Error('Server mode is required');
return this.http
.post<ResultatApiResponse>(this.apiUrl, payload, { headers: this.getNgrokHeaders() })
.pipe(
switchMap((apiResultat) => {
const courseId = String(payload.courseId);
return this.courseService.getById(courseId).pipe(
map((course) => {
if (!course) {
throw new Error('Course not found');
}
return this.transformApiResponse(apiResultat, course);
})
);
}),
catchError((err) => {
console.error('Error creating resultat:', err);
throw err;
})
);
}
// PUT /api/v1/resultat/{id}
@@ -202,10 +206,7 @@ export class ResultatService {
})
.pipe(
switchMap((apiResultat) => {
const courseId =
typeof apiResultat.course === 'object' && 'id' in apiResultat.course
? String(apiResultat.course.id)
: String(apiResultat.course);
const courseId = String((apiResultat as any).courseId ?? '');
return this.courseService.getById(courseId).pipe(
map((course) => {
@@ -261,20 +262,17 @@ export class ResultatService {
return {
id: String(apiResultat.id),
course,
// Normalize ordreArrivee to an array of cheval numbers
ordreArrivee: (apiResultat.ordreArrivee || [])
.map((v) => (typeof v === 'string' ? Number(v) : v))
.filter((v): v is number => typeof v === 'number' && !Number.isNaN(v)),
// Normalize dead-heat horses to numbers as well
chevauxDeadHeat: (apiResultat.chevauxDeadHeat || [])
.map((v) => (typeof v === 'string' ? Number(v) : v))
.filter((v): v is number => typeof v === 'number' && !Number.isNaN(v)),
totalMises: apiResultat.totalMises,
masseAPartager: apiResultat.masseAPartager,
prelevementsLegaux: apiResultat.prelevementsLegaux,
montantRembourse: apiResultat.montantRembourse,
montantCagnotte: apiResultat.montantCagnotte,
adeadHeat: apiResultat.adeadHeat,
// API now returns 'ordreArrivee' as CSV/string; normalize to number[]
ordreArrivee: apiResultat.ordreArrivee,
// dead-heat not provided by new API shape — default to empty
chevauxDeadHeat: [],
// Financial fields may not be present in new API; default to 0
totalMises: (apiResultat as any).totalMises ?? 0,
masseAPartager: (apiResultat as any).masseAPartager ?? 0,
prelevementsLegaux: (apiResultat as any).prelevementsLegaux ?? 0,
montantRembourse: (apiResultat as any).montantRembourse ?? 0,
montantCagnotte: (apiResultat as any).montantCagnotte ?? 0,
adeadHeat: (apiResultat as any).adeadHeat ?? false,
createdAt: apiResultat.createdAt,
updatedAt: apiResultat.updatedAt,
};

View File

@@ -180,7 +180,7 @@ export class ReunionService {
meta: { total: 0, uniqueHippodromes: 0, upcomingReunions: 0, pastReunions: 0 },
},
params.page,
params.perPage
params.size
)
);
}
@@ -197,7 +197,7 @@ export class ReunionService {
meta: { total: 0, uniqueHippodromes: 0, upcomingReunions: 0, pastReunions: 0 },
},
params.page,
params.perPage
params.size
)
);
}
@@ -273,8 +273,8 @@ export class ReunionService {
// 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 start = (params.page - 1) * params.size;
const pageData = filtered.slice(start, start + params.size);
const upcomingReunions = filtered.filter(
(r) => new Date(r.date) >= new Date()
@@ -288,7 +288,7 @@ export class ReunionService {
meta: { total, uniqueHippodromes, upcomingReunions, pastReunions },
},
params.page,
params.perPage
params.size
);
})
);
@@ -359,8 +359,8 @@ export class ReunionService {
});
}
const start = (params.page - 1) * params.perPage;
const pageData = data.slice(start, start + params.perPage);
const start = (params.page - 1) * params.size;
const pageData = data.slice(start, start + params.size);
const upcomingReunions = data.filter((r) => new Date(r.date) >= new Date()).length;
const pastReunions = data.filter((r) => new Date(r.date) < new Date()).length;
@@ -373,7 +373,7 @@ export class ReunionService {
meta: { total: data.length, uniqueHippodromes, upcomingReunions, pastReunions },
},
params.page,
params.perPage
params.size
)
);
}

View File

@@ -91,7 +91,7 @@ export class RoleService {
private buildParams(params: ListParams): HttpParams {
let httpParams = new HttpParams()
.set('page', String(params.page - 1))
.set('size', String(params.perPage));
.set('size', String(params.size));
if (params.search) {
httpParams = httpParams.set('search', params.search);
}
@@ -117,13 +117,13 @@ export class RoleService {
return normalizePage<Role>(
{ data: roles, meta: { total: roles.length } },
params.page,
params.perPage
params.size
);
}),
catchError((err) => {
console.error('Error fetching roles:', err);
return of(
normalizePage<Role>({ data: [], meta: { total: 0 } }, params.page, params.perPage)
normalizePage<Role>({ data: [], meta: { total: 0 } }, params.page, params.size)
);
})
);
@@ -137,7 +137,7 @@ export class RoleService {
meta: { total: 0 },
},
params.page,
params.perPage
params.size
)
);
}

View File

@@ -8,7 +8,7 @@ export class ServicesUtils{
let httpParams = new HttpParams();
Object.entries(params).forEach(([key, value])=>{
if(params != null && params!=undefined){
httpParams.set(key, String(value))
httpParams = httpParams.set(key, String(value))
}
})
return httpParams;

View File

@@ -211,7 +211,7 @@ export class TpeService {
let httpParams = new HttpParams();
if (params) {
if (params.page) httpParams = httpParams.set('page', params.page.toString());
if (params.perPage) httpParams = httpParams.set('perPage', params.perPage.toString());
if (params.size) httpParams = httpParams.set('perPage', params.size.toString());
if (params.search) httpParams = httpParams.set('search', params.search);
if (params.sortKey) httpParams = httpParams.set('sortKey', params.sortKey);
if (params.sortDir) httpParams = httpParams.set('sortDir', params.sortDir);
@@ -230,7 +230,7 @@ export class TpeService {
return normalizePage<TpeDevice>(
{ data: tpes, meta: { total: tpes.length } },
params.page || 1,
params.perPage || 10
params.size || 10
);
}
// Otherwise return all as single page

View File

@@ -117,23 +117,23 @@ export class UserService {
});
}
const start = (params.page - 1) * params.perPage;
const pageData = data.slice(start, start + params.perPage);
const start = (params.page - 1) * params.size;
const pageData = data.slice(start, start + params.size);
return normalizePage<User>(
{ data: pageData, meta: { total: data.length } },
params.page,
params.perPage
params.size
);
}),
catchError(() =>
of(normalizePage<User>({ data: [], meta: { total: 0 } }, params.page, params.perPage))
of(normalizePage<User>({ data: [], meta: { total: 0 } }, params.page, params.size))
)
);
}
// Fallback should not be used anymore
return of(normalizePage<User>({ data: [], meta: { total: 0 } }, params.page, params.perPage));
return of(normalizePage<User>({ data: [], meta: { total: 0 } }, params.page, params.size));
}
create(payload: Omit<User, 'id'>): Observable<User> {