import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, ViewChild, effect, signal, untracked, } from '@angular/core'; import { DataTable, SortState, TableColumn } from '@shared/components/data-table/data-table'; import { Paginator } from '@shared/components/paginator/paginator'; import { SearchBar } from '@shared/components/search-bar/search-bar'; import { Modal } from '@shared/components/modal/modal'; import { ZardButtonComponent } from '@shared/components/button/button.component'; import { ZardCardComponent } from '@shared/components/card/card.component'; import { ZardSelectComponent } from '@shared/components/select/select.component'; import { ZardSelectItemComponent } from '@shared/components/select/select-item.component'; import { ZardFormModule } from '@shared/components/form/form.module'; import { SortDir } from '@shared/paging/paging'; import { Agent, AgentFamilyMember } from 'src/app/core/interfaces/agent'; import { AgentService } from 'src/app/core/services/agent'; import { AgentFamilyMemberService } from 'src/app/core/services/agent-family-member'; import { TpeService } from 'src/app/core/services/tpe'; import { TpeDevice, TpeStatus } from 'src/app/core/interfaces/tpe'; import { AgentFullForm } from '@shared/forms/agent-full-form/agent-full-form'; import { forkJoin, of } from 'rxjs'; import { switchMap, catchError } from 'rxjs/operators'; import { toast } from 'ngx-sonner'; import { AgentForm } from '@shared/forms/agent-form/agent-form'; import { TpeSelect } from "../tpe-select/tpe-select"; @Component({ standalone: true, selector: 'app-agents', templateUrl: './agents.html', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ CommonModule, DataTable, Paginator, SearchBar, Modal, ZardButtonComponent, ZardCardComponent, ZardFormModule, AgentForm, TpeSelect ], }) export class AgentsPage { rows = signal([]); total = signal(0); loading = signal(false); page = signal(0); size = signal(10); search = signal(''); sort = signal({ key: 'code', dir: 'asc' }); modalOpen = signal(false); modalTitle = signal('Nouvel agent'); editingItem = signal(null); detailModalOpen = signal(false); detailItem = signal(null); detailFamilyMembers = signal([]); // TPE Assignment modal assignTpeModalOpen = signal(false); assigningAgent = signal(undefined); availableTpes = signal([]); selectedTpeId = signal([]); tpesLoading = signal(false); tpeDevice = signal(undefined); tpeDevices = signal([]); tpeArray = signal(false); @ViewChild(AgentFullForm) formComp?: AgentFullForm; formatTpeStatut(statut: TpeStatus): string { const statutMap: Record = { VALIDE: 'Valide', INVALIDE: 'Invalide', EN_PANNE: 'En panne', BLOQUE: 'Bloqué', DISPONIBLE: 'Disponible', AFFECTE: 'Affecté', EN_MAINTENANCE: 'En maintenance', HORS_SERVICE: 'Hors service', VOLE: 'Volé', }; return statutMap[statut] || statut; } cols: TableColumn[] = [ { key: 'code', label: 'Code', sortable: true, defaultVisible: true }, { key: 'nomPrenom', label: 'Nom complet', sortable: true, defaultVisible: true, cell: (a) => `${a.nom} ${a.prenom}` }, { key: 'profil', label: 'Profil', sortable: true, defaultVisible: true }, { 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: 'zone', label: 'Zone', sortable: true, defaultVisible: true }, ]; tpeMap = new Map(); agentTpesMap = new Map(); constructor( private api: AgentService, private tpeSvc: TpeService, private familyMemberService: AgentFamilyMemberService ) { effect(() => { const params = { page: this.page(), size: this.size(), search: this.search(), sortKey: this.sort().key, sortDir: this.sort().dir as SortDir, }; untracked(() => this.fetch(params)); }); } private fetch(params: { page: number; size: number; search: string; sortKey: string; sortDir: SortDir; }) { this.loading.set(true); this.api.list(params).subscribe({ next: (res) => { this.rows.set(res.content); this.total.set(res.pageable.total); this.loading.set(false); }, error: () => { this.rows.set([]); this.total.set(0); this.loading.set(false); }, }); } renderStatutBadge(statut: Agent['statut'] | string | undefined): string { if (!statut) return ''; const s = String(statut).toUpperCase(); if (s === 'ACTIF') { return ` Actif`; } if (s === 'INACTIF') { return ` Inactif`; } return ` Suspendu`; } formatLimits(a: Agent): string { const parts: string[] = []; const nf = new Intl.NumberFormat(undefined, { maximumFractionDigits: 2 }); if (a.limiteInferieure !== undefined) parts.push(nf.format(a.limiteInferieure)); if (a.limiteSuperieure !== undefined) parts.push(nf.format(a.limiteSuperieure)); return parts.length ? parts.join(' — ') : ''; } onSearch(q: string) { this.search.set(q); this.page.set(1); } openCreate() { this.modalTitle.set('Nouvel agent'); this.editingItem.set(null); queueMicrotask(() => this.modalOpen.set(true)); } openEdit(row: Agent) { this.modalTitle.set("Modifier l'agent"); this.editingItem.set(row); queueMicrotask(() => this.modalOpen.set(true)); } closeModal() { this.modalOpen.set(false); } openDetail(row: Agent) { // Fetch full agent details this.api.getById(row.id).subscribe({ next: (agent) => { if (agent) { this.detailItem.set(agent); // Load family members separately this.familyMemberService.getByAgentId(agent.id).subscribe({ next: (members) => { this.detailFamilyMembers.set(members); }, error: () => { 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); } }, error: () => { // If fetch fails, use the row data this.detailItem.set(row); // Try to load family members anyway this.familyMemberService.getByAgentId(row.id).subscribe({ next: (members) => { this.detailFamilyMembers.set(members); }, error: () => { this.detailFamilyMembers.set([]); }, }); this.detailModalOpen.set(true); }, }); } closeDetailModal() { this.detailModalOpen.set(false); this.detailItem.set(null); this.detailFamilyMembers.set([]); } submitChildForm() { this.formComp?.onSubmit(); } onFormSave(payload: Partial) { const current = this.editingItem(); const isCreating = !current?.id; // Mode création (compact) const familyMembersData = this.formComp?.getFamilyMembersData() || []; // Save agent first const req$ = current?.id ? this.api.update(current.id, payload) : this.api.create(payload as Omit); req$ .pipe( switchMap((result) => { if (!result && current?.id) { // Update failed throw new Error("Erreur lors de la sauvegarde de l'agent"); } const savedAgentId = result?.id || current?.id || ''; if (!savedAgentId) { throw new Error("Impossible d'obtenir l'ID de l'agent sauvegardé"); } // In creation mode (compact), skip family members management if (isCreating || familyMembersData.length === 0) { return of([]); } // Get existing family members for this agent (only for updates) return this.familyMemberService.getByAgentId(savedAgentId).pipe( switchMap((existingMembers) => { const existingIds = new Set(existingMembers.map((m) => m.id)); const newMembers = familyMembersData.filter((fm) => !fm.id); const updatedMembers = familyMembersData.filter( (fm) => fm.id && existingIds.has(fm.id) ); const deletedIds = existingMembers .filter((em) => !familyMembersData.some((fm) => fm.id === em.id)) .map((em) => em.id); const operations: any[] = []; // Delete removed members deletedIds.forEach((id) => { operations.push( this.familyMemberService.delete(id).pipe( catchError((err) => { console.error(`Error deleting family member ${id}:`, err); return of(false); }) ) ); }); // Create new members newMembers.forEach((member) => { operations.push( this.familyMemberService .create({ agentId: savedAgentId, nom: member.nom, statut: member.statut, dateNaissance: member.dateNaissance, sexe: member.sexe as 'M' | 'F' | undefined, }) .pipe( catchError((err) => { console.error('Error creating family member:', err); return of(null); }) ) ); }); // Update existing members updatedMembers.forEach((member) => { if (member.id) { operations.push( this.familyMemberService .update(member.id, { nom: member.nom, statut: member.statut, dateNaissance: member.dateNaissance, sexe: member.sexe as 'M' | 'F' | undefined, }) .pipe( catchError((err) => { console.error(`Error updating family member ${member.id}:`, err); return of(null); }) ) ); } }); return operations.length > 0 ? forkJoin(operations) : of([]); }) ); }) ) .subscribe({ next: () => { toast.success('Agent sauvegardé avec succès'); // Close modal first this.closeModal(); // Reset form after successful save this.formComp?.resetForm(); // Clear editing item this.editingItem.set(null); // Refresh data this.fetch({ page: this.page(), size: this.size(), search: this.search(), sortKey: this.sort().key, sortDir: this.sort().dir as SortDir, }); }, error: (err) => { console.error('Error saving agent:', err); alert("Erreur lors de la sauvegarde de l'agent"); }, }); } 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) { if (!confirm(`Supprimer l\'agent ${row.code} ?`)) return; this.api.delete(row.id).subscribe(() => this.fetch({ page: this.page(), size: this.size(), search: this.search(), sortKey: this.sort().key, sortDir: this.sort().dir as SortDir, }) ); } openAssignTpe(agent: Agent) { this.assigningAgent.set(agent); this.selectedTpeId.set([]); this.assignTpeModalOpen.set(true); } // loadAvailableTpes() { // this.tpesLoading.set(true); // const agent = this.assigningAgent(); // if (!agent) { // this.availableTpes.set([]); // this.tpesLoading.set(false); // return; // } // const currentAgentTpes = this.agentTpesMap.get(agent.id) || []; // const agentTpeIds = new Set(currentAgentTpes.map((t) => t.id)); // // Load available TPEs (DISPONIBLE or VALIDE status) // forkJoin([this.tpeSvc.getByStatut('ACTIF'), this.tpeSvc.getByStatut('ACTIF')]).subscribe({ // next: ([disponibleTpes, valideTpes]) => { // // 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 available = allTpes.filter( // (t) => // !t.agentConnecteId && // (t.statut === 'ACTIF') && // !agentTpeIds.has(t.id) // ); // // Remove duplicates // const uniqueTpes = Array.from(new Map(available.map((t) => [t.id, t])).values()); // this.availableTpes.set(uniqueTpes); // this.tpesLoading.set(false); // }, // error: () => { // this.availableTpes.set([]); // this.tpesLoading.set(false); // }, // }); // } // testAssigne(tpeIds: string[]){ // console.log(tpeIds); // } confirmAssignTpe() { const agent = this.assigningAgent(); const tpeId = this.selectedTpeId(); if (!agent || tpeId.length === 0) { alert('Veuillez sélectionner un TPE'); return; } forkJoin(this.selectedTpeId().map(id=> this.api.assigner(id, agent.id))).subscribe( { next:()=>{ this.assignTpeModalOpen.set(false); this.assigningAgent.set(undefined); this.selectedTpeId.set([]); toast.success(`Tpe affecté à l'agent avec succès1`) }, error: (err)=>{ console.error(err); } } ) // // 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() { this.assignTpeModalOpen.set(false); this.assigningAgent.set(undefined); this.selectedTpeId.set([]); } }