295 lines
9.3 KiB
TypeScript
295 lines
9.3 KiB
TypeScript
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);
|
|
size = 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, 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<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(),
|
|
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,
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|