first step for plr game platform
This commit is contained in:
@@ -21,6 +21,10 @@ const routes: Routes = [
|
||||
path: 'reunions',
|
||||
loadComponent: () => import('./pages/reunion/reunion').then((m) => m.ReunionList),
|
||||
},
|
||||
{
|
||||
path: 'rapport',
|
||||
loadComponent: () => import('./pages/rapport/rapport').then((m) => m.Rapport),
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
loadComponent: () => import('./pages/users/users').then((m) => m.UsersPage),
|
||||
|
||||
@@ -46,7 +46,7 @@ export class Layout {
|
||||
{ icon: '🏟️', label: 'Hippodromes', link: '/hippodromes' },
|
||||
{ icon: '📅', label: 'Reunions', link: '/reunions' },
|
||||
{ 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[] = [
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
<app-paginator
|
||||
[total]="total()"
|
||||
[page]="page()"
|
||||
[perPage]="perPage()"
|
||||
[perPage]="size()"
|
||||
(pageChange)="page.set($event)"
|
||||
(perPageChange)="perPage.set($event)"
|
||||
(perPageChange)="size.set($event)"
|
||||
></app-paginator>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ export class AgentsPage {
|
||||
loading = signal(false);
|
||||
|
||||
page = signal(1);
|
||||
perPage = signal(10);
|
||||
size = signal(10);
|
||||
search = signal('');
|
||||
sort = signal<SortState>({ key: 'code', dir: 'asc' });
|
||||
|
||||
@@ -148,7 +148,7 @@ export class AgentsPage {
|
||||
) {
|
||||
// Preload TPE maps for display
|
||||
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) => {
|
||||
const tpes = res.content as TpeDevice[];
|
||||
this.rebuildTpeMaps(tpes);
|
||||
@@ -156,7 +156,7 @@ export class AgentsPage {
|
||||
effect(() => {
|
||||
const params = {
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -167,7 +167,7 @@ export class AgentsPage {
|
||||
|
||||
private fetch(params: {
|
||||
page: number;
|
||||
perPage: number;
|
||||
size: number;
|
||||
search: string;
|
||||
sortKey: string;
|
||||
sortDir: SortDir;
|
||||
@@ -191,7 +191,7 @@ export class AgentsPage {
|
||||
|
||||
private refreshTpeMap() {
|
||||
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) => {
|
||||
const tpes = res.content as TpeDevice[];
|
||||
this.rebuildTpeMaps(tpes);
|
||||
@@ -381,7 +381,7 @@ export class AgentsPage {
|
||||
// Refresh data
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -399,7 +399,7 @@ export class AgentsPage {
|
||||
this.api.delete(row.id).subscribe(() =>
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
|
||||
@@ -106,10 +106,10 @@
|
||||
</app-data-table>
|
||||
|
||||
<app-paginator
|
||||
[page]="page()"
|
||||
[page]="page() + 1"
|
||||
[perPage]="perPage()"
|
||||
[total]="total()"
|
||||
(pageChange)="page.set($event)"
|
||||
(pageChange)="page.set($event - 1)"
|
||||
(perPageChange)="perPage.set($event)"
|
||||
[pageSizes]="pageSize"
|
||||
/>
|
||||
@@ -158,12 +158,12 @@
|
||||
<app-resultat-form
|
||||
[course]="selectedCourseForResultat()!"
|
||||
[resultat]="resultatsMap().get(selectedCourseForResultat()!.id)"
|
||||
(save)="onResultatSave($event)"
|
||||
(save)="onResultatSave($event.horses, $event.typesParisOuverts)"
|
||||
(validate)="onResultatValidate()"
|
||||
(confirm)="onResultatConfirm()"
|
||||
(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>
|
||||
</div>
|
||||
</app-modal>
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Course as CourseType } from 'src/app/core/interfaces/course';
|
||||
import { SortDir } from '@shared/paging/paging';
|
||||
import { CourseApiResponse, CourseService } from 'src/app/core/services/course';
|
||||
import { ResultatService } from 'src/app/core/services/resultat';
|
||||
import { Resultat } from 'src/app/core/interfaces/resultat';
|
||||
import { Resultat, ResultatStatut } from 'src/app/core/interfaces/resultat';
|
||||
import { A11yModule } from '@angular/cdk/a11y';
|
||||
import { CourseForm } from '@shared/forms/course-form/course-form';
|
||||
import { NonPartantForm } from '@shared/forms/nonpartant-form/nonpartant-form';
|
||||
@@ -57,10 +57,10 @@ export class Course {
|
||||
totalClosed = signal(0);
|
||||
totalByType = signal<Record<string, number>>({});
|
||||
|
||||
page = signal(1);
|
||||
page = signal(0);
|
||||
perPage = signal(10);
|
||||
search = signal('');
|
||||
sort = signal<SortState>({ key: 'numero', dir: 'asc' });
|
||||
sort = signal<SortState>({ key: 'id', dir: 'asc' });
|
||||
pageSize = [10, 20, 50];
|
||||
|
||||
modalOpen = signal(false);
|
||||
@@ -102,40 +102,42 @@ export class Course {
|
||||
return '<span class="text-gray-500 dark:text-gray-400">—</span>';
|
||||
}
|
||||
|
||||
// Group horses that are at the same place (ex-aequo/dead heat).
|
||||
// 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 || []);
|
||||
return `<span class="text-gray-500 dark:text-gray-400">${resultat.ordreArrivee}</span>`
|
||||
|
||||
const groups: number[][] = [];
|
||||
let currentGroup: number[] = [];
|
||||
// // Group horses that are at the same place (ex-aequo/dead heat).
|
||||
// // 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 isInDeadHeat = deadHeatSet.has(num);
|
||||
const prevNum = index > 0 ? resultat.ordreArrivee[index - 1] : null;
|
||||
const prevIsInDeadHeat = prevNum !== null && deadHeatSet.has(prevNum);
|
||||
// const groups: number[][] = [];
|
||||
// let currentGroup: number[] = [];
|
||||
|
||||
if (isInDeadHeat && prevIsInDeadHeat && currentGroup.length > 0) {
|
||||
// Continue the current dead heat group
|
||||
currentGroup.push(num);
|
||||
} else {
|
||||
// Start a new group
|
||||
if (currentGroup.length > 0) {
|
||||
groups.push(currentGroup);
|
||||
}
|
||||
currentGroup = [num];
|
||||
}
|
||||
});
|
||||
// resultat.ordreArrivee.forEach((num, index) => {
|
||||
// const isInDeadHeat = deadHeatSet.has(num);
|
||||
// const prevNum = index > 0 ? resultat.ordreArrivee[index - 1] : null;
|
||||
// const prevIsInDeadHeat = prevNum !== null && deadHeatSet.has(prevNum);
|
||||
|
||||
// Don't forget the last group
|
||||
if (currentGroup.length > 0) {
|
||||
groups.push(currentGroup);
|
||||
}
|
||||
// if (isInDeadHeat && prevIsInDeadHeat && currentGroup.length > 0) {
|
||||
// // Continue the current dead heat group
|
||||
// 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
|
||||
return `<span class="mr-2">${s}</span>`;
|
||||
// const s = groups.map((nums) => nums.join('=')).join(' - ');
|
||||
|
||||
// // 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(() => {
|
||||
const params = {
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -198,7 +200,7 @@ export class Course {
|
||||
|
||||
private fetch(params: {
|
||||
page: number;
|
||||
perPage: number;
|
||||
size: number;
|
||||
search: string;
|
||||
sortKey: string;
|
||||
sortDir: SortDir;
|
||||
@@ -256,7 +258,7 @@ export class Course {
|
||||
// === UI Actions ===
|
||||
onSearch(q: string) {
|
||||
this.search.set(q);
|
||||
this.page.set(1);
|
||||
this.page.set(0);
|
||||
}
|
||||
|
||||
openCreate() {
|
||||
@@ -283,21 +285,15 @@ export class Course {
|
||||
this.formComp?.onSubmit();
|
||||
}
|
||||
|
||||
onFormSave(payload: Partial<CourseType>) {
|
||||
const current = this.editingItem();
|
||||
const req$ = current?.id
|
||||
? this.api.update(current.id, payload)
|
||||
: this.api.create(payload as Omit<CourseApiResponse, 'id'>);
|
||||
|
||||
req$.subscribe(() => {
|
||||
this.closeModal();
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir,
|
||||
});
|
||||
onFormSave(_: CourseType) {
|
||||
// The form now persists create/update itself. Just close and refresh.
|
||||
this.closeModal();
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
size: 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.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir,
|
||||
@@ -360,7 +356,7 @@ export class Course {
|
||||
this.closeNonPartantModal();
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir,
|
||||
@@ -381,20 +377,22 @@ export class Course {
|
||||
this.selectedCourseForResultat.set(null);
|
||||
}
|
||||
|
||||
onResultatSave(places: number[][]) {
|
||||
onResultatSave(places: number[][], typesParisOuverts: string[]) {
|
||||
const c = this.selectedCourseForResultat();
|
||||
if (!c) return;
|
||||
|
||||
// Determine required number of horses based on course type
|
||||
const getRequiredHorses = (type: string): number => {
|
||||
const typeStr = String(type).toUpperCase();
|
||||
if (typeStr.includes('TIERCE') || typeStr === 'PLAT') return 3;
|
||||
if (typeStr.includes('QUARTE')) return 4;
|
||||
const getRequiredHorses = (types: string[]): number => {
|
||||
const typeStr = types;
|
||||
if (typeStr.includes('PLACE') || typeStr.includes('GAGNANT')) return 3;
|
||||
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;
|
||||
return 3; // Default
|
||||
};
|
||||
|
||||
const requiredHorses = 3;
|
||||
const requiredHorses = getRequiredHorses(typesParisOuverts);
|
||||
|
||||
// Collect all selected horses (flatten the places array)
|
||||
const allHorses: number[] = places
|
||||
@@ -409,46 +407,28 @@ export class Course {
|
||||
// Convert to ordreArrivee format
|
||||
// If all are ex-aequo, they all go in ordreArrivee as they are (first place)
|
||||
// Otherwise, distribute them across places
|
||||
const ordreArrivee: Array<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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
let ordreArrivee: string = '';
|
||||
|
||||
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
|
||||
const existingResultat = this.resultatsMap().get(c.id);
|
||||
|
||||
const payload = {
|
||||
course: { id: c.id },
|
||||
courseId: Number(c.id) ,
|
||||
ordreArrivee,
|
||||
chevauxDeadHeat: chevauxDeadHeat.map((n) => String(n)),
|
||||
totalMises: 0,
|
||||
masseAPartager: 0,
|
||||
prelevementsLegaux: 0,
|
||||
montantRembourse: 0,
|
||||
montantCagnotte: 0,
|
||||
adeadHeat: chevauxDeadHeat.length > 0,
|
||||
statut: ResultatStatut.EN_ATTENTE,
|
||||
};
|
||||
|
||||
const request$ = existingResultat
|
||||
@@ -460,7 +440,7 @@ export class Course {
|
||||
this.closeResultatModal();
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir,
|
||||
@@ -490,7 +470,7 @@ export class Course {
|
||||
this.closeResultatModal();
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir,
|
||||
@@ -520,7 +500,7 @@ export class Course {
|
||||
this.closeResultatModal();
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir,
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
[page]="page()"
|
||||
[perPage]="perPage()"
|
||||
[total]="total()"
|
||||
(pageChange)="page.set($event)"
|
||||
(pageChange)="page.set($event - 1)"
|
||||
(perPageChange)="onPerPage($event)"
|
||||
[pageSizes]="pageSize"
|
||||
/>
|
||||
|
||||
@@ -46,7 +46,7 @@ export class Hippodrome {
|
||||
totalReunions = signal(0);
|
||||
totalCourses = signal(0);
|
||||
|
||||
page = signal(1);
|
||||
page = signal(0);
|
||||
perPage = signal(10);
|
||||
pageSize = [10, 20, 50];
|
||||
search = signal('');
|
||||
@@ -105,7 +105,7 @@ export class Hippodrome {
|
||||
this.api
|
||||
.list({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir,
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</ng-template>
|
||||
</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>
|
||||
|
||||
<app-modal [open]="modalOpen()" [title]="modalTitle()" (close)="closeModal()" size="xl">
|
||||
|
||||
@@ -24,7 +24,7 @@ export class LimitsPage implements OnInit {
|
||||
total = signal(0);
|
||||
loading = signal(false);
|
||||
page = signal(1);
|
||||
perPage = signal(10);
|
||||
size = signal(10);
|
||||
search = signal('');
|
||||
sort = signal<SortState>({ key: 'code', dir: 'asc' });
|
||||
selectedActif = signal<boolean | null>(null);
|
||||
@@ -92,11 +92,11 @@ export class LimitsPage implements OnInit {
|
||||
|
||||
constructor(private api: AgentLimitService) {
|
||||
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 params = {
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: searchValue,
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -120,7 +120,7 @@ export class LimitsPage implements OnInit {
|
||||
// If empty, use normal list
|
||||
return this.api.list({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: '',
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -155,14 +155,14 @@ export class LimitsPage implements OnInit {
|
||||
// Initial fetch
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
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
|
||||
const searchQuery = params.search.trim();
|
||||
if (searchQuery) {
|
||||
@@ -214,7 +214,7 @@ export class LimitsPage implements OnInit {
|
||||
// If empty, fetch normally
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: '',
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -227,7 +227,7 @@ export class LimitsPage implements OnInit {
|
||||
this.page.set(1);
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -261,7 +261,7 @@ export class LimitsPage implements OnInit {
|
||||
this.closeModal();
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -282,7 +282,7 @@ export class LimitsPage implements OnInit {
|
||||
this.api.delete(row.id).subscribe(() => {
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
|
||||
@@ -118,7 +118,7 @@ export class Main {
|
||||
private baseParams(): ListParams {
|
||||
return {
|
||||
page: 1,
|
||||
perPage: 1,
|
||||
size: 1,
|
||||
search: '',
|
||||
sortKey: 'id',
|
||||
sortDir: 'asc' as SortDir,
|
||||
@@ -158,7 +158,7 @@ export class Main {
|
||||
.list(params, true)
|
||||
.pipe(catchError(() => of({ data: [], meta: { total: 0 } } as any))),
|
||||
courses: this.courseService
|
||||
.list(coursesParams, true)
|
||||
.list(coursesParams)
|
||||
.pipe(catchError(() => of({ data: [], meta: { total: 0 } } as any))),
|
||||
roles: this.roleService
|
||||
.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
|
||||
[page]="page()"
|
||||
[perPage]="perPage()"
|
||||
[perPage]="size()"
|
||||
[total]="total()"
|
||||
(pageChange)="page.set($event)"
|
||||
(perPageChange)="perPage.set($event)"
|
||||
(perPageChange)="size.set($event)"
|
||||
[pageSizes]="pageSize"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,7 @@ export class ReunionList {
|
||||
|
||||
// pagination, sorting, search
|
||||
page = signal(1);
|
||||
perPage = signal(10);
|
||||
size = signal(10);
|
||||
search = signal('');
|
||||
sort = signal<SortState>({ key: 'date', dir: 'asc' });
|
||||
pageSize = [10, 20, 50];
|
||||
@@ -134,7 +134,7 @@ export class ReunionList {
|
||||
effect(() => {
|
||||
const params = {
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir,
|
||||
@@ -145,7 +145,7 @@ export class ReunionList {
|
||||
|
||||
private fetch(params: {
|
||||
page: number;
|
||||
perPage: number;
|
||||
size: number;
|
||||
search: string;
|
||||
sortKey: string;
|
||||
sortDir: SortDir;
|
||||
@@ -211,7 +211,7 @@ export class ReunionList {
|
||||
// refetch current page
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir,
|
||||
@@ -224,7 +224,7 @@ export class ReunionList {
|
||||
this.api.delete(row.id).subscribe(() =>
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir,
|
||||
|
||||
@@ -39,7 +39,7 @@ export class RolesPage {
|
||||
loading = signal(false);
|
||||
permissions = signal<Permission[]>([]);
|
||||
page = signal(1);
|
||||
perPage = signal(10);
|
||||
size = signal(10);
|
||||
search = signal('');
|
||||
sort = signal<SortState>({ key: 'name', dir: 'asc' });
|
||||
|
||||
@@ -65,7 +65,7 @@ export class RolesPage {
|
||||
effect(() => {
|
||||
const params = {
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -76,7 +76,7 @@ export class RolesPage {
|
||||
|
||||
private fetch(params: {
|
||||
page: number;
|
||||
perPage: number;
|
||||
size: number;
|
||||
search: string;
|
||||
sortKey: string;
|
||||
sortDir: SortDir;
|
||||
@@ -136,7 +136,7 @@ export class RolesPage {
|
||||
);
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
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`);
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.size(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
|
||||
@@ -152,7 +152,7 @@ export class TpePage implements OnInit {
|
||||
const searchValue = this.search();
|
||||
const params = {
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: searchValue,
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -175,7 +175,7 @@ export class TpePage implements OnInit {
|
||||
// If empty, use normal list
|
||||
return this.api.list({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: '',
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -211,7 +211,7 @@ export class TpePage implements OnInit {
|
||||
if (!this.search().trim()) {
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: '',
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -249,7 +249,7 @@ export class TpePage implements OnInit {
|
||||
|
||||
private fetch(params: {
|
||||
page: number;
|
||||
perPage: number;
|
||||
size: number;
|
||||
search: string;
|
||||
sortKey: string;
|
||||
sortDir: SortDir;
|
||||
@@ -305,7 +305,7 @@ export class TpePage implements OnInit {
|
||||
// If empty, fetch normally
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: '',
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -318,7 +318,7 @@ export class TpePage implements OnInit {
|
||||
this.page.set(1);
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -331,7 +331,7 @@ export class TpePage implements OnInit {
|
||||
next: () => {
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -347,7 +347,7 @@ export class TpePage implements OnInit {
|
||||
next: () => {
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -393,7 +393,7 @@ export class TpePage implements OnInit {
|
||||
this.selectedAgentId.set('');
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -456,7 +456,7 @@ export class TpePage implements OnInit {
|
||||
// Refresh data
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -476,7 +476,7 @@ export class TpePage implements OnInit {
|
||||
this.api.delete(row.id).subscribe(() => {
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
|
||||
@@ -83,7 +83,7 @@ export class UsersPage {
|
||||
effect(() => {
|
||||
const params = {
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
@@ -94,7 +94,7 @@ export class UsersPage {
|
||||
|
||||
private fetch(params: {
|
||||
page: number;
|
||||
perPage: number;
|
||||
size: number;
|
||||
search: string;
|
||||
sortKey: string;
|
||||
sortDir: SortDir;
|
||||
@@ -156,7 +156,7 @@ export class UsersPage {
|
||||
);
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
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`);
|
||||
this.fetch({
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
size: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
|
||||
Reference in New Issue
Block a user