agent done
This commit is contained in:
@@ -48,7 +48,7 @@ export interface Agent {
|
||||
autreTelephone?: string;
|
||||
|
||||
// TPE assignés (actifs seulement)
|
||||
tpes?: TpeDevice[] | TpeDevice;
|
||||
terminauxIds?: number[] | number;
|
||||
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
|
||||
@@ -66,7 +66,7 @@ interface AgentApiResponse {
|
||||
statutMarital?: string;
|
||||
epoux?: string;
|
||||
autreTelephone?: string;
|
||||
tpes?: TpeDevice;
|
||||
terminauxIds?: number[] | number;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
createdBy?: string;
|
||||
@@ -159,7 +159,7 @@ export class AgentService {
|
||||
statutMarital: apiAgent.statutMarital,
|
||||
epoux: apiAgent.epoux,
|
||||
autreTelephone: apiAgent.autreTelephone,
|
||||
tpes: apiAgent.tpes ,
|
||||
terminauxIds: apiAgent.terminauxIds ,
|
||||
createdAt: apiAgent.createdAt,
|
||||
updatedAt: apiAgent.updatedAt,
|
||||
createdBy: apiAgent.createdBy,
|
||||
@@ -227,6 +227,18 @@ export class AgentService {
|
||||
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
|
||||
getById(id: string): Observable<Agent | undefined> {
|
||||
if (USE_SERVER) {
|
||||
@@ -260,7 +272,6 @@ export class AgentService {
|
||||
})
|
||||
.pipe(
|
||||
map((res) => {
|
||||
console.log(res);
|
||||
const agents = res.content.map((apiAgent) => {
|
||||
const transformed = this.transformAgent(apiAgent);
|
||||
return transformed;
|
||||
|
||||
@@ -44,7 +44,6 @@ export class Layout {
|
||||
mainMenuItems: MenuItem[] = [
|
||||
{ icon: '🏠', label: 'Tableau de bord', link: '/', exact: true },
|
||||
{ icon: '🏟️', label: 'Hippodromes', link: '/hippodromes' },
|
||||
{ icon: '📅', label: 'Reunions', link: '/reunions' },
|
||||
{ icon: '🏇', label: 'Courses', link: '/courses' },
|
||||
{ icon: 'icon-chart-bar', label: 'Résultats des courses', link: '/resultat' },
|
||||
{ icon: '💰', label: 'Gains (cagnotte)', link: '/gains' },
|
||||
|
||||
@@ -307,42 +307,19 @@
|
||||
}
|
||||
|
||||
<!-- TPE Assignés -->
|
||||
@if (getAgentTpes(agent.id).length > 0) {
|
||||
<z-card class="p-4">
|
||||
<div class="text-lg font-semibold mb-4">TPE Assignés ({{ getAgentTpes(agent.id).length }})</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
@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>
|
||||
@if (tpeArray()) {
|
||||
@for (tpe of tpeDevices(); track tpe.id) {
|
||||
<ng-container
|
||||
*ngTemplateOutlet="tpeCard; context: { $implicit: tpe }"
|
||||
/>
|
||||
}
|
||||
</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>
|
||||
}
|
||||
</div>
|
||||
</z-card>
|
||||
@else {
|
||||
<ng-container
|
||||
*ngTemplateOutlet="tpeCard; context: { $implicit: tpeDevice() }"
|
||||
/>
|
||||
}
|
||||
|
||||
</div>
|
||||
}
|
||||
<div modal-actions class="flex justify-end gap-2">
|
||||
@@ -357,20 +334,20 @@
|
||||
}
|
||||
|
||||
<!-- TPE Assignment Modal -->
|
||||
@if (assigningAgent()) {
|
||||
@if (assigningAgent() !== undefined) {
|
||||
<app-modal
|
||||
[open]="assignTpeModalOpen()"
|
||||
[title]="'Assigner un TPE à ' + (assigningAgent()?.nom || '') + ' ' + (assigningAgent()?.prenom || '')"
|
||||
(close)="closeAssignTpeModal()"
|
||||
size="md"
|
||||
size="xxl"
|
||||
>
|
||||
<div class="space-y-4">
|
||||
@if (tpesLoading()) {
|
||||
<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 {
|
||||
<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>
|
||||
<div z-form-control>
|
||||
<z-select
|
||||
@@ -388,14 +365,43 @@
|
||||
}
|
||||
</z-select>
|
||||
</div>
|
||||
</z-form-field>
|
||||
</z-form-field> -->
|
||||
}
|
||||
</div>
|
||||
<div modal-actions class="flex justify-end gap-2">
|
||||
<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
|
||||
</button>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@@ -27,6 +27,7 @@ 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,
|
||||
@@ -41,11 +42,10 @@ import { AgentForm } from '@shared/forms/agent-form/agent-form';
|
||||
Modal,
|
||||
ZardButtonComponent,
|
||||
ZardCardComponent,
|
||||
ZardSelectComponent,
|
||||
ZardSelectItemComponent,
|
||||
ZardFormModule,
|
||||
AgentForm
|
||||
],
|
||||
AgentForm,
|
||||
TpeSelect
|
||||
],
|
||||
})
|
||||
export class AgentsPage {
|
||||
rows = signal<Agent[]>([]);
|
||||
@@ -67,11 +67,17 @@ export class AgentsPage {
|
||||
|
||||
// TPE Assignment modal
|
||||
assignTpeModalOpen = signal(false);
|
||||
assigningAgent = signal<Agent | null>(null);
|
||||
assigningAgent = signal<Agent | undefined>(undefined);
|
||||
availableTpes = signal<TpeDevice[]>([]);
|
||||
selectedTpeId = signal<string>('');
|
||||
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 {
|
||||
@@ -95,28 +101,19 @@ export class AgentsPage {
|
||||
{ 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 },
|
||||
{ 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() : '') },
|
||||
{ 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
|
||||
) {
|
||||
// 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(() => {
|
||||
const params = {
|
||||
page: this.page(),
|
||||
@@ -139,12 +136,9 @@ export class AgentsPage {
|
||||
this.loading.set(true);
|
||||
this.api.list(params).subscribe({
|
||||
next: (res) => {
|
||||
console.log(res.content);
|
||||
this.rows.set(res.content);
|
||||
this.total.set(res.pageable.total);
|
||||
this.loading.set(false);
|
||||
// Refresh TPE map to ensure we have latest data
|
||||
this.refreshTpeMap();
|
||||
},
|
||||
error: () => {
|
||||
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 {
|
||||
if (!statut) return '';
|
||||
@@ -218,6 +186,8 @@ export class AgentsPage {
|
||||
closeModal() {
|
||||
this.modalOpen.set(false);
|
||||
}
|
||||
|
||||
|
||||
openDetail(row: Agent) {
|
||||
// Fetch full agent details
|
||||
this.api.getById(row.id).subscribe({
|
||||
@@ -233,6 +203,31 @@ export class AgentsPage {
|
||||
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);
|
||||
}
|
||||
},
|
||||
@@ -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) {
|
||||
if (!confirm(`Supprimer l\'agent ${row.code} ?`)) return;
|
||||
this.api.delete(row.id).subscribe(() =>
|
||||
@@ -401,75 +413,91 @@ export class AgentsPage {
|
||||
|
||||
openAssignTpe(agent: Agent) {
|
||||
this.assigningAgent.set(agent);
|
||||
this.selectedTpeId.set('');
|
||||
this.loadAvailableTpes();
|
||||
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;
|
||||
}
|
||||
// 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));
|
||||
// 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);
|
||||
},
|
||||
});
|
||||
}
|
||||
// // 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) {
|
||||
if (!agent || tpeId.length === 0) {
|
||||
alert('Veuillez sélectionner un TPE');
|
||||
return;
|
||||
}
|
||||
|
||||
// Assign TPE to agent
|
||||
this.tpeSvc.assigner(tpeId, agent.id).subscribe({
|
||||
next: (tpe) => {
|
||||
if (tpe) {
|
||||
// Fermer le modal et recharger complètement la page
|
||||
forkJoin(this.selectedTpeId().map(id=> this.api.assigner(id, agent.id))).subscribe(
|
||||
{
|
||||
next:()=>{
|
||||
this.assignTpeModalOpen.set(false);
|
||||
this.assigningAgent.set(null);
|
||||
this.selectedTpeId.set('');
|
||||
// Rechargement complet pour s'assurer que la liste des agents / TPE est à jour
|
||||
window.location.reload();
|
||||
this.assigningAgent.set(undefined);
|
||||
this.selectedTpeId.set([]);
|
||||
toast.success(`Tpe affecté à l'agent avec succès1`)
|
||||
},
|
||||
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() {
|
||||
this.assignTpeModalOpen.set(false);
|
||||
this.assigningAgent.set(null);
|
||||
this.selectedTpeId.set('');
|
||||
this.assigningAgent.set(undefined);
|
||||
this.selectedTpeId.set([]);
|
||||
}
|
||||
}
|
||||
|
||||
0
src/app/dashboard/pages/tpe-select/tpe-select.css
Normal file
0
src/app/dashboard/pages/tpe-select/tpe-select.css
Normal file
35
src/app/dashboard/pages/tpe-select/tpe-select.html
Normal file
35
src/app/dashboard/pages/tpe-select/tpe-select.html
Normal 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>
|
||||
|
||||
23
src/app/dashboard/pages/tpe-select/tpe-select.spec.ts
Normal file
23
src/app/dashboard/pages/tpe-select/tpe-select.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
110
src/app/dashboard/pages/tpe-select/tpe-select.ts
Normal file
110
src/app/dashboard/pages/tpe-select/tpe-select.ts
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import { ZardFormModule } from '@shared/components/form/form.module';
|
||||
import { forkJoin, Subject } from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';
|
||||
import { toast } from 'ngx-sonner';
|
||||
import { PointsVenteService } from 'src/app/core/services/points-vente';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -53,10 +54,10 @@ export class TpePage implements OnInit {
|
||||
total = signal(0);
|
||||
loading = signal(false);
|
||||
|
||||
page = signal(1);
|
||||
page = signal(0);
|
||||
perPage = signal(10);
|
||||
search = signal('');
|
||||
sort = signal<SortState>({ key: 'imei', dir: 'asc' });
|
||||
sort = signal<SortState>({ key: 'id', dir: 'asc' });
|
||||
selectedStatut = signal<TpeStatus | null>(null);
|
||||
|
||||
modalOpen = signal(false);
|
||||
@@ -96,11 +97,31 @@ export class TpePage implements OnInit {
|
||||
}
|
||||
|
||||
cols: TableColumn<TpeDevice>[] = [
|
||||
{ key: 'imei', label: 'IMEI', sortable: true },
|
||||
{ key: 'serial', label: 'N° de Série', sortable: true },
|
||||
{ key: 'type', label: 'Type', sortable: true },
|
||||
{ key: 'marque', label: 'Marque', sortable: true },
|
||||
{ key: 'modele', label: 'Modèle', sortable: true },
|
||||
{ key: 'numeroSerie', label: 'Numéro de série', sortable: true },
|
||||
{
|
||||
key: 'pointDeVenteId',
|
||||
label: 'Point de vente',
|
||||
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: 'assigne',
|
||||
@@ -136,12 +157,13 @@ export class TpePage implements OnInit {
|
||||
},
|
||||
];
|
||||
|
||||
allStatuses: TpeStatus[] = [
|
||||
'ACTIF',
|
||||
'HORS_SERVICE'
|
||||
];
|
||||
allStatuses: TpeStatus[] = ['ACTIF', 'HORS_SERVICE'];
|
||||
|
||||
constructor(private api: TpeService, private agentService: AgentService) {
|
||||
constructor(
|
||||
private api: TpeService,
|
||||
private agentService: AgentService,
|
||||
private pointVenteService: PointsVenteService
|
||||
) {
|
||||
effect(() => {
|
||||
// Only trigger fetch when page, perPage, or sort changes (not search - handled by searchSubject)
|
||||
const searchValue = this.search();
|
||||
@@ -321,7 +343,8 @@ export class TpePage implements OnInit {
|
||||
}
|
||||
|
||||
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({
|
||||
next: () => {
|
||||
this.fetch({
|
||||
@@ -435,7 +458,7 @@ export class TpePage implements OnInit {
|
||||
: this.api.create(payload as Omit<TpeDevice, 'id'>);
|
||||
req$.subscribe({
|
||||
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)
|
||||
if (current?.id && !result) {
|
||||
console.error('Update failed - result is undefined');
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ZardCheckboxComponent } from "@shared/components/checkbox/checkbox.comp
|
||||
standalone: true,
|
||||
templateUrl: './agent-form.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [CommonModule, ReactiveFormsModule, ZardFormModule, ZardInputDirective, ZardSelectComponent, ZardSelectItemComponent, ZardButtonComponent],
|
||||
imports: [CommonModule, ReactiveFormsModule, ZardFormModule, ZardInputDirective, ZardSelectComponent, ZardSelectItemComponent],
|
||||
})
|
||||
export class AgentForm {
|
||||
@Output() save = new EventEmitter<Agent>();
|
||||
@@ -121,6 +121,7 @@ export class AgentForm {
|
||||
this.submitted = false;
|
||||
return;
|
||||
}
|
||||
console.log(v);
|
||||
this.form.reset({
|
||||
code: v.code || '',
|
||||
profil: v.profil || '',
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
<z-form-field>
|
||||
<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') || ''">
|
||||
@if (pointDeVenteLoading()){
|
||||
<div class="flex items-center justify-center">
|
||||
@@ -85,8 +85,7 @@
|
||||
@for (pdv of pointsDevente; track pdv.id) {
|
||||
<li
|
||||
class="px-3 py-2 cursor-pointer hover:bg-blue-500"
|
||||
(click)="selectPointDeVente(pdv)"
|
||||
>
|
||||
(mousedown)="selectPointDeVente(pdv)">
|
||||
{{ pdv.nom }} / {{ pdv.code }}
|
||||
</li>
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ export class TpeForm {
|
||||
|
||||
pointsDevente:PointVente[] = [];
|
||||
|
||||
|
||||
statuts = [
|
||||
{
|
||||
label: 'Actif',
|
||||
@@ -81,16 +82,6 @@ export class TpeForm {
|
||||
agentConnecteId: ['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)=>{
|
||||
const value = (event.target as HTMLInputElement).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)=>{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiBaseUrl: 'http://192.168.40.225:8080',
|
||||
apiBaseUrl: 'http://192.168.40.204:8080',
|
||||
depouillementBaseUrl: 'http://192.168.1.235:8383'
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user