diff --git a/src/app/core/interfaces/agent.ts b/src/app/core/interfaces/agent.ts index 5e26308..9d62574 100644 --- a/src/app/core/interfaces/agent.ts +++ b/src/app/core/interfaces/agent.ts @@ -48,7 +48,7 @@ export interface Agent { autreTelephone?: string; // TPE assignés (actifs seulement) - tpes?: TpeDevice[] | TpeDevice; + terminauxIds?: number[] | number; createdAt?: string; updatedAt?: string; diff --git a/src/app/core/services/agent.ts b/src/app/core/services/agent.ts index 628a455..f28d7d0 100644 --- a/src/app/core/services/agent.ts +++ b/src/app/core/services/agent.ts @@ -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{ + return this.http.post(`${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 { 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; diff --git a/src/app/dashboard/layout/layout.ts b/src/app/dashboard/layout/layout.ts index 2b80d98..0e8aa7c 100644 --- a/src/app/dashboard/layout/layout.ts +++ b/src/app/dashboard/layout/layout.ts @@ -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' }, diff --git a/src/app/dashboard/pages/agents/agents.html b/src/app/dashboard/pages/agents/agents.html index 00849f0..4c012be 100644 --- a/src/app/dashboard/pages/agents/agents.html +++ b/src/app/dashboard/pages/agents/agents.html @@ -307,42 +307,19 @@ } - @if (getAgentTpes(agent.id).length > 0) { - -
TPE Assignés ({{ getAgentTpes(agent.id).length }})
-
- @for (tpe of getAgentTpes(agent.id); track tpe.id) { -
-
-
{{ tpe.numeroSerie }}
- @if (tpe.statut) { - - {{ formatTpeStatut(tpe.statut) }} - - } -
-
- @if (tpe.modeleAppareil) { -
- Modèle: {{ tpe.modeleAppareil }} -
- } - @if (tpe.numeroSerie) { -
- Série: {{ tpe.numeroSerie }} -
- } - @if (tpe.typeTerminal) { -
- Type: {{ tpe.typeTerminal }} -
- } -
-
- } -
-
+ @if (tpeArray()) { + @for (tpe of tpeDevices(); track tpe.id) { + + } } + @else { + + } + }
@@ -357,20 +334,20 @@ } -@if (assigningAgent()) { +@if (assigningAgent() !== undefined) {
@if (tpesLoading()) {
Chargement des TPE disponibles...
- } @else if (availableTpes().length === 0) { -
Aucun TPE disponible
} @else { - + + + }
Annuler -
} + + + +
+
+
+ {{ tpe.numeroSerie }} +
+ + @if (tpe.statut) { + + {{ formatTpeStatut(tpe.statut) }} + + } +
+ +
+ @if (tpe.modeleAppareil) { +
Modèle: {{ tpe.modeleAppareil }}
+ } + @if (tpe.numeroSerie) { +
Série: {{ tpe.numeroSerie }}
+ } + @if (tpe.typeTerminal) { +
Type: {{ tpe.typeTerminal }}
+ } +
+
+
diff --git a/src/app/dashboard/pages/agents/agents.ts b/src/app/dashboard/pages/agents/agents.ts index cf643d0..be68cbf 100644 --- a/src/app/dashboard/pages/agents/agents.ts +++ b/src/app/dashboard/pages/agents/agents.ts @@ -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([]); @@ -67,11 +67,17 @@ export class AgentsPage { // TPE Assignment modal assignTpeModalOpen = signal(false); - assigningAgent = signal(null); + assigningAgent = signal(undefined); availableTpes = signal([]); - selectedTpeId = signal(''); + selectedTpeId = signal([]); tpesLoading = signal(false); + tpeDevice = signal(undefined); + tpeDevices = signal([]); + + tpeArray = signal(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(); agentTpesMap = new Map(); + + 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 - 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(); + 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); } - }, - 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([]); } } diff --git a/src/app/dashboard/pages/point-vente/point-vente.html b/src/app/dashboard/pages/point-vente/point-vente.html index 6c70c38..246d90c 100644 --- a/src/app/dashboard/pages/point-vente/point-vente.html +++ b/src/app/dashboard/pages/point-vente/point-vente.html @@ -1,41 +1,41 @@ -
-
-

Gestion des Points de Vente

- Nouveau point de vente -
- - - - - -
- - -
-
-
- - -
- - - -
- Annuler - Enregistrer -
-
+
+
+

Gestion des Points de Vente

+ Nouveau point de vente +
+ + + + + +
+ + +
+
+
+ + +
+ + + +
+ Annuler + Enregistrer +
+
diff --git a/src/app/dashboard/pages/point-vente/point-vente.ts b/src/app/dashboard/pages/point-vente/point-vente.ts index b6f53de..a974af5 100644 --- a/src/app/dashboard/pages/point-vente/point-vente.ts +++ b/src/app/dashboard/pages/point-vente/point-vente.ts @@ -1,209 +1,209 @@ -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 { SortDir } from '@shared/paging/paging'; -import { PointVente, PointVenteStatut } from 'src/app/core/interfaces/points-ventes'; -import { PointsVenteService } from 'src/app/core/services/points-vente'; -import { PointsVenteForm } from '@shared/forms/points-vente-form/points-vente-form'; -import { toast } from 'ngx-sonner'; - -@Component({ - standalone: true, - selector: 'app-point-vente', - templateUrl: './point-vente.html', - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [ - CommonModule, - DataTable, - Paginator, - SearchBar, - Modal, - ZardButtonComponent, - PointsVenteForm, - ], -}) -export class PointVentePage { - rows = signal([]); - total = signal(0); - loading = signal(false); - - page = signal(0); - size = signal(10); - search = signal(''); - sort = signal({ key: 'nom', dir: 'asc' }); - - modalOpen = signal(false); - modalTitle = signal('Nouveau point de vente'); - editingItem = signal(null); - - @ViewChild(PointsVenteForm) formComp?: PointsVenteForm; - - cols: TableColumn[] = [ - { key: 'nom', label: 'Nom', sortable: true, defaultVisible: true }, - { key: 'adresse', label: 'Adresse', sortable: true, defaultVisible: true }, - { key: 'ville', label: 'Ville', sortable: true, defaultVisible: true }, - { - key: 'statut', - label: 'Statut', - sortable: true, - defaultVisible: true, - cell: (pv) => this.renderStatutBadge(pv.statut), - }, - { - key: 'latitude', - label: 'Latitude', - cell: (pv) => pv.latitude?.toFixed(6) || '—', - }, - { - key: 'longitude', - label: 'Longitude', - cell: (pv) => pv.longitude?.toFixed(6) || '—', - }, - ]; - - constructor(private api: PointsVenteService) { - 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: PointVenteStatut | string | undefined): string { - if (!statut) return ''; - const s = String(statut).toUpperCase(); - if (s === 'ACTIVE') { - return ` Actif`; - } - return ` Inactif`; - } - - onSearch(q: string) { - this.search.set(q); - this.page.set(0); - } - - openCreate() { - this.modalTitle.set('Nouveau point de vente'); - this.editingItem.set(null); - queueMicrotask(() => this.modalOpen.set(true)); - } - - openEdit(row: PointVente) { - this.modalTitle.set("Modifier le point de vente"); - this.editingItem.set(row); - queueMicrotask(() => this.modalOpen.set(true)); - } - - closeModal() { - this.modalOpen.set(false); - } - - submitChildForm() { - this.formComp?.onSubmit(); - } - - onFormSave(payload: Partial) { - const current = this.editingItem(); - const isCreating = !current?.id; - - const req$ = current?.id - ? this.api.update(current.id, payload) - : this.api.create(payload as Omit); - - req$.subscribe({ - next: (result) => { - if (result) { - toast.success( - isCreating - ? 'Point de vente créé avec succès' - : 'Point de vente modifié avec succès' - ); - // Close modal first - this.closeModal(); - // Reset form - 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 point de vente:', err); - alert( - isCreating - ? "Erreur lors de la création du point de vente" - : "Erreur lors de la modification du point de vente" - ); - }, - }); - } - - remove(row: PointVente) { - if (!confirm(`Supprimer le point de vente "${row.nom}" ?`)) return; - this.api.delete(row.id).subscribe({ - next: (success) => { - if (success) { - toast.success('Point de vente supprimé avec succès'); - this.fetch({ - page: this.page(), - size: this.size(), - search: this.search(), - sortKey: this.sort().key, - sortDir: this.sort().dir as SortDir, - }); - } else { - alert("Erreur lors de la suppression du point de vente"); - } - }, - error: () => { - alert("Erreur lors de la suppression du point de vente"); - }, - }); - } -} +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 { SortDir } from '@shared/paging/paging'; +import { PointVente, PointVenteStatut } from 'src/app/core/interfaces/points-ventes'; +import { PointsVenteService } from 'src/app/core/services/points-vente'; +import { PointsVenteForm } from '@shared/forms/points-vente-form/points-vente-form'; +import { toast } from 'ngx-sonner'; + +@Component({ + standalone: true, + selector: 'app-point-vente', + templateUrl: './point-vente.html', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + CommonModule, + DataTable, + Paginator, + SearchBar, + Modal, + ZardButtonComponent, + PointsVenteForm, + ], +}) +export class PointVentePage { + rows = signal([]); + total = signal(0); + loading = signal(false); + + page = signal(0); + size = signal(10); + search = signal(''); + sort = signal({ key: 'nom', dir: 'asc' }); + + modalOpen = signal(false); + modalTitle = signal('Nouveau point de vente'); + editingItem = signal(null); + + @ViewChild(PointsVenteForm) formComp?: PointsVenteForm; + + cols: TableColumn[] = [ + { key: 'nom', label: 'Nom', sortable: true, defaultVisible: true }, + { key: 'adresse', label: 'Adresse', sortable: true, defaultVisible: true }, + { key: 'ville', label: 'Ville', sortable: true, defaultVisible: true }, + { + key: 'statut', + label: 'Statut', + sortable: true, + defaultVisible: true, + cell: (pv) => this.renderStatutBadge(pv.statut), + }, + { + key: 'latitude', + label: 'Latitude', + cell: (pv) => pv.latitude?.toFixed(6) || '—', + }, + { + key: 'longitude', + label: 'Longitude', + cell: (pv) => pv.longitude?.toFixed(6) || '—', + }, + ]; + + constructor(private api: PointsVenteService) { + 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: PointVenteStatut | string | undefined): string { + if (!statut) return ''; + const s = String(statut).toUpperCase(); + if (s === 'ACTIVE') { + return ` Actif`; + } + return ` Inactif`; + } + + onSearch(q: string) { + this.search.set(q); + this.page.set(0); + } + + openCreate() { + this.modalTitle.set('Nouveau point de vente'); + this.editingItem.set(null); + queueMicrotask(() => this.modalOpen.set(true)); + } + + openEdit(row: PointVente) { + this.modalTitle.set("Modifier le point de vente"); + this.editingItem.set(row); + queueMicrotask(() => this.modalOpen.set(true)); + } + + closeModal() { + this.modalOpen.set(false); + } + + submitChildForm() { + this.formComp?.onSubmit(); + } + + onFormSave(payload: Partial) { + const current = this.editingItem(); + const isCreating = !current?.id; + + const req$ = current?.id + ? this.api.update(current.id, payload) + : this.api.create(payload as Omit); + + req$.subscribe({ + next: (result) => { + if (result) { + toast.success( + isCreating + ? 'Point de vente créé avec succès' + : 'Point de vente modifié avec succès' + ); + // Close modal first + this.closeModal(); + // Reset form + 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 point de vente:', err); + alert( + isCreating + ? "Erreur lors de la création du point de vente" + : "Erreur lors de la modification du point de vente" + ); + }, + }); + } + + remove(row: PointVente) { + if (!confirm(`Supprimer le point de vente "${row.nom}" ?`)) return; + this.api.delete(row.id).subscribe({ + next: (success) => { + if (success) { + toast.success('Point de vente supprimé avec succès'); + this.fetch({ + page: this.page(), + size: this.size(), + search: this.search(), + sortKey: this.sort().key, + sortDir: this.sort().dir as SortDir, + }); + } else { + alert("Erreur lors de la suppression du point de vente"); + } + }, + error: () => { + alert("Erreur lors de la suppression du point de vente"); + }, + }); + } +} diff --git a/src/app/dashboard/pages/tpe-select/tpe-select.css b/src/app/dashboard/pages/tpe-select/tpe-select.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/dashboard/pages/tpe-select/tpe-select.html b/src/app/dashboard/pages/tpe-select/tpe-select.html new file mode 100644 index 0000000..d167b58 --- /dev/null +++ b/src/app/dashboard/pages/tpe-select/tpe-select.html @@ -0,0 +1,35 @@ +
+ @if (loading()) { +
+
+
+ } + @if(!loading()){ + @if(tpes().length > 0){ +
+ + +
+ +
+
+
+ + +
+ } + @if (tpes().length === 0) { +

+ Aucun TPE disponible. +

+ } + } +
+ \ No newline at end of file diff --git a/src/app/dashboard/pages/tpe-select/tpe-select.spec.ts b/src/app/dashboard/pages/tpe-select/tpe-select.spec.ts new file mode 100644 index 0000000..a0b549a --- /dev/null +++ b/src/app/dashboard/pages/tpe-select/tpe-select.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TpeSelect } from './tpe-select'; + +describe('TpeSelect', () => { + let component: TpeSelect; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TpeSelect] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TpeSelect); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/dashboard/pages/tpe-select/tpe-select.ts b/src/app/dashboard/pages/tpe-select/tpe-select.ts new file mode 100644 index 0000000..d4230b8 --- /dev/null +++ b/src/app/dashboard/pages/tpe-select/tpe-select.ts @@ -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(); // TPE sélectionnés + + tpes = signal([]); + total = signal(0); + loading = signal(true); + selectedIds = signal>(new Set()); + page = signal(0); + perPage = signal(10); + search = signal(''); + sort = signal({ key: 'id', dir: 'asc' }); + + + + columns:TableColumn[] = [ + { + 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)); + } + +} diff --git a/src/app/dashboard/pages/tpe/tpe.ts b/src/app/dashboard/pages/tpe/tpe.ts index 292a6df..56d344b 100644 --- a/src/app/dashboard/pages/tpe/tpe.ts +++ b/src/app/dashboard/pages/tpe/tpe.ts @@ -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({ key: 'imei', dir: 'asc' }); + sort = signal({ key: 'id', dir: 'asc' }); selectedStatut = signal(null); modalOpen = signal(false); @@ -96,11 +97,31 @@ export class TpePage implements OnInit { } cols: TableColumn[] = [ - { 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); 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'); diff --git a/src/app/shared/forms/agent-form/agent-form.ts b/src/app/shared/forms/agent-form/agent-form.ts index b81cc0f..bf2166b 100644 --- a/src/app/shared/forms/agent-form/agent-form.ts +++ b/src/app/shared/forms/agent-form/agent-form.ts @@ -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(); @@ -121,6 +121,7 @@ export class AgentForm { this.submitted = false; return; } + console.log(v); this.form.reset({ code: v.code || '', profil: v.profil || '', diff --git a/src/app/shared/forms/tpe-form/tpe-form.html b/src/app/shared/forms/tpe-form/tpe-form.html index f4e8900..ca11b87 100644 --- a/src/app/shared/forms/tpe-form/tpe-form.html +++ b/src/app/shared/forms/tpe-form/tpe-form.html @@ -70,7 +70,7 @@ - +
@if (pointDeVenteLoading()){
@@ -85,8 +85,7 @@ @for (pdv of pointsDevente; track pdv.id) {
  • + (mousedown)="selectPointDeVente(pdv)"> {{ pdv.nom }} / {{ pdv.code }}
  • } diff --git a/src/app/shared/forms/tpe-form/tpe-form.ts b/src/app/shared/forms/tpe-form/tpe-form.ts index f468711..f902114 100644 --- a/src/app/shared/forms/tpe-form/tpe-form.ts +++ b/src/app/shared/forms/tpe-form/tpe-form.ts @@ -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)=>{ diff --git a/src/environments/environment.development.ts b/src/environments/environment.development.ts index a85696d..79ead1f 100644 --- a/src/environments/environment.development.ts +++ b/src/environments/environment.development.ts @@ -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' };