detail on course
This commit is contained in:
41
src/app/core/interfaces/gain.ts
Normal file
41
src/app/core/interfaces/gain.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { Course, CourseType } from "./course";
|
||||||
|
|
||||||
|
export type TypeFormule =
|
||||||
|
| 'UNITAIRE'
|
||||||
|
| 'CHAMP_X'
|
||||||
|
| 'CHAMP_TOTAL'
|
||||||
|
| 'FORMULE_COMPLETE';
|
||||||
|
|
||||||
|
|
||||||
|
export interface RapportDetail {
|
||||||
|
id: number;
|
||||||
|
libelle: string;
|
||||||
|
rapport: number;
|
||||||
|
nombreGagnants: number;
|
||||||
|
massePartageeRang: number;
|
||||||
|
gainsFormule: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface Formule {
|
||||||
|
id: number;
|
||||||
|
gains: string;
|
||||||
|
typePari: CourseType;
|
||||||
|
typeFormule: TypeFormule;
|
||||||
|
masseInitiale: number;
|
||||||
|
masseApresPrelevements: number;
|
||||||
|
masseFinale: number;
|
||||||
|
totalPari: number;
|
||||||
|
totalGagnants: number;
|
||||||
|
rapportsDetails: RapportDetail[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface ResultatCagnotte {
|
||||||
|
id: number;
|
||||||
|
course: Course;
|
||||||
|
montantCagnotte: number;
|
||||||
|
montantARembourser: number;
|
||||||
|
dateCalcul: string;
|
||||||
|
formules: Formule[];
|
||||||
|
}
|
||||||
@@ -157,7 +157,6 @@ export class CourseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getById(id: string): Observable<Course | undefined> {
|
getById(id: string): Observable<Course | undefined> {
|
||||||
if (USE_SERVER) {
|
|
||||||
return this.http
|
return this.http
|
||||||
.get<CourseApiResponse>(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() })
|
.get<CourseApiResponse>(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() })
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -165,9 +164,6 @@ export class CourseService {
|
|||||||
// Fetch the reunion (non-partants are already included in the API response)
|
// Fetch the reunion (non-partants are already included in the API response)
|
||||||
return this.hippodromeService.getById(String(apiCourse.hippodromeId)).pipe(
|
return this.hippodromeService.getById(String(apiCourse.hippodromeId)).pipe(
|
||||||
map((hippodrome) => {
|
map((hippodrome) => {
|
||||||
if (!hippodrome) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
id: String(apiCourse.id),
|
id: String(apiCourse.id),
|
||||||
hippodrome: hippodrome ?? undefined,
|
hippodrome: hippodrome ?? undefined,
|
||||||
@@ -196,8 +192,6 @@ export class CourseService {
|
|||||||
return of(undefined);
|
return of(undefined);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
|
||||||
return of(undefined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getByReunionId(reunionId: string): Observable<Course[]> {
|
// getByReunionId(reunionId: string): Observable<Course[]> {
|
||||||
|
|||||||
@@ -8,15 +8,10 @@ import { environment } from 'src/environments/environment.development';
|
|||||||
|
|
||||||
export interface ResultatCourse {
|
export interface ResultatCourse {
|
||||||
id: number;
|
id: number;
|
||||||
course: Course;
|
course: Partial<Course>;
|
||||||
statut: ResultatStatut;
|
statut: ResultatStatut;
|
||||||
ordreArrivee: string;
|
ordreArrivee: string;
|
||||||
datePublication?: string; // ISO string
|
datePublication?: string;
|
||||||
dateValidation?: string; // ISO string
|
|
||||||
dateAnnulation?: string; // ISO string
|
|
||||||
notes?: string;
|
|
||||||
createdAt?: string;
|
|
||||||
updatedAt?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_BASE = '/api/v1/depouillement';
|
const API_BASE = '/api/v1/depouillement';
|
||||||
|
|||||||
16
src/app/core/services/gain.spec.ts
Normal file
16
src/app/core/services/gain.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { Gain } from './gain';
|
||||||
|
|
||||||
|
describe('Gain', () => {
|
||||||
|
let service: Gain;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(Gain);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
107
src/app/core/services/gain.ts
Normal file
107
src/app/core/services/gain.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { map, catchError, switchMap } from 'rxjs/operators';
|
||||||
|
import { ResultatCagnotte, Formule } from '../interfaces/gain';
|
||||||
|
import { normalizePage } from '@shared/paging/normalize-page';
|
||||||
|
import { PaginatedHttpService } from '@shared/paging/paginated-http.service';
|
||||||
|
import { ListParams, PagedResult } from '@shared/paging/paging';
|
||||||
|
import { environment } from 'src/environments/environment.development';
|
||||||
|
import { ServicesUtils } from './services-utils';
|
||||||
|
|
||||||
|
const USE_SERVER = true;
|
||||||
|
const API_BASE = '/api/v1/gains';
|
||||||
|
|
||||||
|
export interface ResultatCagnotteApi extends ResultatCagnotte {}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class Gain {
|
||||||
|
private apiUrl = environment.depouillementBaseUrl + API_BASE;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient, private pager: PaginatedHttpService, private servicesUtil: ServicesUtils) {}
|
||||||
|
|
||||||
|
private getNgrokHeaders(): Record<string, string> {
|
||||||
|
const isNgrok =
|
||||||
|
environment.apiBaseUrl.includes('ngrok-free.app') ||
|
||||||
|
environment.apiBaseUrl.includes('ngrok.io') ||
|
||||||
|
environment.apiBaseUrl.includes('ngrok');
|
||||||
|
return isNgrok ? { 'ngrok-skip-browser-warning': 'true' } : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// List (paginated) ResultatCagnotte
|
||||||
|
list(params: ListParams): Observable<PagedResult<ResultatCagnotte>> {
|
||||||
|
if (USE_SERVER) {
|
||||||
|
const url = this.apiUrl;
|
||||||
|
return this.pager.fetch<ResultatCagnotteApi>(url, params).pipe(
|
||||||
|
map((res) => {
|
||||||
|
const content = (res.content ?? []).map((api) => api as ResultatCagnotte);
|
||||||
|
return {
|
||||||
|
pageable: res.pageable,
|
||||||
|
totalPages: res.totalPages,
|
||||||
|
totalElements: res.totalElements,
|
||||||
|
content,
|
||||||
|
} as PagedResult<ResultatCagnotte>;
|
||||||
|
}),
|
||||||
|
catchError((err) => {
|
||||||
|
console.error('Error fetching gains list:', err);
|
||||||
|
return of({ content: [], pageable: { pageNumber: 1, pageSize: 0, total: 0 }, totalPages: 1, totalElements: 0 } as PagedResult<ResultatCagnotte>);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return of({ content: [], pageable: { pageNumber: 1, pageSize: 0, total: 0 }, totalPages: 1, totalElements: 0 } as PagedResult<ResultatCagnotte>);
|
||||||
|
}
|
||||||
|
|
||||||
|
getById(id: string): Observable<ResultatCagnotte | undefined> {
|
||||||
|
if (USE_SERVER) {
|
||||||
|
return this.http.get<ResultatCagnotteApi>(`${this.apiUrl}/rapport/${id}`, { headers: this.getNgrokHeaders() }).pipe(
|
||||||
|
map((api) => api as ResultatCagnotte),
|
||||||
|
catchError((err) => {
|
||||||
|
console.error(`Error fetching gain ${id}:`, err);
|
||||||
|
return of(undefined);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return of(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
create(payload: Partial<ResultatCagnotteApi>): Observable<ResultatCagnotte> {
|
||||||
|
if (USE_SERVER) {
|
||||||
|
return this.http.post<ResultatCagnotteApi>(this.apiUrl, payload, { headers: this.getNgrokHeaders() }).pipe(
|
||||||
|
map((api) => api as ResultatCagnotte),
|
||||||
|
catchError((err) => {
|
||||||
|
console.error('Error creating gain:', err);
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw new Error('Server mode required');
|
||||||
|
}
|
||||||
|
|
||||||
|
update(id: string, payload: Partial<ResultatCagnotteApi>): Observable<ResultatCagnotte | undefined> {
|
||||||
|
if (USE_SERVER) {
|
||||||
|
return this.http.put<ResultatCagnotteApi>(`${this.apiUrl}/${id}`, payload, { headers: this.getNgrokHeaders() }).pipe(
|
||||||
|
map((api) => api as ResultatCagnotte),
|
||||||
|
catchError((err) => {
|
||||||
|
console.error(`Error updating gain ${id}:`, err);
|
||||||
|
return of(undefined);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return of(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(id: string): Observable<void> {
|
||||||
|
if (USE_SERVER) {
|
||||||
|
return this.http.delete<void>(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() }).pipe(
|
||||||
|
catchError((err) => {
|
||||||
|
console.error(`Error deleting gain ${id}:`, err);
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return of(void 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,14 @@ const routes: Routes = [
|
|||||||
path: 'resultat',
|
path: 'resultat',
|
||||||
loadComponent: () => import('./pages/rapport/rapport').then((m) => m.Rapport),
|
loadComponent: () => import('./pages/rapport/rapport').then((m) => m.Rapport),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'gains',
|
||||||
|
loadComponent: () => import('./pages/gains/gains').then((m) => m.Gains),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'gains/:id',
|
||||||
|
loadComponent: () => import('./pages/gain-details/gain-details').then((m) => m.GainDetails),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'users',
|
path: 'users',
|
||||||
loadComponent: () => import('./pages/users/users').then((m) => m.UsersPage),
|
loadComponent: () => import('./pages/users/users').then((m) => m.UsersPage),
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export class Layout {
|
|||||||
{ 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: 'Résultats des courses', link: '/resultat' },
|
{ icon: 'icon-chart-bar', label: 'Résultats des courses', link: '/resultat' },
|
||||||
|
{ icon: '💰', label: 'Gains (cagnotte)', link: '/gains' },
|
||||||
];
|
];
|
||||||
|
|
||||||
workspaceMenuItems: MenuItem[] = [
|
workspaceMenuItems: MenuItem[] = [
|
||||||
|
|||||||
16
src/app/dashboard/pages/gain-details/gain-details.css
Normal file
16
src/app/dashboard/pages/gain-details/gain-details.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/* Cohesive styles for gain-details */
|
||||||
|
.space-y-4 > * + * { margin-top: 1rem; }
|
||||||
|
.text-muted { color: var(--muted-foreground, #6b7280); }
|
||||||
|
.bg-accent { background-color: var(--accent, #f3f4f6); }
|
||||||
|
.p-2 { padding: 0.5rem; }
|
||||||
|
.p-3 { padding: 0.75rem; }
|
||||||
|
.p-4 { padding: 1rem; }
|
||||||
|
.rounded-md { border-radius: 0.375rem; }
|
||||||
|
.border { border: 1px solid var(--border, #e5e7eb); }
|
||||||
|
.font-medium { font-weight: 600; }
|
||||||
|
|
||||||
|
/* Responsive tweaks */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||||
|
}
|
||||||
|
|
||||||
65
src/app/dashboard/pages/gain-details/gain-details.html
Normal file
65
src/app/dashboard/pages/gain-details/gain-details.html
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
@if(detail()){
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h2 class="text-2xl font-semibold">Détails du gain — Course n° {{ detail()!.course.numero }}</h2>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<a z-button routerLink="/gains" zType="ghost"><i class="icon-arrow-left mr-2"></i>Retour</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<z-card class="p-4">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-3 text-sm">
|
||||||
|
<div>
|
||||||
|
<span class="font-medium">Date:</span>
|
||||||
|
{{ detail()!.course.reunionDate | date : 'dd/MM/yyyy' }}
|
||||||
|
</div>
|
||||||
|
<div><span class="font-medium">Nom:</span> {{ detail()!.course.nom }}</div>
|
||||||
|
<div><span class="font-medium">Montant cagnotte:</span> {{ detail()!.montantCagnotte | number : '1.0-0' : 'fr-FR' }} CFA</div>
|
||||||
|
<div><span class="font-medium">Montant à rembourser:</span> {{ detail()!.montantARembourser | number : '1.0-0' : 'fr-FR' }} CFA</div>
|
||||||
|
</div>
|
||||||
|
</z-card>
|
||||||
|
|
||||||
|
@if(detail()!.formules && detail()!.formules.length > 0) {
|
||||||
|
<div class="space-y-4">
|
||||||
|
@for (f of detail()!.formules; track f.id || $index) {
|
||||||
|
<z-card class="p-3">
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<div class="text-sm font-medium">Formule: {{ f.gains }} — {{ f.typeFormule }}</div>
|
||||||
|
<div class="text-sm text-muted">Type pari: {{ f.typePari }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-2 text-sm mb-3">
|
||||||
|
<div><span class="font-medium">Masse initiale:</span> {{ f.masseInitiale | number }}</div>
|
||||||
|
<div><span class="font-medium">Masse après prélèvements:</span> {{ f.masseApresPrelevements | number }}</div>
|
||||||
|
<div><span class="font-medium">Masse finale:</span> {{ f.masseFinale | number }}</div>
|
||||||
|
<div><span class="font-medium">Total gagnants:</span> {{ f.totalGagnants }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rounded-md border overflow-hidden">
|
||||||
|
<table class="w-full text-sm">
|
||||||
|
<thead class="bg-accent">
|
||||||
|
<tr>
|
||||||
|
<th class="text-left p-2">Libellé</th>
|
||||||
|
<th class="text-left p-2">Rapport</th>
|
||||||
|
<th class="text-left p-2">Nombre gagnants</th>
|
||||||
|
<th class="text-left p-2">Masse partagée</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (r of f.rapportsDetails; track r.id || $index) {
|
||||||
|
<tr class="border-t">
|
||||||
|
<td class="p-2">{{ r.libelle }}</td>
|
||||||
|
<td class="p-2">{{ r.rapport }}</td>
|
||||||
|
<td class="p-2">{{ r.nombreGagnants }}</td>
|
||||||
|
<td class="p-2">{{ r.massePartageeRang | number }}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</z-card>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
23
src/app/dashboard/pages/gain-details/gain-details.spec.ts
Normal file
23
src/app/dashboard/pages/gain-details/gain-details.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { GainDetails } from './gain-details';
|
||||||
|
|
||||||
|
describe('GainDetails', () => {
|
||||||
|
let component: GainDetails;
|
||||||
|
let fixture: ComponentFixture<GainDetails>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [GainDetails]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(GainDetails);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
37
src/app/dashboard/pages/gain-details/gain-details.ts
Normal file
37
src/app/dashboard/pages/gain-details/gain-details.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
||||||
|
import { ActivatedRoute, RouterModule, Router } from '@angular/router';
|
||||||
|
import { ZardCardComponent } from '@shared/components/card/card.component';
|
||||||
|
import { ZardButtonComponent } from '@shared/components/button/button.component';
|
||||||
|
import { Gain } from 'src/app/core/services/gain';
|
||||||
|
import { ResultatCagnotte } from 'src/app/core/interfaces/gain';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-gain-details',
|
||||||
|
templateUrl: './gain-details.html',
|
||||||
|
styleUrl: './gain-details.css',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [CommonModule, RouterModule, ZardCardComponent, ZardButtonComponent],
|
||||||
|
})
|
||||||
|
export class GainDetails {
|
||||||
|
detail = signal<ResultatCagnotte | undefined>(undefined);
|
||||||
|
|
||||||
|
constructor(private route: ActivatedRoute, private api: Gain, private router: Router) {
|
||||||
|
const id = this.route.snapshot.params['id'];
|
||||||
|
if (!id) {
|
||||||
|
// nothing to show, go back
|
||||||
|
this.router.navigate(['/gains']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.api.getById(String(id)).subscribe((d) => {
|
||||||
|
console.log(d);
|
||||||
|
this.detail.set(d);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack() {
|
||||||
|
this.router.navigate(['/gains']);
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/app/dashboard/pages/gains/gains.css
Normal file
0
src/app/dashboard/pages/gains/gains.css
Normal file
34
src/app/dashboard/pages/gains/gains.html
Normal file
34
src/app/dashboard/pages/gains/gains.html
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h2 class="text-2xl font-semibold">Gains — Cagnotte</h2>
|
||||||
|
<z-button zType="default" (click)="fetchPage()">Récupérer les gains</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 détail" (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/gains/gains.spec.ts
Normal file
23
src/app/dashboard/pages/gains/gains.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { Gains } from './gains';
|
||||||
|
|
||||||
|
describe('Gains', () => {
|
||||||
|
let component: Gains;
|
||||||
|
let fixture: ComponentFixture<Gains>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [Gains]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(Gains);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
84
src/app/dashboard/pages/gains/gains.ts
Normal file
84
src/app/dashboard/pages/gains/gains.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
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 { Gain } from 'src/app/core/services/gain';
|
||||||
|
import { ResultatCagnotte } from 'src/app/core/interfaces/gain';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-gains',
|
||||||
|
templateUrl: './gains.html',
|
||||||
|
styleUrl: './gains.css',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [CommonModule, DataTable, ZardButtonComponent, ZardPaginationModule],
|
||||||
|
})
|
||||||
|
export class Gains {
|
||||||
|
rows = signal<ResultatCagnotte[]>([]);
|
||||||
|
loading = signal(false);
|
||||||
|
// Pagination
|
||||||
|
page = signal<number>(1);
|
||||||
|
perPage = signal<number>(10);
|
||||||
|
totalPages = signal<number>(1);
|
||||||
|
totalElements = signal<number>(0);
|
||||||
|
|
||||||
|
cols: TableColumn<ResultatCagnotte>[] = [
|
||||||
|
{ key: 'course.nom', label: 'Course' , cell: (r) => r.course?.nom ?? '—'},
|
||||||
|
{ key: 'montantCagnotte', label: 'Montant cagnotte', cell: (r) => `${r.montantCagnotte.toLocaleString('fr-FR')} CFA` },
|
||||||
|
{ key: 'montantARembourser', label: 'Montant à rembourser', cell: (r) => `${r.montantARembourser.toLocaleString('fr-FR')} CFA` },
|
||||||
|
{ key: 'dateCalcul', label: 'Date calcul', cell: (r) => r.dateCalcul ?? '—' },
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(private gainService: Gain, private router: Router) {
|
||||||
|
this.fetchPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchPage(params?: Partial<ListParams>) {
|
||||||
|
this.loading.set(true);
|
||||||
|
const p: ListParams = { page: this.page(), size: this.perPage(), ...(params || {}) };
|
||||||
|
this.gainService.list(p).subscribe({
|
||||||
|
next: (res: PagedResult<ResultatCagnotte>) => {
|
||||||
|
this.rows.set(res.content || []);
|
||||||
|
this.totalPages.set(res.totalPages ?? 1);
|
||||||
|
this.totalElements.set(res.totalElements ?? (res.content?.length ?? 0));
|
||||||
|
if ((res as any).pageable?.pageNumber) this.page.set((res as any).pageable.pageNumber);
|
||||||
|
this.loading.set(false);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Error fetching gains:', err);
|
||||||
|
this.rows.set([]);
|
||||||
|
this.loading.set(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageChange(next: number) {
|
||||||
|
this.page.set(next);
|
||||||
|
this.fetchPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPerPageChange(size: number) {
|
||||||
|
this.perPage.set(size);
|
||||||
|
this.page.set(1);
|
||||||
|
this.fetchPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template-friendly wrapper to safely parse the select event value
|
||||||
|
onPerPageChangeEvent(e: Event) {
|
||||||
|
const v = (e.target as HTMLSelectElement)?.value;
|
||||||
|
const size = Number(v) || 10;
|
||||||
|
this.onPerPageChange(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
openReport(row: ResultatCagnotte) {
|
||||||
|
try {
|
||||||
|
// Navigate to detail page
|
||||||
|
this.router.navigate(['/gains', String(row.course.id)]);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to open gain details for', row, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -114,43 +114,19 @@ export class Rapport {
|
|||||||
this.setSending(id, true);
|
this.setSending(id, true);
|
||||||
|
|
||||||
// Build a minimal ResultatCourse payload using available fields.
|
// Build a minimal ResultatCourse payload using available fields.
|
||||||
const course: Course = {
|
const course = {
|
||||||
id: String((row as any).courseId ?? ''),
|
id: String((row as any).courseId ?? '')
|
||||||
hippodrome: undefined,
|
|
||||||
reunionNumero: Number((row as any).reunionNumero ?? 0),
|
|
||||||
reunionDate: '',
|
|
||||||
nom: row.courseNom ?? '',
|
|
||||||
numero: Number(row.courseNumero ?? 0),
|
|
||||||
heureDepartPrevue: '',
|
|
||||||
discipline: '',
|
|
||||||
distanceMetres: 0,
|
|
||||||
categorie: '',
|
|
||||||
nombrePartants: 0,
|
|
||||||
statut: '',
|
|
||||||
annulee: false,
|
|
||||||
reporteeMemeJour: false,
|
|
||||||
reporteeAutreJour: false,
|
|
||||||
incidentTechnique: false,
|
|
||||||
nonPartants: [],
|
|
||||||
typesParisOuverts: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const payload: ResultatCourse = {
|
const payload: Omit<ResultatCourse, "id"> = {
|
||||||
id: Number(row.id as any),
|
|
||||||
course,
|
course,
|
||||||
statut: (row.statut as any) ?? (0 as any),
|
statut: (row.statut as any) ?? (0 as any),
|
||||||
ordreArrivee: String(row.ordreArrivee ?? ''),
|
ordreArrivee: String(row.ordreArrivee ?? ''),
|
||||||
datePublication: row.datePublication ?? row.createdAt,
|
datePublication: row.datePublication ?? row.createdAt,
|
||||||
dateValidation: row.dateValidation,
|
|
||||||
dateAnnulation: row.dateAnnulation,
|
|
||||||
notes: '',
|
|
||||||
createdAt: row.createdAt,
|
|
||||||
updatedAt: row.updatedAt,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.depouillement.sendResultat(payload).subscribe({
|
this.depouillement.sendResultat(payload).subscribe({
|
||||||
next: (res) => {
|
next: (res) => {
|
||||||
console.debug('Depouillement sent:', res);
|
|
||||||
// After successful depouillement, update the resultat statut to PROVISOIRE
|
// After successful depouillement, update the resultat statut to PROVISOIRE
|
||||||
const updateId = String((res && (res as any).id) ?? row.id);
|
const updateId = String((res && (res as any).id) ?? row.id);
|
||||||
this.api.update(updateId, { statut: ResultatStatut.PROVISOIRE }).subscribe({
|
this.api.update(updateId, { statut: ResultatStatut.PROVISOIRE }).subscribe({
|
||||||
|
|||||||
@@ -334,7 +334,8 @@ onSubmit() {
|
|||||||
if (updated) this.save.emit(updated);
|
if (updated) this.save.emit(updated);
|
||||||
else console.error('Update returned empty result');
|
else console.error('Update returned empty result');
|
||||||
},
|
},
|
||||||
error: (err) => console.error('Error updating course:', err),
|
error: (err) => {
|
||||||
|
console.error('Error updating course:', err)},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.courseServive.create(payload).subscribe({
|
this.courseServive.create(payload).subscribe({
|
||||||
|
|||||||
Reference in New Issue
Block a user