Files
pmu-plateforme-jeux-admin-plr/src/app/dashboard/pages/agents/agents.ts
OnlyPapy98 d7bcbce50d agent done
2026-01-07 16:06:54 +01:00

504 lines
16 KiB
TypeScript

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<Agent[]>([]);
total = signal(0);
loading = signal(false);
page = signal(0);
size = signal(10);
search = signal('');
sort = signal<SortState>({ key: 'code', dir: 'asc' });
modalOpen = signal(false);
modalTitle = signal('Nouvel agent');
editingItem = signal<Agent | null>(null);
detailModalOpen = signal(false);
detailItem = signal<Agent | null>(null);
detailFamilyMembers = signal<AgentFamilyMember[]>([]);
// TPE Assignment modal
assignTpeModalOpen = signal(false);
assigningAgent = signal<Agent | undefined>(undefined);
availableTpes = signal<TpeDevice[]>([]);
selectedTpeId = signal<string[]>([]);
tpesLoading = signal(false);
tpeDevice = signal<TpeDevice | undefined>(undefined);
tpeDevices = signal<TpeDevice[]>([]);
tpeArray = signal<boolean>(false);
@ViewChild(AgentFullForm) formComp?: AgentFullForm;
formatTpeStatut(statut: TpeStatus): string {
const statutMap: Record<string, string> = {
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<Agent>[] = [
{ 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<string, TpeDevice>();
agentTpesMap = new Map<string, TpeDevice[]>();
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 `<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-green-500/10 text-green-600 dark:text-green-400 text-xs font-medium"><i class="icon-check"></i> Actif</span>`;
}
if (s === 'INACTIF') {
return `<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-gray-500/10 text-gray-600 dark:text-gray-400 text-xs font-medium"><i class="icon-x"></i> Inactif</span>`;
}
return `<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-orange-500/10 text-orange-600 dark:text-orange-400 text-xs font-medium"><i class="icon-alert-circle"></i> Suspendu</span>`;
}
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<Agent>) {
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<Agent, 'id'>);
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([]);
}
}