import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, ViewChild, effect, signal, untracked, OnInit } 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 { AgentLimit } from 'src/app/core/interfaces/agent-limit'; import { AgentLimitService } from 'src/app/core/services/agent-limit'; import { LimitForm } from '@shared/forms/limit-form/limit-form'; import { Subject, of } from 'rxjs'; import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators'; @Component({ standalone: true, selector: 'app-limits', templateUrl: './limits.html', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, DataTable, Paginator, SearchBar, Modal, ZardButtonComponent, LimitForm], }) export class LimitsPage implements OnInit { rows = signal([]); total = signal(0); loading = signal(false); page = signal(1); size = signal(10); search = signal(''); sort = signal({ key: 'code', dir: 'asc' }); selectedActif = signal(null); modalOpen = signal(false); modalTitle = signal('Nouvelle limite'); editingItem = signal(null); // Live search private searchSubject = new Subject(); @ViewChild(LimitForm) formComp?: LimitForm; cols: TableColumn[] = [ { key: 'code', label: 'Code', sortable: true }, { key: 'configCode', label: 'Config', sortable: true }, { key: 'nom', label: 'Nom', sortable: true }, { key: 'isDefault', label: 'Défaut', cell: (l) => l.isDefault ? ' Par défaut' : '', }, { key: 'actif', label: 'Actif', cell: (l) => l.actif ? ' Actif' : ' Inactif', }, { key: 'betMin', label: 'Min Bet', cell: (l) => (l.betMin ?? 0).toLocaleString('fr-FR'), }, { key: 'betMax', label: 'Max Bet', cell: (l) => (l.betMax ?? 0).toLocaleString('fr-FR'), }, { key: 'maxBet', label: 'Max Bet (tx)', cell: (l) => (l.maxBet ?? 0).toLocaleString('fr-FR'), }, { key: 'maxDisburseBet', label: 'Max Disburse', cell: (l) => (l.maxDisburseBet ?? 0).toLocaleString('fr-FR'), }, { key: 'airtimeMin', label: 'Airtime Min', cell: (l) => (l.airtimeMin ?? 0).toLocaleString('fr-FR'), }, { key: 'airtimeMax', label: 'Airtime Max', cell: (l) => (l.airtimeMax ?? 0).toLocaleString('fr-FR'), }, ]; constructor(private api: AgentLimitService) { effect(() => { // Only trigger fetch when page, size, or sort changes (not search - handled by searchSubject) const searchValue = this.search(); const params = { page: this.page(), size: this.size(), search: searchValue, sortKey: this.sort().key, sortDir: this.sort().dir as SortDir, }; // Only fetch if search is empty (search is handled by searchSubject) if (!searchValue.trim()) { untracked(() => this.fetch(params)); } }); // Setup live search with debounce this.searchSubject .pipe( debounceTime(300), distinctUntilChanged(), switchMap((query) => { if (query.trim()) { // Use search API which returns array return this.api.search(query); } else { // If empty, use normal list return this.api.list({ page: this.page(), size: this.size(), search: '', sortKey: this.sort().key, sortDir: this.sort().dir as SortDir, }).pipe( switchMap((res) => { // Convert PagedResult to array for consistency return of(res.content); }) ); } }) ) .subscribe({ next: (res) => { // Search API always returns array if (Array.isArray(res)) { this.rows.set(res); this.total.set(res.length); } this.loading.set(false); }, error: (err) => { console.error('Search error:', err); this.rows.set([]); this.total.set(0); this.loading.set(false); }, }); } ngOnInit() { // Initial fetch this.fetch({ page: this.page(), size: this.size(), search: this.search(), sortKey: this.sort().key, sortDir: this.sort().dir as SortDir, }); } private fetch(params: { page: number; size: number; search: string; sortKey: string; sortDir: SortDir }) { // Don't fetch if there's a search query - it's handled by searchSubject const searchQuery = params.search.trim(); if (searchQuery) { return; // Search is handled by searchSubject subscription } this.loading.set(true); const actif = this.selectedActif(); if (actif !== null) { // Filter by actif status - returns array this.api.getByActif(actif).subscribe({ next: (res: AgentLimit[]) => { this.rows.set(res); this.total.set(res.length); this.loading.set(false); }, error: () => { this.rows.set([]); this.total.set(0); this.loading.set(false); }, }); } else { // Normal list with pagination 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); }, }); } } onSearch(q: string) { this.search.set(q); this.page.set(1); // Trigger search via subject for live search if (q.trim()) { this.loading.set(true); this.searchSubject.next(q); } else { // If empty, fetch normally this.fetch({ page: this.page(), size: this.size(), search: '', sortKey: this.sort().key, sortDir: this.sort().dir as SortDir, }); } } onActifFilter(actif: boolean | null) { this.selectedActif.set(actif); this.page.set(1); this.fetch({ page: this.page(), size: this.size(), search: this.search(), sortKey: this.sort().key, sortDir: this.sort().dir as SortDir, }); } openCreate() { this.modalTitle.set('Nouvelle limite'); this.editingItem.set(null); queueMicrotask(() => this.modalOpen.set(true)); } openEdit(row: AgentLimit) { this.modalTitle.set('Modifier la limite'); 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 isSettingDefault = payload.isDefault === true; const wasDefault = current?.isDefault; // If setting as default and it wasn't default before, show confirmation if (isSettingDefault && !wasDefault) { if (!confirm('Définir cette limite comme limite par défaut ?\n\nTous les agents recevront automatiquement cette limite, et l\'ancienne limite par défaut perdra son statut.')) { return; } } const req$ = current?.id ? this.api.update(current.id, payload) : this.api.create(payload as Omit); req$.subscribe({ next: (result) => { if (!result && current?.id) { // Update failed alert('Erreur lors de la sauvegarde de la limite'); return; } this.closeModal(); this.fetch({ page: this.page(), size: this.size(), search: this.search(), sortKey: this.sort().key, sortDir: this.sort().dir as SortDir, }); if (isSettingDefault && !wasDefault) { alert('La limite a été définie comme limite par défaut. Tous les agents ont été mis à jour.'); } }, error: (err) => { console.error('Error saving limit:', err); alert('Erreur lors de la sauvegarde de la limite'); }, }); } remove(row: AgentLimit) { if (!confirm(`Supprimer la limite ${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, }); }); } }