first commit
This commit is contained in:
294
src/app/dashboard/pages/limits/limits.ts
Normal file
294
src/app/dashboard/pages/limits/limits.ts
Normal file
@@ -0,0 +1,294 @@
|
||||
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<AgentLimit[]>([]);
|
||||
total = signal(0);
|
||||
loading = signal(false);
|
||||
page = signal(1);
|
||||
perPage = signal(10);
|
||||
search = signal('');
|
||||
sort = signal<SortState>({ key: 'code', dir: 'asc' });
|
||||
selectedActif = signal<boolean | null>(null);
|
||||
|
||||
modalOpen = signal(false);
|
||||
modalTitle = signal('Nouvelle limite');
|
||||
editingItem = signal<AgentLimit | null>(null);
|
||||
|
||||
// Live search
|
||||
private searchSubject = new Subject<string>();
|
||||
|
||||
@ViewChild(LimitForm) formComp?: LimitForm;
|
||||
|
||||
cols: TableColumn<AgentLimit>[] = [
|
||||
{ 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
|
||||
? '<span class="inline-flex items-center gap-1 px-2 py-1 rounded bg-primary/10 text-primary text-xs font-medium"><i class="icon-star"></i> Par défaut</span>'
|
||||
: '<span class="text-muted-foreground">—</span>',
|
||||
},
|
||||
{
|
||||
key: 'actif',
|
||||
label: 'Actif',
|
||||
cell: (l) =>
|
||||
l.actif
|
||||
? '<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>'
|
||||
: '<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>',
|
||||
},
|
||||
{
|
||||
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, perPage, or sort changes (not search - handled by searchSubject)
|
||||
const searchValue = this.search();
|
||||
const params = {
|
||||
page: this.page(),
|
||||
perPage: this.perPage(),
|
||||
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(),
|
||||
perPage: this.perPage(),
|
||||
search: '',
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
}).pipe(
|
||||
switchMap((res) => {
|
||||
// Convert PagedResult to array for consistency
|
||||
return of(res.data);
|
||||
})
|
||||
);
|
||||
}
|
||||
})
|
||||
)
|
||||
.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(),
|
||||
perPage: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
});
|
||||
}
|
||||
|
||||
private fetch(params: { page: number; perPage: 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.data);
|
||||
this.total.set(res.meta.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(),
|
||||
perPage: this.perPage(),
|
||||
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(),
|
||||
perPage: this.perPage(),
|
||||
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<AgentLimit>) {
|
||||
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<AgentLimit, 'id'>);
|
||||
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(),
|
||||
perPage: this.perPage(),
|
||||
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(),
|
||||
perPage: this.perPage(),
|
||||
search: this.search(),
|
||||
sortKey: this.sort().key,
|
||||
sortDir: this.sort().dir as SortDir,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user