agent done

This commit is contained in:
OnlyPapy98
2026-01-07 16:06:54 +01:00
parent 0ae7fa316e
commit d7bcbce50d
16 changed files with 658 additions and 425 deletions

View File

@@ -48,7 +48,7 @@ export interface Agent {
autreTelephone?: string; autreTelephone?: string;
// TPE assignés (actifs seulement) // TPE assignés (actifs seulement)
tpes?: TpeDevice[] | TpeDevice; terminauxIds?: number[] | number;
createdAt?: string; createdAt?: string;
updatedAt?: string; updatedAt?: string;

View File

@@ -66,7 +66,7 @@ interface AgentApiResponse {
statutMarital?: string; statutMarital?: string;
epoux?: string; epoux?: string;
autreTelephone?: string; autreTelephone?: string;
tpes?: TpeDevice; terminauxIds?: number[] | number;
createdAt?: string; createdAt?: string;
updatedAt?: string; updatedAt?: string;
createdBy?: string; createdBy?: string;
@@ -159,7 +159,7 @@ export class AgentService {
statutMarital: apiAgent.statutMarital, statutMarital: apiAgent.statutMarital,
epoux: apiAgent.epoux, epoux: apiAgent.epoux,
autreTelephone: apiAgent.autreTelephone, autreTelephone: apiAgent.autreTelephone,
tpes: apiAgent.tpes , terminauxIds: apiAgent.terminauxIds ,
createdAt: apiAgent.createdAt, createdAt: apiAgent.createdAt,
updatedAt: apiAgent.updatedAt, updatedAt: apiAgent.updatedAt,
createdBy: apiAgent.createdBy, createdBy: apiAgent.createdBy,
@@ -227,6 +227,18 @@ export class AgentService {
return payload; return payload;
} }
assigner(tpeId: string, agentId:string):Observable<Agent | undefined>{
return this.http.post<Agent>(`${this.apiUrl}/${agentId}/terminaux/${tpeId}`,
{Headers: this.getNgrokHeaders()}).pipe(map(res=> res),
catchError((err)=>{
console.error(err);
return of(undefined);
})
)
}
// GET /api/v1/agents/{id} - Get by ID // GET /api/v1/agents/{id} - Get by ID
getById(id: string): Observable<Agent | undefined> { getById(id: string): Observable<Agent | undefined> {
if (USE_SERVER) { if (USE_SERVER) {
@@ -260,7 +272,6 @@ export class AgentService {
}) })
.pipe( .pipe(
map((res) => { map((res) => {
console.log(res);
const agents = res.content.map((apiAgent) => { const agents = res.content.map((apiAgent) => {
const transformed = this.transformAgent(apiAgent); const transformed = this.transformAgent(apiAgent);
return transformed; return transformed;

View File

@@ -44,7 +44,6 @@ export class Layout {
mainMenuItems: MenuItem[] = [ mainMenuItems: MenuItem[] = [
{ icon: '🏠', label: 'Tableau de bord', link: '/', exact: true }, { icon: '🏠', label: 'Tableau de bord', link: '/', exact: true },
{ icon: '🏟️', label: 'Hippodromes', link: '/hippodromes' }, { icon: '🏟️', label: 'Hippodromes', link: '/hippodromes' },
{ 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' }, { icon: '💰', label: 'Gains (cagnotte)', link: '/gains' },

View File

@@ -307,42 +307,19 @@
} }
<!-- TPE Assignés --> <!-- TPE Assignés -->
@if (getAgentTpes(agent.id).length > 0) { @if (tpeArray()) {
<z-card class="p-4"> @for (tpe of tpeDevices(); track tpe.id) {
<div class="text-lg font-semibold mb-4">TPE Assignés ({{ getAgentTpes(agent.id).length }})</div> <ng-container
<div class="grid grid-cols-1 md:grid-cols-2 gap-3"> *ngTemplateOutlet="tpeCard; context: { $implicit: tpe }"
@for (tpe of getAgentTpes(agent.id); track tpe.id) { />
<div class="px-3 py-2.5 rounded bg-primary/10 border border-primary/20">
<div class="flex items-start justify-between mb-2">
<div class="font-medium text-sm">{{ tpe.numeroSerie }}</div>
@if (tpe.statut) {
<span class="text-xs px-2 py-0.5 rounded bg-surface text-muted-foreground">
{{ formatTpeStatut(tpe.statut) }}
</span>
} }
</div>
<div class="space-y-1 text-xs text-muted-foreground">
@if (tpe.modeleAppareil) {
<div>
<span class="font-medium">Modèle:</span> {{ tpe.modeleAppareil }}
</div>
} }
@if (tpe.numeroSerie) { @else {
<div> <ng-container
<span class="font-medium">Série:</span> {{ tpe.numeroSerie }} *ngTemplateOutlet="tpeCard; context: { $implicit: tpeDevice() }"
</div> />
}
@if (tpe.typeTerminal) {
<div>
<span class="font-medium">Type:</span> {{ tpe.typeTerminal }}
</div>
}
</div>
</div>
}
</div>
</z-card>
} }
</div> </div>
} }
<div modal-actions class="flex justify-end gap-2"> <div modal-actions class="flex justify-end gap-2">
@@ -357,20 +334,20 @@
} }
<!-- TPE Assignment Modal --> <!-- TPE Assignment Modal -->
@if (assigningAgent()) { @if (assigningAgent() !== undefined) {
<app-modal <app-modal
[open]="assignTpeModalOpen()" [open]="assignTpeModalOpen()"
[title]="'Assigner un TPE à ' + (assigningAgent()?.nom || '') + ' ' + (assigningAgent()?.prenom || '')" [title]="'Assigner un TPE à ' + (assigningAgent()?.nom || '') + ' ' + (assigningAgent()?.prenom || '')"
(close)="closeAssignTpeModal()" (close)="closeAssignTpeModal()"
size="md" size="xxl"
> >
<div class="space-y-4"> <div class="space-y-4">
@if (tpesLoading()) { @if (tpesLoading()) {
<div class="text-center py-4">Chargement des TPE disponibles...</div> <div class="text-center py-4">Chargement des TPE disponibles...</div>
} @else if (availableTpes().length === 0) {
<div class="text-center py-4 text-muted-foreground">Aucun TPE disponible</div>
} @else { } @else {
<z-form-field> <app-tpe-select (selectionChange)="selectedTpeId.set($event)" [agent]="assigningAgent()" [selected]="getSelectedTpeIds()" ></app-tpe-select>
<!-- <z-form-field>
<label z-form-label>Sélectionner un TPE</label> <label z-form-label>Sélectionner un TPE</label>
<div z-form-control> <div z-form-control>
<z-select <z-select
@@ -388,14 +365,43 @@
} }
</z-select> </z-select>
</div> </div>
</z-form-field> </z-form-field> -->
} }
</div> </div>
<div modal-actions class="flex justify-end gap-2"> <div modal-actions class="flex justify-end gap-2">
<z-button zType="destructive" (click)="closeAssignTpeModal()">Annuler</z-button> <z-button zType="destructive" (click)="closeAssignTpeModal()">Annuler</z-button>
<button z-button [disabled]="!selectedTpeId() || tpesLoading()" (click)="confirmAssignTpe()"> <button z-button [disabled]="selectedTpeId().length === 0 || tpesLoading()" (click)="confirmAssignTpe()">
Assigner Assigner
</button> </button>
</div> </div>
</app-modal> </app-modal>
} }
<ng-template #tpeCard let-tpe>
<div class="px-3 py-2.5 rounded bg-primary/10 border border-primary/20">
<div class="flex items-start justify-between mb-2">
<div class="font-medium text-sm">
{{ tpe.numeroSerie }}
</div>
@if (tpe.statut) {
<span class="text-xs px-2 py-0.5 rounded bg-surface text-muted-foreground">
{{ formatTpeStatut(tpe.statut) }}
</span>
}
</div>
<div class="space-y-1 text-xs text-muted-foreground">
@if (tpe.modeleAppareil) {
<div><span class="font-medium">Modèle:</span> {{ tpe.modeleAppareil }}</div>
}
@if (tpe.numeroSerie) {
<div><span class="font-medium">Série:</span> {{ tpe.numeroSerie }}</div>
}
@if (tpe.typeTerminal) {
<div><span class="font-medium">Type:</span> {{ tpe.typeTerminal }}</div>
}
</div>
</div>
</ng-template>

View File

@@ -27,6 +27,7 @@ import { forkJoin, of } from 'rxjs';
import { switchMap, catchError } from 'rxjs/operators'; import { switchMap, catchError } from 'rxjs/operators';
import { toast } from 'ngx-sonner'; import { toast } from 'ngx-sonner';
import { AgentForm } from '@shared/forms/agent-form/agent-form'; import { AgentForm } from '@shared/forms/agent-form/agent-form';
import { TpeSelect } from "../tpe-select/tpe-select";
@Component({ @Component({
standalone: true, standalone: true,
@@ -41,11 +42,10 @@ import { AgentForm } from '@shared/forms/agent-form/agent-form';
Modal, Modal,
ZardButtonComponent, ZardButtonComponent,
ZardCardComponent, ZardCardComponent,
ZardSelectComponent,
ZardSelectItemComponent,
ZardFormModule, ZardFormModule,
AgentForm AgentForm,
], TpeSelect
],
}) })
export class AgentsPage { export class AgentsPage {
rows = signal<Agent[]>([]); rows = signal<Agent[]>([]);
@@ -67,11 +67,17 @@ export class AgentsPage {
// TPE Assignment modal // TPE Assignment modal
assignTpeModalOpen = signal(false); assignTpeModalOpen = signal(false);
assigningAgent = signal<Agent | null>(null); assigningAgent = signal<Agent | undefined>(undefined);
availableTpes = signal<TpeDevice[]>([]); availableTpes = signal<TpeDevice[]>([]);
selectedTpeId = signal<string>(''); selectedTpeId = signal<string[]>([]);
tpesLoading = signal(false); tpesLoading = signal(false);
tpeDevice = signal<TpeDevice | undefined>(undefined);
tpeDevices = signal<TpeDevice[]>([]);
tpeArray = signal<boolean>(false);
@ViewChild(AgentFullForm) formComp?: AgentFullForm; @ViewChild(AgentFullForm) formComp?: AgentFullForm;
formatTpeStatut(statut: TpeStatus): string { formatTpeStatut(statut: TpeStatus): string {
@@ -95,28 +101,19 @@ export class AgentsPage {
{ key: 'profil', label: 'Profil', sortable: true, defaultVisible: true }, { key: 'profil', label: 'Profil', sortable: true, defaultVisible: true },
{ key: 'statut', label: 'Statut', sortable: true, defaultVisible: true, cell: (a) => this.renderStatutBadge(a.statut) }, { key: 'statut', label: 'Statut', sortable: true, defaultVisible: true, cell: (a) => this.renderStatutBadge(a.statut) },
{ key: 'phone', label: 'Téléphone', sortable: true, defaultVisible: true }, { key: 'phone', label: 'Téléphone', sortable: true, defaultVisible: true },
{ key: 'zone', label: 'Zone', sortable: true }, { key: 'zone', label: 'Zone', sortable: true, defaultVisible: true },
{ key: 'kiosk', label: 'Kiosque', sortable: true },
{ key: 'tpes', label: 'TPE', cell: (a) => `${(this.getAgentTpes(a.id) || []).length}` },
{ key: 'limites', label: 'Limites', cell: (a) => this.formatLimits(a) },
{ key: 'dateEmbauche', label: 'Embauché le', cell: (a) => (a.dateEmbauche ? new Date(a.dateEmbauche).toLocaleDateString() : '') },
]; ];
tpeMap = new Map<string, TpeDevice>(); tpeMap = new Map<string, TpeDevice>();
agentTpesMap = new Map<string, TpeDevice[]>(); agentTpesMap = new Map<string, TpeDevice[]>();
constructor( constructor(
private api: AgentService, private api: AgentService,
private tpeSvc: TpeService, private tpeSvc: TpeService,
private familyMemberService: AgentFamilyMemberService private familyMemberService: AgentFamilyMemberService
) { ) {
// Preload TPE maps for display
this.tpeSvc
.list({ page: 1, size: 200, search: '', sortKey: 'id', sortDir: 'asc' } as any)
.subscribe((res) => {
const tpes = res.content as TpeDevice[];
this.rebuildTpeMaps(tpes);
});
effect(() => { effect(() => {
const params = { const params = {
page: this.page(), page: this.page(),
@@ -139,12 +136,9 @@ export class AgentsPage {
this.loading.set(true); this.loading.set(true);
this.api.list(params).subscribe({ this.api.list(params).subscribe({
next: (res) => { next: (res) => {
console.log(res.content);
this.rows.set(res.content); this.rows.set(res.content);
this.total.set(res.pageable.total); this.total.set(res.pageable.total);
this.loading.set(false); this.loading.set(false);
// Refresh TPE map to ensure we have latest data
this.refreshTpeMap();
}, },
error: () => { error: () => {
this.rows.set([]); this.rows.set([]);
@@ -154,32 +148,6 @@ export class AgentsPage {
}); });
} }
private refreshTpeMap() {
this.tpeSvc
.list({ page: 1, size: 200, search: '', sortKey: 'imei', sortDir: 'asc' } as any)
.subscribe((res) => {
const tpes = res.content as TpeDevice[];
this.rebuildTpeMaps(tpes);
});
}
private rebuildTpeMaps(tpes: TpeDevice[]) {
this.tpeMap.clear();
this.agentTpesMap.clear();
tpes.forEach((t) => {
this.tpeMap.set(t.id, t);
const agentId = t.agentConnecteId;
if (agentId) {
const list = this.agentTpesMap.get(agentId) || [];
list.push(t);
this.agentTpesMap.set(agentId, list);
}
});
}
getAgentTpes(agentId: string): TpeDevice[] {
return this.agentTpesMap.get(agentId) || [];
}
renderStatutBadge(statut: Agent['statut'] | string | undefined): string { renderStatutBadge(statut: Agent['statut'] | string | undefined): string {
if (!statut) return ''; if (!statut) return '';
@@ -218,6 +186,8 @@ export class AgentsPage {
closeModal() { closeModal() {
this.modalOpen.set(false); this.modalOpen.set(false);
} }
openDetail(row: Agent) { openDetail(row: Agent) {
// Fetch full agent details // Fetch full agent details
this.api.getById(row.id).subscribe({ this.api.getById(row.id).subscribe({
@@ -233,6 +203,31 @@ export class AgentsPage {
this.detailFamilyMembers.set([]); this.detailFamilyMembers.set([]);
}, },
}); });
const tpeIds = agent.terminauxIds;
if(Array.isArray(tpeIds)){
this.tpeArray.set(true);
forkJoin(
tpeIds.map(id=>this.tpeSvc.getById(String(id)))
).subscribe({
next:(tpes)=>{
this.tpeDevices.set(tpes.filter(tpe=>tpe!==undefined))
},
error:(err)=>{
console.error(err);
}
})
}else{
this.tpeArray.set(false);
this.tpeSvc.getById(String(tpeIds)).subscribe({
next:(tpe)=>{
if(tpe && tpe !== undefined)
this.tpeDevice.set(tpe);
},
error:(err)=>{
console.error(err);
}
})
}
this.detailModalOpen.set(true); this.detailModalOpen.set(true);
} }
}, },
@@ -386,6 +381,23 @@ export class AgentsPage {
}); });
} }
getSelectedTpeIds = (): string[] => {
const ids = this.assigningAgent()?.terminauxIds;
if (!ids) return []; // undefined ou null → tableau vide
// Si c'est un tableau, on map en string
if (Array.isArray(ids)) {
return ids.map(id => id.toString());
}
// Si c'est un seul nombre, on retourne un tableau avec un élément
return [ids.toString()];
};
remove(row: Agent) { remove(row: Agent) {
if (!confirm(`Supprimer l\'agent ${row.code} ?`)) return; if (!confirm(`Supprimer l\'agent ${row.code} ?`)) return;
this.api.delete(row.id).subscribe(() => this.api.delete(row.id).subscribe(() =>
@@ -401,75 +413,91 @@ export class AgentsPage {
openAssignTpe(agent: Agent) { openAssignTpe(agent: Agent) {
this.assigningAgent.set(agent); this.assigningAgent.set(agent);
this.selectedTpeId.set(''); this.selectedTpeId.set([]);
this.loadAvailableTpes();
this.assignTpeModalOpen.set(true); this.assignTpeModalOpen.set(true);
} }
loadAvailableTpes() { // loadAvailableTpes() {
this.tpesLoading.set(true); // this.tpesLoading.set(true);
const agent = this.assigningAgent(); // const agent = this.assigningAgent();
if (!agent) { // if (!agent) {
this.availableTpes.set([]); // this.availableTpes.set([]);
this.tpesLoading.set(false); // this.tpesLoading.set(false);
return; // return;
} // }
const currentAgentTpes = this.agentTpesMap.get(agent.id) || []; // const currentAgentTpes = this.agentTpesMap.get(agent.id) || [];
const agentTpeIds = new Set(currentAgentTpes.map((t) => t.id)); // const agentTpeIds = new Set(currentAgentTpes.map((t) => t.id));
// Load available TPEs (DISPONIBLE or VALIDE status) // // Load available TPEs (DISPONIBLE or VALIDE status)
forkJoin([this.tpeSvc.getByStatut('ACTIF'), this.tpeSvc.getByStatut('ACTIF')]).subscribe({ // forkJoin([this.tpeSvc.getByStatut('ACTIF'), this.tpeSvc.getByStatut('ACTIF')]).subscribe({
next: ([disponibleTpes, valideTpes]) => { // next: ([disponibleTpes, valideTpes]) => {
// Combine and filter: only show TPEs that are not assigned to any agent AND not already assigned to this agent // // Combine and filter: only show TPEs that are not assigned to any agent AND not already assigned to this agent
const allTpes = [...disponibleTpes, ...valideTpes]; // const allTpes = [...disponibleTpes, ...valideTpes];
const available = allTpes.filter( // const available = allTpes.filter(
(t) => // (t) =>
!t.agentConnecteId && // !t.agentConnecteId &&
(t.statut === 'ACTIF') && // (t.statut === 'ACTIF') &&
!agentTpeIds.has(t.id) // !agentTpeIds.has(t.id)
); // );
// Remove duplicates // // Remove duplicates
const uniqueTpes = Array.from(new Map(available.map((t) => [t.id, t])).values()); // const uniqueTpes = Array.from(new Map(available.map((t) => [t.id, t])).values());
this.availableTpes.set(uniqueTpes); // this.availableTpes.set(uniqueTpes);
this.tpesLoading.set(false); // this.tpesLoading.set(false);
}, // },
error: () => { // error: () => {
this.availableTpes.set([]); // this.availableTpes.set([]);
this.tpesLoading.set(false); // this.tpesLoading.set(false);
}, // },
}); // });
} // }
// testAssigne(tpeIds: string[]){
// console.log(tpeIds);
// }
confirmAssignTpe() { confirmAssignTpe() {
const agent = this.assigningAgent(); const agent = this.assigningAgent();
const tpeId = this.selectedTpeId(); const tpeId = this.selectedTpeId();
if (!agent || !tpeId) { if (!agent || tpeId.length === 0) {
alert('Veuillez sélectionner un TPE'); alert('Veuillez sélectionner un TPE');
return; return;
} }
// Assign TPE to agent forkJoin(this.selectedTpeId().map(id=> this.api.assigner(id, agent.id))).subscribe(
this.tpeSvc.assigner(tpeId, agent.id).subscribe({ {
next: (tpe) => { next:()=>{
if (tpe) {
// Fermer le modal et recharger complètement la page
this.assignTpeModalOpen.set(false); this.assignTpeModalOpen.set(false);
this.assigningAgent.set(null); this.assigningAgent.set(undefined);
this.selectedTpeId.set(''); this.selectedTpeId.set([]);
// Rechargement complet pour s'assurer que la liste des agents / TPE est à jour toast.success(`Tpe affecté à l'agent avec succès1`)
window.location.reload(); },
error: (err)=>{
console.error(err);
} }
}, }
error: () => { )
alert("Erreur lors de l'assignation du TPE");
}, // // Assign TPE to agent
}); // this.tpeSvc.assigner(tpeId, agent.id).subscribe({
// next: (tpe) => {
// if (tpe) {
// // Fermer le modal et recharger complètement la page
// this.selectedTpeId.set('');
// // Rechargement complet pour s'assurer que la liste des agents / TPE est à jour
// window.location.reload();
// }
// },
// error: () => {
// alert("Erreur lors de l'assignation du TPE");
// },
// });
} }
closeAssignTpeModal() { closeAssignTpeModal() {
this.assignTpeModalOpen.set(false); this.assignTpeModalOpen.set(false);
this.assigningAgent.set(null); this.assigningAgent.set(undefined);
this.selectedTpeId.set(''); this.selectedTpeId.set([]);
} }
} }

View File

@@ -0,0 +1,35 @@
<div class="p-4 border rounded-lg shadow bg-white dark:bg-gray-900">
@if (loading()) {
<div class="flex justify-center py-4">
<div class="w-6 h-6 border-2 border-gray-300 border-t-blue-600 rounded-full animate-spin"></div>
</div>
}
@if(!loading()){
@if(tpes().length > 0){
<div>
<app-data-table
[data]="tpes()"
[columns]="columns">
<ng-template #rowActions let-row>
<div class="flex gap-2 flex-wrap">
<input type="checkbox" (click)="toggleTpe(row)" [checked] = "isChecked(row)" />
</div>
</ng-template>
</app-data-table>
<app-paginator
[total]="total()"
[page]="page()"
[perPage]="perPage()"
(pageChange)="page.set($event)"
(perPageChange)="perPage.set($event)">
</app-paginator>
</div>
}
@if (tpes().length === 0) {
<p class="text-gray-500 dark:text-gray-400">
Aucun TPE disponible.
</p>
}
}
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TpeSelect } from './tpe-select';
describe('TpeSelect', () => {
let component: TpeSelect;
let fixture: ComponentFixture<TpeSelect>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TpeSelect]
})
.compileComponents();
fixture = TestBed.createComponent(TpeSelect);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,110 @@
import { Component, effect, EventEmitter, Input, Output, signal } from '@angular/core';
import { SortState, TableColumn, DataTable } from '@shared/components/data-table/data-table';
import { ListParams, SortDir } from '@shared/paging/paging';
import { Agent } from 'src/app/core/interfaces/agent';
import { TpeDevice } from 'src/app/core/interfaces/tpe';
import { TpeService } from 'src/app/core/services/tpe';
import { Paginator } from "@shared/components/paginator/paginator";
@Component({
selector: 'app-tpe-select',
imports: [DataTable, Paginator],
templateUrl: './tpe-select.html',
styleUrl: './tpe-select.css',
})
export class TpeSelect {
@Input() agent?: Agent; // Agent pour filtrer les TPE assignés
@Input() selected: string[] = []; // Ids de TPE à cocher par défaut
@Output() selectionChange = new EventEmitter<string[]>(); // TPE sélectionnés
tpes = signal<TpeDevice[]>([]);
total = signal(0);
loading = signal<boolean>(true);
selectedIds = signal<Set<string>>(new Set());
page = signal(0);
perPage = signal(10);
search = signal('');
sort = signal<SortState>({ key: 'id', dir: 'asc' });
columns:TableColumn<TpeDevice>[] = [
{
key: 'numeroSerie',
label: "Numero de serie"
},
{
key: 'versionLogicielle',
label: "Version du logiciel"
},
{
key: 'modeleAppareil',
label: "Model"
},
{
key: 'systemeExploitation',
label: 'Système d\'exploitation'
},
{
key: 'versionOs',
label: 'Version Os'
}
]
constructor(private tpeService: TpeService) {
effect(()=>{
const params = {
page: this.page(),
size: this.perPage(),
sortKey: this.sort().key,
sortDir: this.sort().dir as SortDir,
}
this.loadTpes(params);
})
}
ngOnInit() {
// Initialiser les TPE sélectionnés si fournis
this.selectedIds.set(new Set(this.selected));
}
private loadTpes(params:ListParams) {
this.loading.set(true);
this.tpeService.list(params).subscribe({
next: (res) => {
// Filtrer : TPE non assignés ou déjà assignés à cet agent
// console.log(res.content);
// const available = res.content.filter(
// (t) =>
// !t.agentConnecteId || t.agentConnecteId === this.agent?.id
// );
this.tpes.set(res.content);
this.total.set(res.pageable.total);
this.loading.set(false);
},
error: (err) => {
console.error(err);
this.loading.set(false);
},
});
}
toggleTpe(tpe: TpeDevice) {
const current = new Set(this.selectedIds());
if (current.has(tpe.id)) {
current.delete(tpe.id);
} else {
current.add(tpe.id);
}
this.selectedIds.set(current);
this.selectionChange.emit(Array.from(current));
}
isChecked(tpe: TpeDevice) {
return this.selectedIds().has(String(tpe.id));
}
}

View File

@@ -27,6 +27,7 @@ import { ZardFormModule } from '@shared/components/form/form.module';
import { forkJoin, Subject } from 'rxjs'; import { forkJoin, Subject } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators'; import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
import { toast } from 'ngx-sonner'; import { toast } from 'ngx-sonner';
import { PointsVenteService } from 'src/app/core/services/points-vente';
@Component({ @Component({
standalone: true, standalone: true,
@@ -53,10 +54,10 @@ export class TpePage implements OnInit {
total = signal(0); total = signal(0);
loading = signal(false); loading = signal(false);
page = signal(1); page = signal(0);
perPage = signal(10); perPage = signal(10);
search = signal(''); search = signal('');
sort = signal<SortState>({ key: 'imei', dir: 'asc' }); sort = signal<SortState>({ key: 'id', dir: 'asc' });
selectedStatut = signal<TpeStatus | null>(null); selectedStatut = signal<TpeStatus | null>(null);
modalOpen = signal(false); modalOpen = signal(false);
@@ -96,11 +97,31 @@ export class TpePage implements OnInit {
} }
cols: TableColumn<TpeDevice>[] = [ cols: TableColumn<TpeDevice>[] = [
{ key: 'imei', label: 'IMEI', sortable: true }, { key: 'numeroSerie', label: 'Numéro de série', sortable: true },
{ key: 'serial', label: 'N° de Série', sortable: true }, {
{ key: 'type', label: 'Type', sortable: true }, key: 'pointDeVenteId',
{ key: 'marque', label: 'Marque', sortable: true }, label: 'Point de vente',
{ key: 'modele', label: 'Modèle', sortable: true }, sortable: true,
cell:(p) => {
let pointVente = signal('');
this.pointVenteService.getById(p.pointDeVenteId).subscribe({
next: (pdv)=>{
if(!pdv || pdv === undefined){
pointVente.set("Aucun point")
return;
};
pointVente.set(`${pdv.ville}/${pdv.adresse}`)
},
error:(err)=>{
pointVente.set('Aucun point')
}
})
return pointVente();
},
},
{ key: 'versionLogicielle', label: 'Version', sortable: true },
{ key: 'typeTerminal', label: 'Type', sortable: true },
{ key: 'systemeExploitation', label: 'Sytème', sortable: true },
{ key: 'statut', label: 'Statut', sortable: true, cell: (d) => this.formatStatut(d.statut) }, { key: 'statut', label: 'Statut', sortable: true, cell: (d) => this.formatStatut(d.statut) },
{ {
key: 'assigne', key: 'assigne',
@@ -136,12 +157,13 @@ export class TpePage implements OnInit {
}, },
]; ];
allStatuses: TpeStatus[] = [ allStatuses: TpeStatus[] = ['ACTIF', 'HORS_SERVICE'];
'ACTIF',
'HORS_SERVICE'
];
constructor(private api: TpeService, private agentService: AgentService) { constructor(
private api: TpeService,
private agentService: AgentService,
private pointVenteService: PointsVenteService
) {
effect(() => { effect(() => {
// Only trigger fetch when page, perPage, or sort changes (not search - handled by searchSubject) // Only trigger fetch when page, perPage, or sort changes (not search - handled by searchSubject)
const searchValue = this.search(); const searchValue = this.search();
@@ -321,7 +343,8 @@ export class TpePage implements OnInit {
} }
onUpdateStatut(row: TpeDevice, newStatut: TpeStatus) { onUpdateStatut(row: TpeDevice, newStatut: TpeStatus) {
if (!confirm(`Changer le statut de ${row.numeroSerie} vers ${this.formatStatut(newStatut)} ?`)) return; if (!confirm(`Changer le statut de ${row.numeroSerie} vers ${this.formatStatut(newStatut)} ?`))
return;
this.api.updateStatut(row.id, newStatut).subscribe({ this.api.updateStatut(row.id, newStatut).subscribe({
next: () => { next: () => {
this.fetch({ this.fetch({
@@ -435,7 +458,7 @@ export class TpePage implements OnInit {
: this.api.create(payload as Omit<TpeDevice, 'id'>); : this.api.create(payload as Omit<TpeDevice, 'id'>);
req$.subscribe({ req$.subscribe({
next: (result) => { next: (result) => {
toast.success('Tpe créé avec succès!') toast.success('Tpe créé avec succès!');
// For update, check if result is valid (update can return undefined on error) // For update, check if result is valid (update can return undefined on error)
if (current?.id && !result) { if (current?.id && !result) {
console.error('Update failed - result is undefined'); console.error('Update failed - result is undefined');

View File

@@ -16,7 +16,7 @@ import { ZardCheckboxComponent } from "@shared/components/checkbox/checkbox.comp
standalone: true, standalone: true,
templateUrl: './agent-form.html', templateUrl: './agent-form.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, ReactiveFormsModule, ZardFormModule, ZardInputDirective, ZardSelectComponent, ZardSelectItemComponent, ZardButtonComponent], imports: [CommonModule, ReactiveFormsModule, ZardFormModule, ZardInputDirective, ZardSelectComponent, ZardSelectItemComponent],
}) })
export class AgentForm { export class AgentForm {
@Output() save = new EventEmitter<Agent>(); @Output() save = new EventEmitter<Agent>();
@@ -121,6 +121,7 @@ export class AgentForm {
this.submitted = false; this.submitted = false;
return; return;
} }
console.log(v);
this.form.reset({ this.form.reset({
code: v.code || '', code: v.code || '',
profil: v.profil || '', profil: v.profil || '',

View File

@@ -70,7 +70,7 @@
<z-form-field> <z-form-field>
<label z-form-label>Point de vente</label> <label z-form-label>Point de vente</label>
<input [value]="pointDeVenteText()" (click)="onClose()" (input)="pointDeVenteTextChange($event)" z-input placeholder="Entrez le nom du point de vente"/> <input [value]="pointDeVenteText()" (blur)="onClose()" (input)="pointDeVenteTextChange($event)" z-input placeholder="Entrez le nom du point de vente"/>
<div z-form-control [errorMessage]="errorMessage('pointDeVenteId') || ''"> <div z-form-control [errorMessage]="errorMessage('pointDeVenteId') || ''">
@if (pointDeVenteLoading()){ @if (pointDeVenteLoading()){
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
@@ -85,8 +85,7 @@
@for (pdv of pointsDevente; track pdv.id) { @for (pdv of pointsDevente; track pdv.id) {
<li <li
class="px-3 py-2 cursor-pointer hover:bg-blue-500" class="px-3 py-2 cursor-pointer hover:bg-blue-500"
(click)="selectPointDeVente(pdv)" (mousedown)="selectPointDeVente(pdv)">
>
{{ pdv.nom }} / {{ pdv.code }} {{ pdv.nom }} / {{ pdv.code }}
</li> </li>
} }

View File

@@ -51,6 +51,7 @@ export class TpeForm {
pointsDevente:PointVente[] = []; pointsDevente:PointVente[] = [];
statuts = [ statuts = [
{ {
label: 'Actif', label: 'Actif',
@@ -81,16 +82,6 @@ export class TpeForm {
agentConnecteId: ['1'], agentConnecteId: ['1'],
journalSession: ['1'], journalSession: ['1'],
}); });
effect(()=>{
const text = this.pointDeVenteText();
const params: ListParams = {
page: 0,
size: 10,
search: text
}
this.getPointDeventeFromText(text, params);
})
} }
@@ -99,6 +90,13 @@ export class TpeForm {
pointDeVenteTextChange =(event: Event)=>{ pointDeVenteTextChange =(event: Event)=>{
const value = (event.target as HTMLInputElement).value; const value = (event.target as HTMLInputElement).value;
this.pointDeVenteText.set(value); this.pointDeVenteText.set(value);
const text = this.pointDeVenteText();
const params: ListParams = {
page: 0,
size: 10,
search: text
}
this.getPointDeventeFromText(text, params);
} }
getPointDeventeFromText=(text: string, params: ListParams)=>{ getPointDeventeFromText=(text: string, params: ListParams)=>{

View File

@@ -1,5 +1,5 @@
export const environment = { export const environment = {
production: false, production: false,
apiBaseUrl: 'http://192.168.40.225:8080', apiBaseUrl: 'http://192.168.40.204:8080',
depouillementBaseUrl: 'http://192.168.1.235:8383' depouillementBaseUrl: 'http://192.168.1.235:8383'
}; };