first step for plr game platform
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
import { Course } from './course';
|
import { Course } from './course';
|
||||||
|
|
||||||
|
|
||||||
|
export enum ResultatStatut {
|
||||||
|
PROVISOIRE,
|
||||||
|
OFFICIEL,
|
||||||
|
ANNULE,
|
||||||
|
EN_ATTENTE
|
||||||
|
}
|
||||||
|
|
||||||
export interface Resultat {
|
export interface Resultat {
|
||||||
id: string;
|
id: string;
|
||||||
course: Course;
|
course: Course;
|
||||||
@@ -8,7 +16,7 @@ export interface Resultat {
|
|||||||
* The backend returns an array of strings/numbers (cheval numbers);
|
* The backend returns an array of strings/numbers (cheval numbers);
|
||||||
* in the UI we normalize them to plain numbers.
|
* in the UI we normalize them to plain numbers.
|
||||||
*/
|
*/
|
||||||
ordreArrivee: number[];
|
ordreArrivee: string;
|
||||||
/**
|
/**
|
||||||
* Chevaux en dead-heat (ex aequo), represented by their numbers.
|
* 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)
|
// API response structure (course may be just an ID in some cases)
|
||||||
export interface ResultatApiResponse {
|
export interface ResultatApiResponse {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
course: Course | string | number;
|
courseId: string | number;
|
||||||
/**
|
ordreArrivee: string;
|
||||||
* In the raw API this is an array of strings/numbers.
|
courseNom: string;
|
||||||
*/
|
courseNumero: number;
|
||||||
ordreArrivee: (string | number)[];
|
reunionNumero:number;
|
||||||
chevauxDeadHeat: (string | number)[];
|
hippodromeNom: string;
|
||||||
totalMises: number;
|
statut: ResultatStatut;
|
||||||
masseAPartager: number;
|
datePublication?: string;
|
||||||
prelevementsLegaux: number;
|
dateAnnulation?: string;
|
||||||
montantRembourse: number;
|
dateValidation?: string;
|
||||||
montantCagnotte: number;
|
|
||||||
adeadHeat: boolean;
|
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST payload structure
|
// POST payload structure
|
||||||
export interface CreateResultatPayload {
|
export interface CreateResultatPayload {
|
||||||
course: {
|
courseId: number;
|
||||||
id: string | number;
|
statut: ResultatStatut;
|
||||||
};
|
ordreArrivee: string;
|
||||||
ordreArrivee: string[];
|
notes?: string
|
||||||
chevauxDeadHeat?: (string | number)[];
|
|
||||||
totalMises?: number;
|
|
||||||
masseAPartager?: number;
|
|
||||||
prelevementsLegaux?: number;
|
|
||||||
montantRembourse?: number;
|
|
||||||
montantCagnotte?: number;
|
|
||||||
adeadHeat?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export class AgentLimitService {
|
|||||||
let httpParams = new HttpParams();
|
let httpParams = new HttpParams();
|
||||||
if (params) {
|
if (params) {
|
||||||
if (params.page) httpParams = httpParams.set('page', params.page.toString());
|
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.search) httpParams = httpParams.set('search', params.search);
|
||||||
if (params.sortKey) httpParams = httpParams.set('sortKey', params.sortKey);
|
if (params.sortKey) httpParams = httpParams.set('sortKey', params.sortKey);
|
||||||
if (params.sortDir) httpParams = httpParams.set('sortDir', params.sortDir);
|
if (params.sortDir) httpParams = httpParams.set('sortDir', params.sortDir);
|
||||||
@@ -127,7 +127,7 @@ export class AgentLimitService {
|
|||||||
return normalizePage<AgentLimit>(
|
return normalizePage<AgentLimit>(
|
||||||
{ data: limits, meta: { total: limits.length } },
|
{ data: limits, meta: { total: limits.length } },
|
||||||
params.page || 1,
|
params.page || 1,
|
||||||
params.perPage || 10
|
params.size || 10
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Otherwise return all as single page
|
// Otherwise return all as single page
|
||||||
@@ -207,7 +207,7 @@ export class AgentLimitService {
|
|||||||
// First, find the previous default limit
|
// First, find the previous default limit
|
||||||
return this.list({
|
return this.list({
|
||||||
page: 1,
|
page: 1,
|
||||||
perPage: 1000,
|
size: 1000,
|
||||||
search: '',
|
search: '',
|
||||||
sortKey: 'code',
|
sortKey: 'code',
|
||||||
sortDir: 'asc',
|
sortDir: 'asc',
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ export class AgentService {
|
|||||||
let httpParams = new HttpParams();
|
let httpParams = new HttpParams();
|
||||||
if (params) {
|
if (params) {
|
||||||
if (params.page) httpParams = httpParams.set('page', params.page.toString());
|
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.search) httpParams = httpParams.set('search', params.search);
|
||||||
if (params.sortKey) httpParams = httpParams.set('sortKey', params.sortKey);
|
if (params.sortKey) httpParams = httpParams.set('sortKey', params.sortKey);
|
||||||
if (params.sortDir) httpParams = httpParams.set('sortDir', params.sortDir);
|
if (params.sortDir) httpParams = httpParams.set('sortDir', params.sortDir);
|
||||||
@@ -296,7 +296,7 @@ export class AgentService {
|
|||||||
return normalizePage<Agent>(
|
return normalizePage<Agent>(
|
||||||
{ data: agents, meta: { total: agents.length } },
|
{ data: agents, meta: { total: agents.length } },
|
||||||
params.page || 1,
|
params.page || 1,
|
||||||
params.perPage || 10
|
params.size || 10
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Otherwise return all as single page
|
// Otherwise return all as single page
|
||||||
@@ -445,7 +445,7 @@ export class AgentService {
|
|||||||
// Get all agents first
|
// Get all agents first
|
||||||
return this.list({
|
return this.list({
|
||||||
page: 1,
|
page: 1,
|
||||||
perPage: 10000,
|
size: 10000,
|
||||||
search: '',
|
search: '',
|
||||||
sortKey: 'code',
|
sortKey: 'code',
|
||||||
sortDir: 'asc',
|
sortDir: 'asc',
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export class CourseService {
|
|||||||
return isNgrok ? { 'ngrok-skip-browser-warning': 'true' } : {};
|
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, {
|
const coursesList = this.http.get<PagedResult<CourseApiResponse>>(this.apiUrl, {
|
||||||
headers: this.getNgrokHeaders(),
|
headers: this.getNgrokHeaders(),
|
||||||
params: this.servivesUtil.getParamsFromModel(params),
|
params: this.servivesUtil.getParamsFromModel(params),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Observable, of, forkJoin } from 'rxjs';
|
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 { map, catchError, switchMap } from 'rxjs/operators';
|
||||||
import { Resultat, ResultatApiResponse, CreateResultatPayload } from '../interfaces/resultat';
|
import { Resultat, ResultatApiResponse, CreateResultatPayload } from '../interfaces/resultat';
|
||||||
import { Course } from '../interfaces/course';
|
import { Course } from '../interfaces/course';
|
||||||
@@ -8,13 +10,20 @@ import { CourseService } from './course';
|
|||||||
import { environment } from 'src/environments/environment.development';
|
import { environment } from 'src/environments/environment.development';
|
||||||
|
|
||||||
const USE_SERVER = true;
|
const USE_SERVER = true;
|
||||||
const API_BASE = '/api/v1/resultat';
|
const API_BASE = '/api/resultats';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ResultatService {
|
export class ResultatService {
|
||||||
private apiUrl = environment.apiBaseUrl + API_BASE;
|
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> {
|
private getNgrokHeaders(): Record<string, string> {
|
||||||
const isNgrok =
|
const isNgrok =
|
||||||
@@ -31,11 +40,8 @@ export class ResultatService {
|
|||||||
.get<ResultatApiResponse>(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() })
|
.get<ResultatApiResponse>(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() })
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((apiResultat) => {
|
switchMap((apiResultat) => {
|
||||||
// Fetch the full course object if course is just an ID
|
// New API uses `courseId` explicitly
|
||||||
const courseId =
|
const courseId = String((apiResultat as any).courseId ?? '');
|
||||||
typeof apiResultat.course === 'object' && 'id' in apiResultat.course
|
|
||||||
? String(apiResultat.course.id)
|
|
||||||
: String(apiResultat.course);
|
|
||||||
|
|
||||||
return this.courseService.getById(courseId).pipe(
|
return this.courseService.getById(courseId).pipe(
|
||||||
map((course) => {
|
map((course) => {
|
||||||
@@ -62,16 +68,8 @@ export class ResultatService {
|
|||||||
.get<ResultatApiResponse[]>(this.apiUrl, { headers: this.getNgrokHeaders() })
|
.get<ResultatApiResponse[]>(this.apiUrl, { headers: this.getNgrokHeaders() })
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((apiResultats) => {
|
switchMap((apiResultats) => {
|
||||||
// Fetch all unique course IDs
|
// Fetch all unique course IDs (API uses courseId)
|
||||||
const courseIds = [
|
const courseIds = [...new Set(apiResultats.map((r) => String((r as any).courseId ?? '')))].filter(Boolean);
|
||||||
...new Set(
|
|
||||||
apiResultats.map((r) =>
|
|
||||||
typeof r.course === 'object' && 'id' in r.course
|
|
||||||
? String(r.course.id)
|
|
||||||
: String(r.course)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
];
|
|
||||||
// Fetch all courses in parallel
|
// Fetch all courses in parallel
|
||||||
const courseRequests = courseIds.map((id) =>
|
const courseRequests = courseIds.map((id) =>
|
||||||
this.courseService
|
this.courseService
|
||||||
@@ -91,10 +89,7 @@ export class ResultatService {
|
|||||||
|
|
||||||
return apiResultats
|
return apiResultats
|
||||||
.map((apiResultat) => {
|
.map((apiResultat) => {
|
||||||
const courseId =
|
const courseId = String((apiResultat as any).courseId ?? '');
|
||||||
typeof apiResultat.course === 'object' && 'id' in apiResultat.course
|
|
||||||
? String(apiResultat.course.id)
|
|
||||||
: String(apiResultat.course);
|
|
||||||
const course = courseMap.get(courseId);
|
const course = courseMap.get(courseId);
|
||||||
if (!course) {
|
if (!course) {
|
||||||
return null;
|
return null;
|
||||||
@@ -118,11 +113,23 @@ export class ResultatService {
|
|||||||
return of([]);
|
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}
|
// GET /api/v1/resultat/course/{courseId}
|
||||||
getByCourseId(courseId: string): Observable<Resultat | undefined> {
|
getByCourseId(courseId: string): Observable<Resultat | undefined> {
|
||||||
if (!USE_SERVER) {
|
|
||||||
return of(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.http
|
return this.http
|
||||||
.get<any>(`${this.apiUrl}/course/${courseId}`, {
|
.get<any>(`${this.apiUrl}/course/${courseId}`, {
|
||||||
@@ -169,28 +176,25 @@ export class ResultatService {
|
|||||||
|
|
||||||
// POST /api/v1/resultat
|
// POST /api/v1/resultat
|
||||||
create(payload: CreateResultatPayload): Observable<Resultat> {
|
create(payload: CreateResultatPayload): Observable<Resultat> {
|
||||||
if (USE_SERVER) {
|
return this.http
|
||||||
return this.http
|
.post<ResultatApiResponse>(this.apiUrl, payload, { headers: this.getNgrokHeaders() })
|
||||||
.post<ResultatApiResponse>(this.apiUrl, payload, { headers: this.getNgrokHeaders() })
|
.pipe(
|
||||||
.pipe(
|
switchMap((apiResultat) => {
|
||||||
switchMap((apiResultat) => {
|
const courseId = String(payload.courseId);
|
||||||
const courseId = String(payload.course.id);
|
return this.courseService.getById(courseId).pipe(
|
||||||
return this.courseService.getById(courseId).pipe(
|
map((course) => {
|
||||||
map((course) => {
|
if (!course) {
|
||||||
if (!course) {
|
throw new Error('Course not found');
|
||||||
throw new Error('Course not found');
|
}
|
||||||
}
|
return this.transformApiResponse(apiResultat, course);
|
||||||
return this.transformApiResponse(apiResultat, course);
|
})
|
||||||
})
|
);
|
||||||
);
|
}),
|
||||||
}),
|
catchError((err) => {
|
||||||
catchError((err) => {
|
console.error('Error creating resultat:', err);
|
||||||
console.error('Error creating resultat:', err);
|
throw err;
|
||||||
throw err;
|
})
|
||||||
})
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
throw new Error('Server mode is required');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT /api/v1/resultat/{id}
|
// PUT /api/v1/resultat/{id}
|
||||||
@@ -202,10 +206,7 @@ export class ResultatService {
|
|||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((apiResultat) => {
|
switchMap((apiResultat) => {
|
||||||
const courseId =
|
const courseId = String((apiResultat as any).courseId ?? '');
|
||||||
typeof apiResultat.course === 'object' && 'id' in apiResultat.course
|
|
||||||
? String(apiResultat.course.id)
|
|
||||||
: String(apiResultat.course);
|
|
||||||
|
|
||||||
return this.courseService.getById(courseId).pipe(
|
return this.courseService.getById(courseId).pipe(
|
||||||
map((course) => {
|
map((course) => {
|
||||||
@@ -261,20 +262,17 @@ export class ResultatService {
|
|||||||
return {
|
return {
|
||||||
id: String(apiResultat.id),
|
id: String(apiResultat.id),
|
||||||
course,
|
course,
|
||||||
// Normalize ordreArrivee to an array of cheval numbers
|
// API now returns 'ordreArrivee' as CSV/string; normalize to number[]
|
||||||
ordreArrivee: (apiResultat.ordreArrivee || [])
|
ordreArrivee: apiResultat.ordreArrivee,
|
||||||
.map((v) => (typeof v === 'string' ? Number(v) : v))
|
// dead-heat not provided by new API shape — default to empty
|
||||||
.filter((v): v is number => typeof v === 'number' && !Number.isNaN(v)),
|
chevauxDeadHeat: [],
|
||||||
// Normalize dead-heat horses to numbers as well
|
// Financial fields may not be present in new API; default to 0
|
||||||
chevauxDeadHeat: (apiResultat.chevauxDeadHeat || [])
|
totalMises: (apiResultat as any).totalMises ?? 0,
|
||||||
.map((v) => (typeof v === 'string' ? Number(v) : v))
|
masseAPartager: (apiResultat as any).masseAPartager ?? 0,
|
||||||
.filter((v): v is number => typeof v === 'number' && !Number.isNaN(v)),
|
prelevementsLegaux: (apiResultat as any).prelevementsLegaux ?? 0,
|
||||||
totalMises: apiResultat.totalMises,
|
montantRembourse: (apiResultat as any).montantRembourse ?? 0,
|
||||||
masseAPartager: apiResultat.masseAPartager,
|
montantCagnotte: (apiResultat as any).montantCagnotte ?? 0,
|
||||||
prelevementsLegaux: apiResultat.prelevementsLegaux,
|
adeadHeat: (apiResultat as any).adeadHeat ?? false,
|
||||||
montantRembourse: apiResultat.montantRembourse,
|
|
||||||
montantCagnotte: apiResultat.montantCagnotte,
|
|
||||||
adeadHeat: apiResultat.adeadHeat,
|
|
||||||
createdAt: apiResultat.createdAt,
|
createdAt: apiResultat.createdAt,
|
||||||
updatedAt: apiResultat.updatedAt,
|
updatedAt: apiResultat.updatedAt,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ export class ReunionService {
|
|||||||
meta: { total: 0, uniqueHippodromes: 0, upcomingReunions: 0, pastReunions: 0 },
|
meta: { total: 0, uniqueHippodromes: 0, upcomingReunions: 0, pastReunions: 0 },
|
||||||
},
|
},
|
||||||
params.page,
|
params.page,
|
||||||
params.perPage
|
params.size
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -197,7 +197,7 @@ export class ReunionService {
|
|||||||
meta: { total: 0, uniqueHippodromes: 0, upcomingReunions: 0, pastReunions: 0 },
|
meta: { total: 0, uniqueHippodromes: 0, upcomingReunions: 0, pastReunions: 0 },
|
||||||
},
|
},
|
||||||
params.page,
|
params.page,
|
||||||
params.perPage
|
params.size
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -273,8 +273,8 @@ export class ReunionService {
|
|||||||
// Apply client-side filtering, sorting, and pagination
|
// Apply client-side filtering, sorting, and pagination
|
||||||
let filtered = this.applyClientFilters(transformedData, params);
|
let filtered = this.applyClientFilters(transformedData, params);
|
||||||
const total = filtered.length;
|
const total = filtered.length;
|
||||||
const start = (params.page - 1) * params.perPage;
|
const start = (params.page - 1) * params.size;
|
||||||
const pageData = filtered.slice(start, start + params.perPage);
|
const pageData = filtered.slice(start, start + params.size);
|
||||||
|
|
||||||
const upcomingReunions = filtered.filter(
|
const upcomingReunions = filtered.filter(
|
||||||
(r) => new Date(r.date) >= new Date()
|
(r) => new Date(r.date) >= new Date()
|
||||||
@@ -288,7 +288,7 @@ export class ReunionService {
|
|||||||
meta: { total, uniqueHippodromes, upcomingReunions, pastReunions },
|
meta: { total, uniqueHippodromes, upcomingReunions, pastReunions },
|
||||||
},
|
},
|
||||||
params.page,
|
params.page,
|
||||||
params.perPage
|
params.size
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -359,8 +359,8 @@ export class ReunionService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const start = (params.page - 1) * params.perPage;
|
const start = (params.page - 1) * params.size;
|
||||||
const pageData = data.slice(start, start + params.perPage);
|
const pageData = data.slice(start, start + params.size);
|
||||||
|
|
||||||
const upcomingReunions = data.filter((r) => new Date(r.date) >= new Date()).length;
|
const upcomingReunions = data.filter((r) => new Date(r.date) >= new Date()).length;
|
||||||
const pastReunions = 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 },
|
meta: { total: data.length, uniqueHippodromes, upcomingReunions, pastReunions },
|
||||||
},
|
},
|
||||||
params.page,
|
params.page,
|
||||||
params.perPage
|
params.size
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export class RoleService {
|
|||||||
private buildParams(params: ListParams): HttpParams {
|
private buildParams(params: ListParams): HttpParams {
|
||||||
let httpParams = new HttpParams()
|
let httpParams = new HttpParams()
|
||||||
.set('page', String(params.page - 1))
|
.set('page', String(params.page - 1))
|
||||||
.set('size', String(params.perPage));
|
.set('size', String(params.size));
|
||||||
if (params.search) {
|
if (params.search) {
|
||||||
httpParams = httpParams.set('search', params.search);
|
httpParams = httpParams.set('search', params.search);
|
||||||
}
|
}
|
||||||
@@ -117,13 +117,13 @@ export class RoleService {
|
|||||||
return normalizePage<Role>(
|
return normalizePage<Role>(
|
||||||
{ data: roles, meta: { total: roles.length } },
|
{ data: roles, meta: { total: roles.length } },
|
||||||
params.page,
|
params.page,
|
||||||
params.perPage
|
params.size
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
catchError((err) => {
|
catchError((err) => {
|
||||||
console.error('Error fetching roles:', err);
|
console.error('Error fetching roles:', err);
|
||||||
return of(
|
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 },
|
meta: { total: 0 },
|
||||||
},
|
},
|
||||||
params.page,
|
params.page,
|
||||||
params.perPage
|
params.size
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export class ServicesUtils{
|
|||||||
let httpParams = new HttpParams();
|
let httpParams = new HttpParams();
|
||||||
Object.entries(params).forEach(([key, value])=>{
|
Object.entries(params).forEach(([key, value])=>{
|
||||||
if(params != null && params!=undefined){
|
if(params != null && params!=undefined){
|
||||||
httpParams.set(key, String(value))
|
httpParams = httpParams.set(key, String(value))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return httpParams;
|
return httpParams;
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ export class TpeService {
|
|||||||
let httpParams = new HttpParams();
|
let httpParams = new HttpParams();
|
||||||
if (params) {
|
if (params) {
|
||||||
if (params.page) httpParams = httpParams.set('page', params.page.toString());
|
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.search) httpParams = httpParams.set('search', params.search);
|
||||||
if (params.sortKey) httpParams = httpParams.set('sortKey', params.sortKey);
|
if (params.sortKey) httpParams = httpParams.set('sortKey', params.sortKey);
|
||||||
if (params.sortDir) httpParams = httpParams.set('sortDir', params.sortDir);
|
if (params.sortDir) httpParams = httpParams.set('sortDir', params.sortDir);
|
||||||
@@ -230,7 +230,7 @@ export class TpeService {
|
|||||||
return normalizePage<TpeDevice>(
|
return normalizePage<TpeDevice>(
|
||||||
{ data: tpes, meta: { total: tpes.length } },
|
{ data: tpes, meta: { total: tpes.length } },
|
||||||
params.page || 1,
|
params.page || 1,
|
||||||
params.perPage || 10
|
params.size || 10
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Otherwise return all as single page
|
// Otherwise return all as single page
|
||||||
|
|||||||
@@ -117,23 +117,23 @@ export class UserService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const start = (params.page - 1) * params.perPage;
|
const start = (params.page - 1) * params.size;
|
||||||
const pageData = data.slice(start, start + params.perPage);
|
const pageData = data.slice(start, start + params.size);
|
||||||
|
|
||||||
return normalizePage<User>(
|
return normalizePage<User>(
|
||||||
{ data: pageData, meta: { total: data.length } },
|
{ data: pageData, meta: { total: data.length } },
|
||||||
params.page,
|
params.page,
|
||||||
params.perPage
|
params.size
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
catchError(() =>
|
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
|
// 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> {
|
create(payload: Omit<User, 'id'>): Observable<User> {
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ const routes: Routes = [
|
|||||||
path: 'reunions',
|
path: 'reunions',
|
||||||
loadComponent: () => import('./pages/reunion/reunion').then((m) => m.ReunionList),
|
loadComponent: () => import('./pages/reunion/reunion').then((m) => m.ReunionList),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'rapport',
|
||||||
|
loadComponent: () => import('./pages/rapport/rapport').then((m) => m.Rapport),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'users',
|
path: 'users',
|
||||||
loadComponent: () => import('./pages/users/users').then((m) => m.UsersPage),
|
loadComponent: () => import('./pages/users/users').then((m) => m.UsersPage),
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export class Layout {
|
|||||||
{ icon: '🏟️', label: 'Hippodromes', link: '/hippodromes' },
|
{ icon: '🏟️', label: 'Hippodromes', link: '/hippodromes' },
|
||||||
{ icon: '📅', label: 'Reunions', link: '/reunions' },
|
{ icon: '📅', label: 'Reunions', link: '/reunions' },
|
||||||
{ icon: '🏇', label: 'Courses', link: '/courses' },
|
{ icon: '🏇', label: 'Courses', link: '/courses' },
|
||||||
{ icon: 'icon-chart-bar', label: 'Rapport des courses', link: '/rapport-courses' },
|
{ icon: 'icon-chart-bar', label: 'Rapport des courses', link: '/rapport' },
|
||||||
];
|
];
|
||||||
|
|
||||||
workspaceMenuItems: MenuItem[] = [
|
workspaceMenuItems: MenuItem[] = [
|
||||||
|
|||||||
@@ -28,9 +28,9 @@
|
|||||||
<app-paginator
|
<app-paginator
|
||||||
[total]="total()"
|
[total]="total()"
|
||||||
[page]="page()"
|
[page]="page()"
|
||||||
[perPage]="perPage()"
|
[perPage]="size()"
|
||||||
(pageChange)="page.set($event)"
|
(pageChange)="page.set($event)"
|
||||||
(perPageChange)="perPage.set($event)"
|
(perPageChange)="size.set($event)"
|
||||||
></app-paginator>
|
></app-paginator>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export class AgentsPage {
|
|||||||
loading = signal(false);
|
loading = signal(false);
|
||||||
|
|
||||||
page = signal(1);
|
page = signal(1);
|
||||||
perPage = signal(10);
|
size = signal(10);
|
||||||
search = signal('');
|
search = signal('');
|
||||||
sort = signal<SortState>({ key: 'code', dir: 'asc' });
|
sort = signal<SortState>({ key: 'code', dir: 'asc' });
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ export class AgentsPage {
|
|||||||
) {
|
) {
|
||||||
// Preload TPE maps for display
|
// Preload TPE maps for display
|
||||||
this.tpeSvc
|
this.tpeSvc
|
||||||
.list({ page: 1, perPage: 200, search: '', sortKey: 'imei', sortDir: 'asc' } as any)
|
.list({ page: 1, size: 200, search: '', sortKey: 'imei', sortDir: 'asc' } as any)
|
||||||
.subscribe((res) => {
|
.subscribe((res) => {
|
||||||
const tpes = res.content as TpeDevice[];
|
const tpes = res.content as TpeDevice[];
|
||||||
this.rebuildTpeMaps(tpes);
|
this.rebuildTpeMaps(tpes);
|
||||||
@@ -156,7 +156,7 @@ export class AgentsPage {
|
|||||||
effect(() => {
|
effect(() => {
|
||||||
const params = {
|
const params = {
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -167,7 +167,7 @@ export class AgentsPage {
|
|||||||
|
|
||||||
private fetch(params: {
|
private fetch(params: {
|
||||||
page: number;
|
page: number;
|
||||||
perPage: number;
|
size: number;
|
||||||
search: string;
|
search: string;
|
||||||
sortKey: string;
|
sortKey: string;
|
||||||
sortDir: SortDir;
|
sortDir: SortDir;
|
||||||
@@ -191,7 +191,7 @@ export class AgentsPage {
|
|||||||
|
|
||||||
private refreshTpeMap() {
|
private refreshTpeMap() {
|
||||||
this.tpeSvc
|
this.tpeSvc
|
||||||
.list({ page: 1, perPage: 200, search: '', sortKey: 'imei', sortDir: 'asc' } as any)
|
.list({ page: 1, size: 200, search: '', sortKey: 'imei', sortDir: 'asc' } as any)
|
||||||
.subscribe((res) => {
|
.subscribe((res) => {
|
||||||
const tpes = res.content as TpeDevice[];
|
const tpes = res.content as TpeDevice[];
|
||||||
this.rebuildTpeMaps(tpes);
|
this.rebuildTpeMaps(tpes);
|
||||||
@@ -381,7 +381,7 @@ export class AgentsPage {
|
|||||||
// Refresh data
|
// Refresh data
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -399,7 +399,7 @@ export class AgentsPage {
|
|||||||
this.api.delete(row.id).subscribe(() =>
|
this.api.delete(row.id).subscribe(() =>
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
|
|||||||
@@ -106,10 +106,10 @@
|
|||||||
</app-data-table>
|
</app-data-table>
|
||||||
|
|
||||||
<app-paginator
|
<app-paginator
|
||||||
[page]="page()"
|
[page]="page() + 1"
|
||||||
[perPage]="perPage()"
|
[perPage]="perPage()"
|
||||||
[total]="total()"
|
[total]="total()"
|
||||||
(pageChange)="page.set($event)"
|
(pageChange)="page.set($event - 1)"
|
||||||
(perPageChange)="perPage.set($event)"
|
(perPageChange)="perPage.set($event)"
|
||||||
[pageSizes]="pageSize"
|
[pageSizes]="pageSize"
|
||||||
/>
|
/>
|
||||||
@@ -158,12 +158,12 @@
|
|||||||
<app-resultat-form
|
<app-resultat-form
|
||||||
[course]="selectedCourseForResultat()!"
|
[course]="selectedCourseForResultat()!"
|
||||||
[resultat]="resultatsMap().get(selectedCourseForResultat()!.id)"
|
[resultat]="resultatsMap().get(selectedCourseForResultat()!.id)"
|
||||||
(save)="onResultatSave($event)"
|
(save)="onResultatSave($event.horses, $event.typesParisOuverts)"
|
||||||
(validate)="onResultatValidate()"
|
(validate)="onResultatValidate()"
|
||||||
(confirm)="onResultatConfirm()"
|
(confirm)="onResultatConfirm()"
|
||||||
(cancel)="closeResultatModal()"
|
(cancel)="closeResultatModal()"
|
||||||
/>
|
/>
|
||||||
<div modal-actions class="flex justify-end gap-2">
|
<div modal-actions class="flex justify-between gap-2">
|
||||||
<z-button zType="destructive" (click)="closeResultatModal()">Fermer</z-button>
|
<z-button zType="destructive" (click)="closeResultatModal()">Fermer</z-button>
|
||||||
</div>
|
</div>
|
||||||
</app-modal>
|
</app-modal>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { Course as CourseType } from 'src/app/core/interfaces/course';
|
|||||||
import { SortDir } from '@shared/paging/paging';
|
import { SortDir } from '@shared/paging/paging';
|
||||||
import { CourseApiResponse, 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 { ResultatService } from 'src/app/core/services/resultat';
|
||||||
import { Resultat } from 'src/app/core/interfaces/resultat';
|
import { Resultat, ResultatStatut } from 'src/app/core/interfaces/resultat';
|
||||||
import { A11yModule } from '@angular/cdk/a11y';
|
import { A11yModule } from '@angular/cdk/a11y';
|
||||||
import { CourseForm } from '@shared/forms/course-form/course-form';
|
import { CourseForm } from '@shared/forms/course-form/course-form';
|
||||||
import { NonPartantForm } from '@shared/forms/nonpartant-form/nonpartant-form';
|
import { NonPartantForm } from '@shared/forms/nonpartant-form/nonpartant-form';
|
||||||
@@ -57,10 +57,10 @@ export class Course {
|
|||||||
totalClosed = signal(0);
|
totalClosed = signal(0);
|
||||||
totalByType = signal<Record<string, number>>({});
|
totalByType = signal<Record<string, number>>({});
|
||||||
|
|
||||||
page = signal(1);
|
page = signal(0);
|
||||||
perPage = signal(10);
|
perPage = signal(10);
|
||||||
search = signal('');
|
search = signal('');
|
||||||
sort = signal<SortState>({ key: 'numero', dir: 'asc' });
|
sort = signal<SortState>({ key: 'id', dir: 'asc' });
|
||||||
pageSize = [10, 20, 50];
|
pageSize = [10, 20, 50];
|
||||||
|
|
||||||
modalOpen = signal(false);
|
modalOpen = signal(false);
|
||||||
@@ -102,40 +102,42 @@ export class Course {
|
|||||||
return '<span class="text-gray-500 dark:text-gray-400">—</span>';
|
return '<span class="text-gray-500 dark:text-gray-400">—</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group horses that are at the same place (ex-aequo/dead heat).
|
return `<span class="text-gray-500 dark:text-gray-400">${resultat.ordreArrivee}</span>`
|
||||||
// Backend/Resultat model store ordreArrivee as cheval numbers (1,2,3,...) and
|
|
||||||
// chevauxDeadHeat as the subset that are ex-aequo.
|
|
||||||
const deadHeatSet = new Set(resultat.chevauxDeadHeat || []);
|
|
||||||
|
|
||||||
const groups: number[][] = [];
|
// // Group horses that are at the same place (ex-aequo/dead heat).
|
||||||
let currentGroup: number[] = [];
|
// // Backend/Resultat model store ordreArrivee as cheval numbers (1,2,3,...) and
|
||||||
|
// // chevauxDeadHeat as the subset that are ex-aequo.
|
||||||
|
// const deadHeatSet = new Set(resultat.chevauxDeadHeat || []);
|
||||||
|
|
||||||
resultat.ordreArrivee.forEach((num, index) => {
|
// const groups: number[][] = [];
|
||||||
const isInDeadHeat = deadHeatSet.has(num);
|
// let currentGroup: number[] = [];
|
||||||
const prevNum = index > 0 ? resultat.ordreArrivee[index - 1] : null;
|
|
||||||
const prevIsInDeadHeat = prevNum !== null && deadHeatSet.has(prevNum);
|
|
||||||
|
|
||||||
if (isInDeadHeat && prevIsInDeadHeat && currentGroup.length > 0) {
|
// resultat.ordreArrivee.forEach((num, index) => {
|
||||||
// Continue the current dead heat group
|
// const isInDeadHeat = deadHeatSet.has(num);
|
||||||
currentGroup.push(num);
|
// const prevNum = index > 0 ? resultat.ordreArrivee[index - 1] : null;
|
||||||
} else {
|
// const prevIsInDeadHeat = prevNum !== null && deadHeatSet.has(prevNum);
|
||||||
// Start a new group
|
|
||||||
if (currentGroup.length > 0) {
|
|
||||||
groups.push(currentGroup);
|
|
||||||
}
|
|
||||||
currentGroup = [num];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Don't forget the last group
|
// if (isInDeadHeat && prevIsInDeadHeat && currentGroup.length > 0) {
|
||||||
if (currentGroup.length > 0) {
|
// // Continue the current dead heat group
|
||||||
groups.push(currentGroup);
|
// currentGroup.push(num);
|
||||||
}
|
// } else {
|
||||||
|
// // Start a new group
|
||||||
|
// if (currentGroup.length > 0) {
|
||||||
|
// groups.push(currentGroup);
|
||||||
|
// }
|
||||||
|
// currentGroup = [num];
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
const s = groups.map((nums) => nums.join('=')).join(' - ');
|
// // Don't forget the last group
|
||||||
|
// if (currentGroup.length > 0) {
|
||||||
|
// groups.push(currentGroup);
|
||||||
|
// }
|
||||||
|
|
||||||
// For now, we'll show the resultat. In the future, we might add a statut field to Resultat
|
// const s = groups.map((nums) => nums.join('=')).join(' - ');
|
||||||
return `<span class="mr-2">${s}</span>`;
|
|
||||||
|
// // For now, we'll show the resultat. In the future, we might add a statut field to Resultat
|
||||||
|
// return ;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -187,7 +189,7 @@ export class Course {
|
|||||||
effect(() => {
|
effect(() => {
|
||||||
const params = {
|
const params = {
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -198,7 +200,7 @@ export class Course {
|
|||||||
|
|
||||||
private fetch(params: {
|
private fetch(params: {
|
||||||
page: number;
|
page: number;
|
||||||
perPage: number;
|
size: number;
|
||||||
search: string;
|
search: string;
|
||||||
sortKey: string;
|
sortKey: string;
|
||||||
sortDir: SortDir;
|
sortDir: SortDir;
|
||||||
@@ -256,7 +258,7 @@ export class Course {
|
|||||||
// === UI Actions ===
|
// === UI Actions ===
|
||||||
onSearch(q: string) {
|
onSearch(q: string) {
|
||||||
this.search.set(q);
|
this.search.set(q);
|
||||||
this.page.set(1);
|
this.page.set(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
openCreate() {
|
openCreate() {
|
||||||
@@ -283,21 +285,15 @@ export class Course {
|
|||||||
this.formComp?.onSubmit();
|
this.formComp?.onSubmit();
|
||||||
}
|
}
|
||||||
|
|
||||||
onFormSave(payload: Partial<CourseType>) {
|
onFormSave(_: CourseType) {
|
||||||
const current = this.editingItem();
|
// The form now persists create/update itself. Just close and refresh.
|
||||||
const req$ = current?.id
|
this.closeModal();
|
||||||
? this.api.update(current.id, payload)
|
this.fetch({
|
||||||
: this.api.create(payload as Omit<CourseApiResponse, 'id'>);
|
page: this.page(),
|
||||||
|
size: this.perPage(),
|
||||||
req$.subscribe(() => {
|
search: this.search(),
|
||||||
this.closeModal();
|
sortKey: this.sort().key,
|
||||||
this.fetch({
|
sortDir: this.sort().dir,
|
||||||
page: this.page(),
|
|
||||||
perPage: this.perPage(),
|
|
||||||
search: this.search(),
|
|
||||||
sortKey: this.sort().key,
|
|
||||||
sortDir: this.sort().dir,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +303,7 @@ export class Course {
|
|||||||
this.api.delete(row.id).subscribe(() =>
|
this.api.delete(row.id).subscribe(() =>
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir,
|
sortDir: this.sort().dir,
|
||||||
@@ -360,7 +356,7 @@ export class Course {
|
|||||||
this.closeNonPartantModal();
|
this.closeNonPartantModal();
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir,
|
sortDir: this.sort().dir,
|
||||||
@@ -381,20 +377,22 @@ export class Course {
|
|||||||
this.selectedCourseForResultat.set(null);
|
this.selectedCourseForResultat.set(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
onResultatSave(places: number[][]) {
|
onResultatSave(places: number[][], typesParisOuverts: string[]) {
|
||||||
const c = this.selectedCourseForResultat();
|
const c = this.selectedCourseForResultat();
|
||||||
if (!c) return;
|
if (!c) return;
|
||||||
|
|
||||||
// Determine required number of horses based on course type
|
// Determine required number of horses based on course type
|
||||||
const getRequiredHorses = (type: string): number => {
|
const getRequiredHorses = (types: string[]): number => {
|
||||||
const typeStr = String(type).toUpperCase();
|
const typeStr = types;
|
||||||
if (typeStr.includes('TIERCE') || typeStr === 'PLAT') return 3;
|
if (typeStr.includes('PLACE') || typeStr.includes('GAGNANT')) return 3;
|
||||||
if (typeStr.includes('QUARTE')) return 4;
|
if(typeStr.includes('JUMELE_GAGNANT') || typeStr.includes('JUMELE_PLACE') || typeStr.includes('JUMELE_ORDRE')) return 2;
|
||||||
|
if(typeStr.includes('TRIO') || typeStr.includes('TRIO_ORDRE') || typeStr.includes('TRIPLET')) return 3
|
||||||
|
if (typeStr.includes('QUATRO')) return 4;
|
||||||
if (typeStr.includes('QUINTE')) return 5;
|
if (typeStr.includes('QUINTE')) return 5;
|
||||||
return 3; // Default
|
return 3; // Default
|
||||||
};
|
};
|
||||||
|
|
||||||
const requiredHorses = 3;
|
const requiredHorses = getRequiredHorses(typesParisOuverts);
|
||||||
|
|
||||||
// Collect all selected horses (flatten the places array)
|
// Collect all selected horses (flatten the places array)
|
||||||
const allHorses: number[] = places
|
const allHorses: number[] = places
|
||||||
@@ -409,46 +407,28 @@ export class Course {
|
|||||||
// Convert to ordreArrivee format
|
// Convert to ordreArrivee format
|
||||||
// If all are ex-aequo, they all go in ordreArrivee as they are (first place)
|
// If all are ex-aequo, they all go in ordreArrivee as they are (first place)
|
||||||
// Otherwise, distribute them across places
|
// Otherwise, distribute them across places
|
||||||
const ordreArrivee: Array<string> = [];
|
let ordreArrivee: string = '';
|
||||||
const chevauxDeadHeat: number[] = [];
|
|
||||||
|
|
||||||
if (isAllExAequo) {
|
|
||||||
// All horses are in first place (ex-aequo)
|
|
||||||
allHorses.forEach((numero) => {
|
|
||||||
ordreArrivee.push(numero.toString());
|
|
||||||
chevauxDeadHeat.push(numero);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Horses are distributed across places
|
|
||||||
places.forEach((placeGroup, placeIndex) => {
|
|
||||||
const validHorses = placeGroup.filter((n) => typeof n === 'number' && n > 0);
|
|
||||||
if (validHorses.length === 0) return;
|
|
||||||
|
|
||||||
const isDeadHeat = validHorses.length > 1;
|
|
||||||
|
|
||||||
validHorses.forEach((numero) => {
|
|
||||||
ordreArrivee.push(numero.toString());
|
|
||||||
|
|
||||||
if (isDeadHeat) {
|
|
||||||
chevauxDeadHeat.push(numero);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
places.forEach((place)=>{
|
||||||
|
if(Array.isArray(place) && place.length>1){
|
||||||
|
place.forEach((p, index)=>{
|
||||||
|
if(index == 0){
|
||||||
|
ordreArrivee = ordreArrivee ==''? String(p) : ordreArrivee+","+String(p)
|
||||||
|
}else{
|
||||||
|
ordreArrivee = ordreArrivee ==''? String(p) : ordreArrivee+"="+String(p)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
ordreArrivee = ordreArrivee ==''? String(place[0]): ordreArrivee+","+String(place[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
// Check if resultat already exists
|
// Check if resultat already exists
|
||||||
const existingResultat = this.resultatsMap().get(c.id);
|
const existingResultat = this.resultatsMap().get(c.id);
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
course: { id: c.id },
|
courseId: Number(c.id) ,
|
||||||
ordreArrivee,
|
ordreArrivee,
|
||||||
chevauxDeadHeat: chevauxDeadHeat.map((n) => String(n)),
|
statut: ResultatStatut.EN_ATTENTE,
|
||||||
totalMises: 0,
|
|
||||||
masseAPartager: 0,
|
|
||||||
prelevementsLegaux: 0,
|
|
||||||
montantRembourse: 0,
|
|
||||||
montantCagnotte: 0,
|
|
||||||
adeadHeat: chevauxDeadHeat.length > 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const request$ = existingResultat
|
const request$ = existingResultat
|
||||||
@@ -460,7 +440,7 @@ export class Course {
|
|||||||
this.closeResultatModal();
|
this.closeResultatModal();
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir,
|
sortDir: this.sort().dir,
|
||||||
@@ -490,7 +470,7 @@ export class Course {
|
|||||||
this.closeResultatModal();
|
this.closeResultatModal();
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir,
|
sortDir: this.sort().dir,
|
||||||
@@ -520,7 +500,7 @@ export class Course {
|
|||||||
this.closeResultatModal();
|
this.closeResultatModal();
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir,
|
sortDir: this.sort().dir,
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
[page]="page()"
|
[page]="page()"
|
||||||
[perPage]="perPage()"
|
[perPage]="perPage()"
|
||||||
[total]="total()"
|
[total]="total()"
|
||||||
(pageChange)="page.set($event)"
|
(pageChange)="page.set($event - 1)"
|
||||||
(perPageChange)="onPerPage($event)"
|
(perPageChange)="onPerPage($event)"
|
||||||
[pageSizes]="pageSize"
|
[pageSizes]="pageSize"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export class Hippodrome {
|
|||||||
totalReunions = signal(0);
|
totalReunions = signal(0);
|
||||||
totalCourses = signal(0);
|
totalCourses = signal(0);
|
||||||
|
|
||||||
page = signal(1);
|
page = signal(0);
|
||||||
perPage = signal(10);
|
perPage = signal(10);
|
||||||
pageSize = [10, 20, 50];
|
pageSize = [10, 20, 50];
|
||||||
search = signal('');
|
search = signal('');
|
||||||
@@ -105,7 +105,7 @@ export class Hippodrome {
|
|||||||
this.api
|
this.api
|
||||||
.list({
|
.list({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir,
|
sortDir: this.sort().dir,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</app-data-table>
|
</app-data-table>
|
||||||
|
|
||||||
<app-paginator [total]="total()" [page]="page()" [perPage]="perPage()" (pageChange)="page.set($event)" (perPageChange)="perPage.set($event)"></app-paginator>
|
<app-paginator [total]="total()" [page]="page()" [perPage]="size()" (pageChange)="page.set($event)" (perPageChange)="size.set($event)"></app-paginator>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-modal [open]="modalOpen()" [title]="modalTitle()" (close)="closeModal()" size="xl">
|
<app-modal [open]="modalOpen()" [title]="modalTitle()" (close)="closeModal()" size="xl">
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class LimitsPage implements OnInit {
|
|||||||
total = signal(0);
|
total = signal(0);
|
||||||
loading = signal(false);
|
loading = signal(false);
|
||||||
page = signal(1);
|
page = signal(1);
|
||||||
perPage = signal(10);
|
size = signal(10);
|
||||||
search = signal('');
|
search = signal('');
|
||||||
sort = signal<SortState>({ key: 'code', dir: 'asc' });
|
sort = signal<SortState>({ key: 'code', dir: 'asc' });
|
||||||
selectedActif = signal<boolean | null>(null);
|
selectedActif = signal<boolean | null>(null);
|
||||||
@@ -92,11 +92,11 @@ export class LimitsPage implements OnInit {
|
|||||||
|
|
||||||
constructor(private api: AgentLimitService) {
|
constructor(private api: AgentLimitService) {
|
||||||
effect(() => {
|
effect(() => {
|
||||||
// Only trigger fetch when page, perPage, or sort changes (not search - handled by searchSubject)
|
// Only trigger fetch when page, size, or sort changes (not search - handled by searchSubject)
|
||||||
const searchValue = this.search();
|
const searchValue = this.search();
|
||||||
const params = {
|
const params = {
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: searchValue,
|
search: searchValue,
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -120,7 +120,7 @@ export class LimitsPage implements OnInit {
|
|||||||
// If empty, use normal list
|
// If empty, use normal list
|
||||||
return this.api.list({
|
return this.api.list({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: '',
|
search: '',
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -155,14 +155,14 @@ export class LimitsPage implements OnInit {
|
|||||||
// Initial fetch
|
// Initial fetch
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private fetch(params: { page: number; perPage: number; search: string; sortKey: string; sortDir: SortDir }) {
|
private fetch(params: { page: number; size: number; search: string; sortKey: string; sortDir: SortDir }) {
|
||||||
// Don't fetch if there's a search query - it's handled by searchSubject
|
// Don't fetch if there's a search query - it's handled by searchSubject
|
||||||
const searchQuery = params.search.trim();
|
const searchQuery = params.search.trim();
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
@@ -214,7 +214,7 @@ export class LimitsPage implements OnInit {
|
|||||||
// If empty, fetch normally
|
// If empty, fetch normally
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: '',
|
search: '',
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -227,7 +227,7 @@ export class LimitsPage implements OnInit {
|
|||||||
this.page.set(1);
|
this.page.set(1);
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -261,7 +261,7 @@ export class LimitsPage implements OnInit {
|
|||||||
this.closeModal();
|
this.closeModal();
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -282,7 +282,7 @@ export class LimitsPage implements OnInit {
|
|||||||
this.api.delete(row.id).subscribe(() => {
|
this.api.delete(row.id).subscribe(() => {
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ export class Main {
|
|||||||
private baseParams(): ListParams {
|
private baseParams(): ListParams {
|
||||||
return {
|
return {
|
||||||
page: 1,
|
page: 1,
|
||||||
perPage: 1,
|
size: 1,
|
||||||
search: '',
|
search: '',
|
||||||
sortKey: 'id',
|
sortKey: 'id',
|
||||||
sortDir: 'asc' as SortDir,
|
sortDir: 'asc' as SortDir,
|
||||||
@@ -158,7 +158,7 @@ export class Main {
|
|||||||
.list(params, true)
|
.list(params, true)
|
||||||
.pipe(catchError(() => of({ data: [], meta: { total: 0 } } as any))),
|
.pipe(catchError(() => of({ data: [], meta: { total: 0 } } as any))),
|
||||||
courses: this.courseService
|
courses: this.courseService
|
||||||
.list(coursesParams, true)
|
.list(coursesParams)
|
||||||
.pipe(catchError(() => of({ data: [], meta: { total: 0 } } as any))),
|
.pipe(catchError(() => of({ data: [], meta: { total: 0 } } as any))),
|
||||||
roles: this.roleService
|
roles: this.roleService
|
||||||
.list(params)
|
.list(params)
|
||||||
|
|||||||
0
src/app/dashboard/pages/rapport/rapport.css
Normal file
0
src/app/dashboard/pages/rapport/rapport.css
Normal file
33
src/app/dashboard/pages/rapport/rapport.html
Normal file
33
src/app/dashboard/pages/rapport/rapport.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h2 class="text-2xl font-semibold">Rapport — Courses avec résultats</h2>
|
||||||
|
<z-button zType="default" (click)="fetch()">Récupérer le rapport</z-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<app-data-table [data]="rows()" [columns]="cols" [loading]="loading()">
|
||||||
|
<ng-template #rowActions let-row>
|
||||||
|
<z-button zType="ghost" zSize="icon" aria-label="Voir le rapport" (click)="openReport(row)">
|
||||||
|
<div class="icon-file-text"></div>
|
||||||
|
</z-button>
|
||||||
|
</ng-template>
|
||||||
|
</app-data-table>
|
||||||
|
<div class="flex items-center justify-between mt-3">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<label class="text-sm">Lignes par page</label>
|
||||||
|
<select class="border rounded px-2 py-1" [value]="perPage()" (change)="onPerPageChangeEvent($event)">
|
||||||
|
<option [value]="5">5</option>
|
||||||
|
<option [value]="10">10</option>
|
||||||
|
<option [value]="25">25</option>
|
||||||
|
<option [value]="50">50</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-muted">{{ totalElements() }} résultats</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<z-pagination [zPageIndex]="page()" [zTotal]="totalPages()" (zPageIndexChange)="onPageChange($event)"></z-pagination>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
23
src/app/dashboard/pages/rapport/rapport.spec.ts
Normal file
23
src/app/dashboard/pages/rapport/rapport.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { Rapport } from './rapport';
|
||||||
|
|
||||||
|
describe('Rapport', () => {
|
||||||
|
let component: Rapport;
|
||||||
|
let fixture: ComponentFixture<Rapport>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [Rapport]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(Rapport);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
94
src/app/dashboard/pages/rapport/rapport.ts
Normal file
94
src/app/dashboard/pages/rapport/rapport.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ChangeDetectionStrategy, Component, signal, ViewChild } from '@angular/core';
|
||||||
|
import { DataTable, TableColumn } from '@shared/components/data-table/data-table';
|
||||||
|
import { ZardButtonComponent } from '@shared/components/button/button.component';
|
||||||
|
import { ZardPaginationModule } from '@shared/components/pagination/pagination.module';
|
||||||
|
import { ListParams, PagedResult } from '@shared/paging/paging';
|
||||||
|
import { ResultatApiResponse } from 'src/app/core/interfaces/resultat';
|
||||||
|
import { ResultatService } from 'src/app/core/services/resultat';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-rapport',
|
||||||
|
templateUrl: './rapport.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [CommonModule, DataTable, ZardButtonComponent, ZardPaginationModule],
|
||||||
|
})
|
||||||
|
export class Rapport {
|
||||||
|
rows = signal<ResultatApiResponse[]>([]);
|
||||||
|
loading = signal(false);
|
||||||
|
// Pagination state
|
||||||
|
page = signal<number>(1);
|
||||||
|
perPage = signal<number>(10);
|
||||||
|
totalPages = signal<number>(1);
|
||||||
|
totalElements = signal<number>(0);
|
||||||
|
@ViewChild(DataTable) table?: DataTable<ResultatApiResponse>;
|
||||||
|
|
||||||
|
cols: TableColumn<ResultatApiResponse>[] = [
|
||||||
|
{ key: 'courseNumero', label: 'N°' },
|
||||||
|
{ key: 'hippodromeNom', label: 'Hippodrome' },
|
||||||
|
{ key: 'courseNom', label: 'Course' },
|
||||||
|
{ key: 'ordreArrivee', label: "Ordre d'arrivée", cell: (r) => String(r.ordreArrivee ?? '').replace(/,/g, ' - ') },
|
||||||
|
{ key: 'statut', label: 'Statut', cell: (r)=>(r.statut.toString().toLowerCase().replace("_", " ")) },
|
||||||
|
{ key: 'datePublication', label: 'Date pub.', cell: (r) => r.datePublication ?? r.createdAt ?? '—' },
|
||||||
|
{ key: 'dateValidation', label: 'Date validation' },
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(private api: ResultatService) {
|
||||||
|
// initial load
|
||||||
|
this.fetch();
|
||||||
|
}
|
||||||
|
private fetchPage(params?: Partial<ListParams>) {
|
||||||
|
this.loading.set(true);
|
||||||
|
const p: ListParams = { page: this.page(), size: this.perPage(), ...(params || {}) };
|
||||||
|
this.api.listRawPaged(p).subscribe({
|
||||||
|
next: (res: PagedResult<ResultatApiResponse>) => {
|
||||||
|
const filtered = (res?.content || []).filter((r) => !!(r.ordreArrivee && String(r.ordreArrivee).trim()));
|
||||||
|
this.rows.set(filtered);
|
||||||
|
// normalize paging meta
|
||||||
|
this.totalPages.set(res.totalPages ?? 1);
|
||||||
|
this.totalElements.set(res.totalElements ?? (filtered.length || 0));
|
||||||
|
// ensure local page is in sync with backend response
|
||||||
|
if (res.pageable?.pageNumber) this.page.set(res.pageable.pageNumber);
|
||||||
|
this.loading.set(false);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Error fetching paged reports:', err);
|
||||||
|
this.rows.set([]);
|
||||||
|
this.loading.set(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch() {
|
||||||
|
this.fetchPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageChange(nextPage: number) {
|
||||||
|
this.page.set(nextPage);
|
||||||
|
this.fetchPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPerPageChange(size: number) {
|
||||||
|
this.perPage.set(size);
|
||||||
|
this.page.set(1);
|
||||||
|
this.fetchPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapper for template change event to avoid $event typing issues
|
||||||
|
onPerPageChangeEvent(e: Event) {
|
||||||
|
const v = (e.target as HTMLSelectElement)?.value;
|
||||||
|
const size = Number(v) || 10;
|
||||||
|
this.onPerPageChange(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
openReport(row: ResultatApiResponse) {
|
||||||
|
try {
|
||||||
|
// Open a per-result report URL in a new tab. Adjust path if your server uses another route.
|
||||||
|
const url = `/rapport/${row.id}`;
|
||||||
|
window.open(url, '_blank');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to open report for', row, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,10 +64,10 @@
|
|||||||
|
|
||||||
<app-paginator
|
<app-paginator
|
||||||
[page]="page()"
|
[page]="page()"
|
||||||
[perPage]="perPage()"
|
[perPage]="size()"
|
||||||
[total]="total()"
|
[total]="total()"
|
||||||
(pageChange)="page.set($event)"
|
(pageChange)="page.set($event)"
|
||||||
(perPageChange)="perPage.set($event)"
|
(perPageChange)="size.set($event)"
|
||||||
[pageSizes]="pageSize"
|
[pageSizes]="pageSize"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export class ReunionList {
|
|||||||
|
|
||||||
// pagination, sorting, search
|
// pagination, sorting, search
|
||||||
page = signal(1);
|
page = signal(1);
|
||||||
perPage = signal(10);
|
size = signal(10);
|
||||||
search = signal('');
|
search = signal('');
|
||||||
sort = signal<SortState>({ key: 'date', dir: 'asc' });
|
sort = signal<SortState>({ key: 'date', dir: 'asc' });
|
||||||
pageSize = [10, 20, 50];
|
pageSize = [10, 20, 50];
|
||||||
@@ -134,7 +134,7 @@ export class ReunionList {
|
|||||||
effect(() => {
|
effect(() => {
|
||||||
const params = {
|
const params = {
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir,
|
sortDir: this.sort().dir,
|
||||||
@@ -145,7 +145,7 @@ export class ReunionList {
|
|||||||
|
|
||||||
private fetch(params: {
|
private fetch(params: {
|
||||||
page: number;
|
page: number;
|
||||||
perPage: number;
|
size: number;
|
||||||
search: string;
|
search: string;
|
||||||
sortKey: string;
|
sortKey: string;
|
||||||
sortDir: SortDir;
|
sortDir: SortDir;
|
||||||
@@ -211,7 +211,7 @@ export class ReunionList {
|
|||||||
// refetch current page
|
// refetch current page
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir,
|
sortDir: this.sort().dir,
|
||||||
@@ -224,7 +224,7 @@ export class ReunionList {
|
|||||||
this.api.delete(row.id).subscribe(() =>
|
this.api.delete(row.id).subscribe(() =>
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir,
|
sortDir: this.sort().dir,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export class RolesPage {
|
|||||||
loading = signal(false);
|
loading = signal(false);
|
||||||
permissions = signal<Permission[]>([]);
|
permissions = signal<Permission[]>([]);
|
||||||
page = signal(1);
|
page = signal(1);
|
||||||
perPage = signal(10);
|
size = signal(10);
|
||||||
search = signal('');
|
search = signal('');
|
||||||
sort = signal<SortState>({ key: 'name', dir: 'asc' });
|
sort = signal<SortState>({ key: 'name', dir: 'asc' });
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ export class RolesPage {
|
|||||||
effect(() => {
|
effect(() => {
|
||||||
const params = {
|
const params = {
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -76,7 +76,7 @@ export class RolesPage {
|
|||||||
|
|
||||||
private fetch(params: {
|
private fetch(params: {
|
||||||
page: number;
|
page: number;
|
||||||
perPage: number;
|
size: number;
|
||||||
search: string;
|
search: string;
|
||||||
sortKey: string;
|
sortKey: string;
|
||||||
sortDir: SortDir;
|
sortDir: SortDir;
|
||||||
@@ -136,7 +136,7 @@ export class RolesPage {
|
|||||||
);
|
);
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -160,7 +160,7 @@ export class RolesPage {
|
|||||||
toast.success(`Le rôle « ${row.name} » a été supprimé avec succès`);
|
toast.success(`Le rôle « ${row.name} » a été supprimé avec succès`);
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.size(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ export class TpePage implements OnInit {
|
|||||||
const searchValue = this.search();
|
const searchValue = this.search();
|
||||||
const params = {
|
const params = {
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: searchValue,
|
search: searchValue,
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -175,7 +175,7 @@ export class TpePage implements OnInit {
|
|||||||
// If empty, use normal list
|
// If empty, use normal list
|
||||||
return this.api.list({
|
return this.api.list({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: '',
|
search: '',
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -211,7 +211,7 @@ export class TpePage implements OnInit {
|
|||||||
if (!this.search().trim()) {
|
if (!this.search().trim()) {
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: '',
|
search: '',
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -249,7 +249,7 @@ export class TpePage implements OnInit {
|
|||||||
|
|
||||||
private fetch(params: {
|
private fetch(params: {
|
||||||
page: number;
|
page: number;
|
||||||
perPage: number;
|
size: number;
|
||||||
search: string;
|
search: string;
|
||||||
sortKey: string;
|
sortKey: string;
|
||||||
sortDir: SortDir;
|
sortDir: SortDir;
|
||||||
@@ -305,7 +305,7 @@ export class TpePage implements OnInit {
|
|||||||
// If empty, fetch normally
|
// If empty, fetch normally
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: '',
|
search: '',
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -318,7 +318,7 @@ export class TpePage implements OnInit {
|
|||||||
this.page.set(1);
|
this.page.set(1);
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -331,7 +331,7 @@ export class TpePage implements OnInit {
|
|||||||
next: () => {
|
next: () => {
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -347,7 +347,7 @@ export class TpePage implements OnInit {
|
|||||||
next: () => {
|
next: () => {
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -393,7 +393,7 @@ export class TpePage implements OnInit {
|
|||||||
this.selectedAgentId.set('');
|
this.selectedAgentId.set('');
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -456,7 +456,7 @@ export class TpePage implements OnInit {
|
|||||||
// Refresh data
|
// Refresh data
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -476,7 +476,7 @@ export class TpePage implements OnInit {
|
|||||||
this.api.delete(row.id).subscribe(() => {
|
this.api.delete(row.id).subscribe(() => {
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export class UsersPage {
|
|||||||
effect(() => {
|
effect(() => {
|
||||||
const params = {
|
const params = {
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -94,7 +94,7 @@ export class UsersPage {
|
|||||||
|
|
||||||
private fetch(params: {
|
private fetch(params: {
|
||||||
page: number;
|
page: number;
|
||||||
perPage: number;
|
size: number;
|
||||||
search: string;
|
search: string;
|
||||||
sortKey: string;
|
sortKey: string;
|
||||||
sortDir: SortDir;
|
sortDir: SortDir;
|
||||||
@@ -156,7 +156,7 @@ export class UsersPage {
|
|||||||
);
|
);
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
@@ -181,7 +181,7 @@ export class UsersPage {
|
|||||||
toast.success(`L'utilisateur « ${row.nom} ${row.prenom} » a été supprimé avec succès`);
|
toast.success(`L'utilisateur « ${row.nom} ${row.prenom} » a été supprimé avec succès`);
|
||||||
this.fetch({
|
this.fetch({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search(),
|
search: this.search(),
|
||||||
sortKey: this.sort().key,
|
sortKey: this.sort().key,
|
||||||
sortDir: this.sort().dir as SortDir,
|
sortDir: this.sort().dir as SortDir,
|
||||||
|
|||||||
@@ -19,26 +19,25 @@
|
|||||||
|
|
||||||
<div class="grid sm:grid-cols-3 gap-5">
|
<div class="grid sm:grid-cols-3 gap-5">
|
||||||
<z-form-field>
|
<z-form-field>
|
||||||
<label z-form-label zRequired for="hippodrome">Hippodrome</label>
|
<label z-form-label zRequired for="hippodrome">Hippodrome</label>
|
||||||
<z-form-control
|
<z-form-control
|
||||||
[errorMessage]="isInvalid('hippodromeId') ? 'Veuillez sélectionner une réunion' : ''"
|
[errorMessage]="isInvalid('hippodromeId') ? 'Veuillez sélectionner une réunion' : ''"
|
||||||
>
|
>
|
||||||
<z-select
|
<z-select
|
||||||
id="hippodromeId"
|
id="hippodromeId"
|
||||||
placeholder="Rechercher une réunion..."
|
placeholder="Rechercher une réunion..."
|
||||||
formControlName="hippodromeId"
|
formControlName="hippodromeId"
|
||||||
[zLabel]="selectedHippodromeLabel() || ''">
|
[zLabel]="selectedHippodromeLabel() || ''"
|
||||||
@if (loadingHippodromes()) {
|
>
|
||||||
<z-select-item [zValue]="''" disabled>Chargement des Hippodromes...</z-select-item>
|
@if (loadingHippodromes()) {
|
||||||
} @else { @for (r of filteredHippodromes(); track r.id) {
|
<z-select-item [zValue]="''" disabled>Chargement des Hippodromes...</z-select-item>
|
||||||
<z-select-item [zValue]="r.id">
|
} @else { @for (r of filteredHippodromes(); track r.id) {
|
||||||
{{ r.nom }} - ({{ r.ville }})
|
<z-select-item [zValue]="r.id"> {{ r.nom }} - ({{ r.ville }}) </z-select-item>
|
||||||
</z-select-item>
|
} }
|
||||||
} }
|
</z-select>
|
||||||
</z-select>
|
</z-form-control>
|
||||||
</z-form-control>
|
</z-form-field>
|
||||||
</z-form-field>
|
<z-form-field>
|
||||||
<z-form-field>
|
|
||||||
<label z-form-label zRequired class="text-sm font-semibold text-gray-700 dark:text-gray-300"
|
<label z-form-label zRequired class="text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||||
>Numero de la reunion</label
|
>Numero de la reunion</label
|
||||||
>
|
>
|
||||||
@@ -84,7 +83,7 @@
|
|||||||
<z-form-control>
|
<z-form-control>
|
||||||
<input
|
<input
|
||||||
z-input
|
z-input
|
||||||
placeholder="Ex: TIERCE, QUINTE"
|
placeholder="Ex: Plat"
|
||||||
formControlName="discipline"
|
formControlName="discipline"
|
||||||
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"
|
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"
|
||||||
/>
|
/>
|
||||||
@@ -142,13 +141,13 @@
|
|||||||
z-form-label
|
z-form-label
|
||||||
class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2 block"
|
class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2 block"
|
||||||
>
|
>
|
||||||
heure de départ
|
heure de départ
|
||||||
</label>
|
</label>
|
||||||
<div class="flex gap-3 items-center">
|
<div class="flex gap-3 items-center">
|
||||||
<input
|
<input
|
||||||
z-input
|
z-input
|
||||||
type="time"
|
type="time"
|
||||||
formControlName="heureDepartPrevu"
|
formControlName="heureDepartPrevue"
|
||||||
class="w-32 px-3 py-2.5 rounded-lg border border-gray-300 dark:border-gray-600 focus:ring-2 focus:ring-purple-500 dark:bg-gray-800 dark:text-gray-100 text-sm"
|
class="w-32 px-3 py-2.5 rounded-lg border border-gray-300 dark:border-gray-600 focus:ring-2 focus:ring-purple-500 dark:bg-gray-800 dark:text-gray-100 text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -161,23 +160,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 📅 SECTION 3 — Réunion -->
|
|
||||||
<div class="space-y-4">
|
|
||||||
<div class="flex items-center gap-3 pb-3 border-b border-gray-200 dark:border-gray-700">
|
|
||||||
<div
|
|
||||||
class="flex items-center justify-center w-10 h-10 rounded-xl bg-gradient-to-br from-green-500 to-green-600 text-white text-xl"
|
|
||||||
>
|
|
||||||
📅
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h2 class="text-xl font-bold text-gray-900 dark:text-gray-100">Réunion associée</h2>
|
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
Sélectionnez la réunion concernée ou créez-en une nouvelle
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 🏇 SECTION 4 — Détails techniques -->
|
<!-- 🏇 SECTION 4 — Détails techniques -->
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex items-center gap-3 pb-3 border-b border-gray-200 dark:border-gray-700">
|
<div class="flex items-center gap-3 pb-3 border-b border-gray-200 dark:border-gray-700">
|
||||||
@@ -194,21 +176,25 @@
|
|||||||
|
|
||||||
<div class="grid sm:grid-cols-1 lg:grid-cols-2 gap-5">
|
<div class="grid sm:grid-cols-1 lg:grid-cols-2 gap-5">
|
||||||
<z-form-field>
|
<z-form-field>
|
||||||
<label z-form-label class="text-sm font-semibold text-gray-700 dark:text-gray-300"
|
<label z-form-label class="text-sm font-semibold text-gray-700 dark:text-gray-300">
|
||||||
>Types de paris ouverts (CSV)</label
|
Types de paris ouverts
|
||||||
>
|
</label>
|
||||||
<z-form-control>
|
|
||||||
<z-select
|
<z-form-control class="space-y-2">
|
||||||
|
@for (t of courseTypes; track t.value) {
|
||||||
|
<label class="flex items-center gap-2 text-sm">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
[value]="t.value"
|
||||||
|
(change)="onToggleType($event)"
|
||||||
|
[checked]="form.value.typesParisOuverts?.includes(t.value)"
|
||||||
|
/>
|
||||||
|
{{ t.label }}
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
</z-form-control>
|
||||||
|
</z-form-field>
|
||||||
|
|
||||||
formControlName="typeParisOuverts"
|
|
||||||
class="w-full">
|
|
||||||
@for (t of courseTypes; track t.value) {
|
|
||||||
<z-select-item [zValue]="t.value">{{ t.label }}</z-select-item>
|
|
||||||
}
|
|
||||||
</z-select>
|
|
||||||
<p class="mt-1 text-sm text-gray-500">Séparez les types par des virgules.</p>
|
|
||||||
</z-form-control>
|
|
||||||
</z-form-field>
|
|
||||||
|
|
||||||
<z-form-field>
|
<z-form-field>
|
||||||
<label z-form-label zRequired class="text-sm font-semibold text-gray-700 dark:text-gray-300"
|
<label z-form-label zRequired class="text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||||
@@ -216,7 +202,9 @@
|
|||||||
>
|
>
|
||||||
<z-form-control
|
<z-form-control
|
||||||
[errorMessage]="
|
[errorMessage]="
|
||||||
isInvalid('nombrePartants') ? errorMessage('nombrePartants') || 'Ce champ est obligatoire' : ''
|
isInvalid('nombrePartants')
|
||||||
|
? errorMessage('nombrePartants') || 'Ce champ est obligatoire'
|
||||||
|
: ''
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -251,7 +239,7 @@
|
|||||||
<z-form-control>
|
<z-form-control>
|
||||||
<input
|
<input
|
||||||
z-input
|
z-input
|
||||||
placeholder="Âge, catégorie..."
|
placeholder="Ex: A"
|
||||||
formControlName="categorie"
|
formControlName="categorie"
|
||||||
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"
|
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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -48,10 +48,17 @@ import { CourseApiResponse, CourseService } from 'src/app/core/services/course';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class CourseForm implements OnInit, AfterViewInit, OnDestroy {
|
export class CourseForm implements OnInit, AfterViewInit, OnDestroy {
|
||||||
@Output() save = new EventEmitter<Partial<CourseApiResponse>>();
|
@Output() save = new EventEmitter<Course>();
|
||||||
@Output() cancel = new EventEmitter<void>();
|
@Output() cancel = new EventEmitter<void>();
|
||||||
|
|
||||||
@Input() value?: Course;
|
private _value?: Course;
|
||||||
|
@Input() set value(v: Course | undefined) {
|
||||||
|
this._value = v;
|
||||||
|
this.hydrateFromValue(v);
|
||||||
|
}
|
||||||
|
get value(): Course | undefined {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
|
||||||
form: FormGroup;
|
form: FormGroup;
|
||||||
submitted = false;
|
submitted = false;
|
||||||
@@ -76,7 +83,7 @@ export class CourseForm implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
nom: ['', Validators.required],
|
nom: ['', Validators.required],
|
||||||
numero: [null, Validators.required],
|
numero: [null, Validators.required],
|
||||||
statut: [CourseStatut.BROUILLON, Validators.required],
|
statut: [CourseStatut.BROUILLON, Validators.required],
|
||||||
heureDepartPrevu: ['', Validators.required],
|
heureDepartPrevue: ['', Validators.required],
|
||||||
discipline: ['', Validators.required],
|
discipline: ['', Validators.required],
|
||||||
distanceMetres: [null, Validators.required],
|
distanceMetres: [null, Validators.required],
|
||||||
nombrePartants: [null, Validators.required],
|
nombrePartants: [null, Validators.required],
|
||||||
@@ -86,7 +93,7 @@ export class CourseForm implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
reporteeAutreJour: [false],
|
reporteeAutreJour: [false],
|
||||||
incidentTechnique: [false],
|
incidentTechnique: [false],
|
||||||
nonPartants: [[]],
|
nonPartants: [[]],
|
||||||
typesParisOuverts: [''],
|
typesParisOuverts: [[], Validators.required],
|
||||||
createdBy: ['agent-001'],
|
createdBy: ['agent-001'],
|
||||||
validatedBy: [''],
|
validatedBy: [''],
|
||||||
});
|
});
|
||||||
@@ -158,7 +165,7 @@ export class CourseForm implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
this.subs.add(
|
this.subs.add(
|
||||||
this.hippodromeService
|
this.hippodromeService
|
||||||
.list({ page: 1, perPage: 1000 }, true)
|
.list({ page: 0, size: 1000 }, true)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (res) => {
|
next: (res) => {
|
||||||
this.hippodromes.set(res.content);
|
this.hippodromes.set(res.content);
|
||||||
@@ -175,11 +182,101 @@ export class CourseForm implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.cdr.markForCheck();
|
this.cdr.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDateCorrectFormat = (date: Date): string => {
|
||||||
|
let hour = date.getHours().toString();
|
||||||
|
let minutes = date.getMinutes().toString();
|
||||||
|
hour = hour.length == 1? `0${hour}`: hour;
|
||||||
|
minutes = minutes.length == 1? `0${minutes}`:minutes
|
||||||
|
return `${hour}:${minutes}`
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private hydrateFromValue(v?: Course) {
|
||||||
|
if (!v) {
|
||||||
|
// Reset to defaults
|
||||||
|
this.form.reset({
|
||||||
|
hippodromeId: '',
|
||||||
|
reunionNumero: null,
|
||||||
|
reunionDate: '',
|
||||||
|
nom: '',
|
||||||
|
numero: null,
|
||||||
|
statut: CourseStatut.BROUILLON,
|
||||||
|
heureDepartPrevue: '',
|
||||||
|
discipline: '',
|
||||||
|
distanceMetres: null,
|
||||||
|
nombrePartants: null,
|
||||||
|
annulee: false,
|
||||||
|
categorie: '',
|
||||||
|
reporteeMemeJour: false,
|
||||||
|
reporteeAutreJour: false,
|
||||||
|
incidentTechnique: false,
|
||||||
|
nonPartants: [],
|
||||||
|
typesParisOuverts: [],
|
||||||
|
});
|
||||||
|
this.selectedHippodromeLabel.set('');
|
||||||
|
this.form.markAsPristine();
|
||||||
|
this.form.markAsUntouched();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hippodromeId = v.hippodrome?.id ? String(v.hippodrome.id) : '';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this.form.patchValue(
|
||||||
|
{
|
||||||
|
hippodromeId,
|
||||||
|
reunionNumero: v.reunionNumero ?? null,
|
||||||
|
reunionDate: v.reunionDate ?? '',
|
||||||
|
nom: v.nom ?? '',
|
||||||
|
numero: v.numero ?? null,
|
||||||
|
statut: v.statut ?? CourseStatut.BROUILLON,
|
||||||
|
heureDepartPrevue: v.heureDepartPrevue? this.getDateCorrectFormat(new Date(v.heureDepartPrevue)) :'',
|
||||||
|
discipline: v.discipline ?? '',
|
||||||
|
distanceMetres: v.distanceMetres ?? null,
|
||||||
|
nombrePartants: v.nombrePartants ?? null,
|
||||||
|
annulee: v.annulee ?? false,
|
||||||
|
categorie: v.categorie ?? '',
|
||||||
|
reporteeMemeJour: v.reporteeMemeJour ?? false,
|
||||||
|
reporteeAutreJour: v.reporteeAutreJour ?? false,
|
||||||
|
incidentTechnique: v.incidentTechnique ?? false,
|
||||||
|
nonPartants: v.nonPartants ?? [],
|
||||||
|
typesParisOuverts: v.typesParisOuverts,
|
||||||
|
},
|
||||||
|
{ emitEvent: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set hippodrome label if available
|
||||||
|
if (hippodromeId && this.hippodromes().length > 0) {
|
||||||
|
const h = this.hippodromes().find((r) => String(r.id) === hippodromeId);
|
||||||
|
if (h) this.selectedHippodromeLabel.set(`${h.nom} – (${h.ville})`);
|
||||||
|
else if (v.hippodrome?.nom) this.selectedHippodromeLabel.set(`${v.hippodrome.nom} – (${v.hippodrome.ville || ''})`);
|
||||||
|
} else if (v.hippodrome?.nom) {
|
||||||
|
this.selectedHippodromeLabel.set(`${v.hippodrome.nom} – (${v.hippodrome.ville || ''})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.form.markAsPristine();
|
||||||
|
this.form.markAsUntouched();
|
||||||
|
}
|
||||||
|
|
||||||
isInvalid(control: string): boolean {
|
isInvalid(control: string): boolean {
|
||||||
const c = this.form.get(control);
|
const c = this.form.get(control);
|
||||||
return !!(c && c.invalid && (c.touched || this.submitted));
|
return !!(c && c.invalid && (c.touched || this.submitted));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onToggleType(event: Event) {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
const value = input.value;
|
||||||
|
const current = this.form.value.typesParisOuverts ?? [];
|
||||||
|
|
||||||
|
this.form.patchValue({
|
||||||
|
typesParisOuverts: input.checked
|
||||||
|
? [...current, value]
|
||||||
|
: current.filter((v: string) => v !== value)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
this.submitted = true;
|
this.submitted = true;
|
||||||
|
|
||||||
@@ -196,10 +293,7 @@ onSubmit() {
|
|||||||
const hippodromeObj = foundHippodrome ?? (hippodromeId ? { id: +hippodromeId } : undefined);
|
const hippodromeObj = foundHippodrome ?? (hippodromeId ? { id: +hippodromeId } : undefined);
|
||||||
|
|
||||||
|
|
||||||
// 2️⃣ Transformer typesParisOuverts CSV → tableau
|
// 2️⃣ Transformer typesParisOuverts CSV → tablea
|
||||||
const typesParis = raw.typesParisOuverts
|
|
||||||
? raw.typesParisOuverts.split(',').map((s: string) => s.trim()).filter(Boolean)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// 3️⃣ Construire payload
|
// 3️⃣ Construire payload
|
||||||
const payload: Partial<CourseApiResponse> = {
|
const payload: Partial<CourseApiResponse> = {
|
||||||
@@ -211,10 +305,10 @@ onSubmit() {
|
|||||||
numero: raw.numero ? +raw.numero : undefined,
|
numero: raw.numero ? +raw.numero : undefined,
|
||||||
statut: raw.statut,
|
statut: raw.statut,
|
||||||
discipline: raw.discipline,
|
discipline: raw.discipline,
|
||||||
heureDepartPrevue: new Date(raw.reunionDate+raw.heureDepartPrevu).toISOString(),
|
heureDepartPrevue: new Date(`${raw.reunionDate}T${raw.heureDepartPrevue}:00`).toISOString(),
|
||||||
distanceMetres: raw.distanceMetres ? +raw.distanceMetres : undefined,
|
distanceMetres: raw.distanceMetres ? +raw.distanceMetres : undefined,
|
||||||
nombrePartants: raw.nombrePartants ? +raw.nombrePartants : undefined,
|
nombrePartants: raw.nombrePartants ? +raw.nombrePartants : undefined,
|
||||||
typesParisOuverts: typesParis,
|
typesParisOuverts: raw.typesParisOuverts,
|
||||||
annulee: raw.annulee ?? false,
|
annulee: raw.annulee ?? false,
|
||||||
reporteeMemeJour: raw.reporteeMemeJour ?? false,
|
reporteeMemeJour: raw.reporteeMemeJour ?? false,
|
||||||
reporteeAutreJour: raw.reporteeAutreJour ?? false,
|
reporteeAutreJour: raw.reporteeAutreJour ?? false,
|
||||||
@@ -223,17 +317,20 @@ onSubmit() {
|
|||||||
categorie: raw.categorie,
|
categorie: raw.categorie,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(payload);
|
|
||||||
// 4️⃣ Appeler le service (create ou update)
|
// Persist: create or update via CourseService, then emit the saved Course
|
||||||
if (this.value?.id) {
|
if (this.value && this.value.id) {
|
||||||
this.courseServive.update(this.value.id, payload).subscribe({
|
this.courseServive.update(this.value.id, payload).subscribe({
|
||||||
next: () => this.save.emit(payload),
|
next: (updated) => {
|
||||||
error: err => console.error('Erreur update course', err)
|
if (updated) this.save.emit(updated);
|
||||||
|
else console.error('Update returned empty result');
|
||||||
|
},
|
||||||
|
error: (err) => console.error('Error updating course:', err),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.courseServive.create(payload).subscribe({
|
this.courseServive.create(payload).subscribe({
|
||||||
next: () => this.save.emit(payload),
|
next: (created) => this.save.emit(created),
|
||||||
error: err => console.error(err)
|
error: (err) => console.error('Error creating course:', err),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,6 +369,7 @@ errorMessage(control: string): string | null {
|
|||||||
{ label: 'Brouillon', value: CourseStatut.BROUILLON },
|
{ label: 'Brouillon', value: CourseStatut.BROUILLON },
|
||||||
{ label: 'Validé', value: CourseStatut.VALIDE },
|
{ label: 'Validé', value: CourseStatut.VALIDE },
|
||||||
{ label: 'Fremé', value: CourseStatut.FERME },
|
{ label: 'Fremé', value: CourseStatut.FERME },
|
||||||
|
{label: 'Ouvert', value: CourseStatut.OUVERT},
|
||||||
{ label: 'Resultat Provisoire', value: CourseStatut.RESULTAT_PROVISOIRE },
|
{ label: 'Resultat Provisoire', value: CourseStatut.RESULTAT_PROVISOIRE },
|
||||||
{ label: 'Resultat officiel', value: CourseStatut.RESULTAT_OFFICIEL },
|
{ label: 'Resultat officiel', value: CourseStatut.RESULTAT_OFFICIEL },
|
||||||
{ label: 'Reglée', value: CourseStatut.REGLEE },
|
{ label: 'Reglée', value: CourseStatut.REGLEE },
|
||||||
|
|||||||
@@ -13,26 +13,24 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
<!-- Action Buttons -->
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-x-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex-1 px-3 py-2 text-sm rounded bg-gray-900 text-white hover:bg-gray-800 disabled:opacity-50"
|
class="flex-1 px-3 py-2 text-sm rounded bg-blue-400 text-white hover:bg-blue-600 disabled:opacity-50"
|
||||||
(click)="onSave()"
|
(click)="onSave()"
|
||||||
[disabled]="!canSave()"
|
[disabled]="!canSave()">
|
||||||
>
|
|
||||||
Enregistrer
|
Enregistrer
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="px-3 py-2 text-sm rounded border border-blue-300 bg-blue-50 text-blue-700 hover:bg-blue-100 disabled:opacity-50"
|
class="flex-1 px-3 py-2 text-sm rounded border border-blue-300 bg-blue-50 text-blue-700 hover:bg-blue-100 disabled:opacity-50"
|
||||||
(click)="validate.emit()"
|
(click)="validate.emit()"
|
||||||
[disabled]="!canValidate()"
|
[disabled]="!canValidate()">
|
||||||
>
|
|
||||||
Valider
|
Valider
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="px-3 py-2 text-sm rounded border border-emerald-300 bg-emerald-50 text-emerald-700 hover:bg-emerald-100 disabled:opacity-50"
|
class="flex-1 px-3 py-2 text-sm rounded border border-emerald-300 bg-emerald-50 text-emerald-700 hover:bg-emerald-100 disabled:opacity-50"
|
||||||
(click)="confirm.emit()"
|
(click)="confirm.emit()"
|
||||||
[disabled]="!canConfirm()"
|
[disabled]="!canConfirm()"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ type ResultatShape = { places: FormArray<FormGroup<PlaceRow>> };
|
|||||||
export class ResultatForm {
|
export class ResultatForm {
|
||||||
@Input() course!: Course;
|
@Input() course!: Course;
|
||||||
@Input() resultat?: Resultat | null;
|
@Input() resultat?: Resultat | null;
|
||||||
@Output() save = new EventEmitter<number[][]>();
|
@Output() save = new EventEmitter<{
|
||||||
|
horses: number[][];
|
||||||
|
typesParisOuverts: string[];
|
||||||
|
}>();
|
||||||
@Output() validate = new EventEmitter<void>();
|
@Output() validate = new EventEmitter<void>();
|
||||||
@Output() confirm = new EventEmitter<void>();
|
@Output() confirm = new EventEmitter<void>();
|
||||||
@Output() cancel = new EventEmitter<void>();
|
@Output() cancel = new EventEmitter<void>();
|
||||||
@@ -34,21 +37,40 @@ export class ResultatForm {
|
|||||||
places: new FormArray<FormGroup<PlaceRow>>([]),
|
places: new FormArray<FormGroup<PlaceRow>>([]),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
getRequiredHorses = (types: string[]): number => {
|
||||||
|
// Normalize types to uppercase strings for robust comparisons
|
||||||
|
const normalized = (types || []).map((t) => String(t).toUpperCase());
|
||||||
|
if (normalized.includes('QUINTE')) return 5;
|
||||||
|
if (normalized.includes('QUATRO')) return 4;
|
||||||
|
if (normalized.includes('TRIO') || normalized.includes('TRIO_ORDRE') || normalized.includes('TRIPLET')) return 3;
|
||||||
|
if (
|
||||||
|
normalized.includes('JUMELE_GAGNANT') ||
|
||||||
|
normalized.includes('JUMELE_PLACE') ||
|
||||||
|
normalized.includes('JUMELE_ORDRE')
|
||||||
|
)
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
if (normalized.includes('PLACE') || normalized.includes('GAGNANT')) return 1;
|
||||||
|
|
||||||
|
return 3; // Default
|
||||||
|
};
|
||||||
|
|
||||||
reqLen = computed(() =>
|
reqLen = computed(() =>
|
||||||
3
|
this.getRequiredHorses(this.course.typesParisOuverts)
|
||||||
);
|
);
|
||||||
maxNum = computed(() => this.course?.nombrePartants ?? 0);
|
maxNum = computed(() => this.course?.nombrePartants ?? 0);
|
||||||
npSet = computed(() => new Set(this.course?.nonPartants ?? []));
|
// Ensure non-partants are compared as strings to avoid type mismatches
|
||||||
statut = computed((): 'CREATED' | 'VALIDATED' | 'NONE' => {
|
npSet = computed(() => new Set((this.course?.nonPartants ?? []).map((v) => String(v))));
|
||||||
return this.resultat ? 'CREATED' : 'NONE';
|
statut = computed((): 'PROVISOIRE' | 'OFFICIEL' | 'ANNULE' | 'EN_ATTENTE' => {
|
||||||
|
return this.resultat ? 'PROVISOIRE' : 'EN_ATTENTE';
|
||||||
});
|
});
|
||||||
|
|
||||||
canValidate(): boolean {
|
canValidate(): boolean {
|
||||||
return this.statut() === 'CREATED';
|
return this.statut() === 'EN_ATTENTE';
|
||||||
}
|
}
|
||||||
|
|
||||||
canConfirm(): boolean {
|
canConfirm(): boolean {
|
||||||
return this.statut() === 'VALIDATED';
|
return this.statut() === 'PROVISOIRE';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper methods for template
|
// Helper methods for template
|
||||||
@@ -255,15 +277,25 @@ export class ResultatForm {
|
|||||||
const len = this.reqLen();
|
const len = this.reqLen();
|
||||||
|
|
||||||
// Extract existing places from resultat if available.
|
// Extract existing places from resultat if available.
|
||||||
// Backend now returns ordreArrivee as an array of cheval numbers and
|
// Be defensive: ordreArrivee may be provided as array or CSV string, normalize to number[]
|
||||||
// chevauxDeadHeat as the subset that are in dead-heat (ex-aequo).
|
|
||||||
let existing: number[][] = [];
|
let existing: number[][] = [];
|
||||||
if (this.resultat && this.resultat.ordreArrivee && this.resultat.ordreArrivee.length > 0) {
|
if (this.resultat && this.resultat.ordreArrivee && (this.resultat.ordreArrivee as any).length > 0) {
|
||||||
const deadHeatSet = new Set(this.resultat.chevauxDeadHeat || []);
|
const rawOrdre = (this.resultat as any).ordreArrivee;
|
||||||
|
const allHorses: number[] = ((): number[] => {
|
||||||
|
if (Array.isArray(rawOrdre)) return rawOrdre.map(Number).filter((n) => !Number.isNaN(n));
|
||||||
|
if (typeof rawOrdre === 'string') {
|
||||||
|
return rawOrdre
|
||||||
|
.split(/[^0-9]+/)
|
||||||
|
.map((s) => Number(s))
|
||||||
|
.filter((n) => !Number.isNaN(n));
|
||||||
|
}
|
||||||
|
const n = Number(rawOrdre);
|
||||||
|
return Number.isNaN(n) ? [] : [n];
|
||||||
|
})();
|
||||||
|
|
||||||
const allHorses = this.resultat.ordreArrivee;
|
const deadHeatSet = new Set(((this.resultat.chevauxDeadHeat ?? []) as any).map(Number));
|
||||||
const allInDeadHeat =
|
|
||||||
allHorses.every((num) => deadHeatSet.has(num)) && allHorses.length === len;
|
const allInDeadHeat = allHorses.every((num) => deadHeatSet.has(num)) && allHorses.length === len;
|
||||||
|
|
||||||
if (allInDeadHeat) {
|
if (allInDeadHeat) {
|
||||||
// All horses are in first place (ex-aequo)
|
// All horses are in first place (ex-aequo)
|
||||||
@@ -277,9 +309,9 @@ export class ResultatForm {
|
|||||||
const groups: number[][] = [];
|
const groups: number[][] = [];
|
||||||
let currentGroup: number[] = [];
|
let currentGroup: number[] = [];
|
||||||
|
|
||||||
this.resultat.ordreArrivee.forEach((num, index) => {
|
allHorses.forEach((num, index) => {
|
||||||
const isInDeadHeat = deadHeatSet.has(num);
|
const isInDeadHeat = deadHeatSet.has(num);
|
||||||
const prevNum = index > 0 ? this.resultat!.ordreArrivee[index - 1] : null;
|
const prevNum = index > 0 ? allHorses[index - 1] : null;
|
||||||
const prevIsInDeadHeat = prevNum !== null && deadHeatSet.has(prevNum);
|
const prevIsInDeadHeat = prevNum !== null && deadHeatSet.has(prevNum);
|
||||||
|
|
||||||
if (isInDeadHeat && prevIsInDeadHeat && currentGroup.length > 0) {
|
if (isInDeadHeat && prevIsInDeadHeat && currentGroup.length > 0) {
|
||||||
@@ -538,6 +570,6 @@ export class ResultatForm {
|
|||||||
.filter((v): v is number => typeof v === 'number');
|
.filter((v): v is number => typeof v === 'number');
|
||||||
places.push(placeHorses);
|
places.push(placeHorses);
|
||||||
}
|
}
|
||||||
this.save.emit(places);
|
this.save.emit({horses: places, typesParisOuverts: this.course.typesParisOuverts});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ export class ReunionForm implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
// Fetch hippodromes from API
|
// Fetch hippodromes from API
|
||||||
this.loadingHippodromes.set(true);
|
this.loadingHippodromes.set(true);
|
||||||
this.subs.add(
|
this.subs.add(
|
||||||
this.hippodromeService.list({ page: 1, perPage: 1000 }, false).subscribe({
|
this.hippodromeService.list({ page: 1, size: 1000 }, false).subscribe({
|
||||||
next: (result) => {
|
next: (result) => {
|
||||||
this.hippodromes.set(result.content);
|
this.hippodromes.set(result.content);
|
||||||
this.loadingHippodromes.set(false);
|
this.loadingHippodromes.set(false);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export class TableDataSource {
|
|||||||
|
|
||||||
readonly params = computed<ListParams>(() => ({
|
readonly params = computed<ListParams>(() => ({
|
||||||
page: this.page(),
|
page: this.page(),
|
||||||
perPage: this.perPage(),
|
size: this.perPage(),
|
||||||
search: this.search() || undefined,
|
search: this.search() || undefined,
|
||||||
sortKey: this.sortKey(),
|
sortKey: this.sortKey(),
|
||||||
sortDir: this.sortDir(),
|
sortDir: this.sortDir(),
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export class PaginatedHttpService {
|
|||||||
|
|
||||||
let httpParams = new HttpParams()
|
let httpParams = new HttpParams()
|
||||||
.set(pageParam, String(zeroBasedPageIndex ? Math.max(params.page - 1, 0) : params.page))
|
.set(pageParam, String(zeroBasedPageIndex ? Math.max(params.page - 1, 0) : params.page))
|
||||||
.set(sizeParam, String(params.perPage));
|
.set(sizeParam, String(params.size));
|
||||||
|
|
||||||
if (params.search) httpParams = httpParams.set(searchParam, params.search);
|
if (params.search) httpParams = httpParams.set(searchParam, params.search);
|
||||||
|
|
||||||
@@ -56,6 +56,6 @@ export class PaginatedHttpService {
|
|||||||
|
|
||||||
return this.http
|
return this.http
|
||||||
.get<any>(url, { params: httpParams, headers: this.getNgrokHeaders() })
|
.get<any>(url, { params: httpParams, headers: this.getNgrokHeaders() })
|
||||||
.pipe(map((raw) => normalizePage<T>(raw, params.page, params.perPage)));
|
.pipe(map((raw) => normalizePage<T>(raw, params.page, params.size)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export type SortDir = 'asc' | 'desc' | '';
|
|||||||
|
|
||||||
export interface ListParams {
|
export interface ListParams {
|
||||||
page: number; // 1-based for UI
|
page: number; // 1-based for UI
|
||||||
perPage: number;
|
size: number;
|
||||||
search?: string;
|
search?: string;
|
||||||
sortKey?: string;
|
sortKey?: string;
|
||||||
sortDir?: SortDir;
|
sortDir?: SortDir;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export const environment = {
|
export const environment = {
|
||||||
production: false,
|
production: false,
|
||||||
apiBaseUrl: 'https://ddd3b90fc1ef.ngrok-free.app',
|
apiBaseUrl: 'http://192.168.1.235:8280',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export const environment = {
|
export const environment = {
|
||||||
production: false,
|
production: false,
|
||||||
apiBaseUrl: 'https://ddd3b90fc1ef.ngrok-free.app',
|
apiBaseUrl: 'http://192.168.1.235:8280',
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user