Files
Samir Boulahtit 14d5ff97f3
Some checks failed
CI / ruff (push) Successful in 10s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled
fix(prospecting): add missing pagination computed properties to JS components
The pagination() macro expects startIndex, endIndex, pageNumbers, totalPages,
nextPage(), and previousPage() to be defined in the Alpine.js component.
Added these to scan-jobs.js, prospects.js, and leads.js.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 23:27:46 +01:00

144 lines
5.0 KiB
JavaScript

// static/admin/js/leads.js
const leadsLog = window.LogConfig.createLogger('prospecting-leads');
function leadsList() {
return {
...data(),
currentPage: 'leads',
leads: [],
loading: true,
error: null,
// Filters
minScore: 0,
filterTier: '',
filterChannel: '',
filterIssue: '',
filterHasEmail: '',
// Pagination
pagination: { page: 1, per_page: 20, total: 0, pages: 0 },
async init() {
await I18n.loadModule('prospecting');
if (window._leadsListInit) return;
window._leadsListInit = true;
if (window.PlatformSettings) {
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
}
leadsLog.info('Leads list initializing');
await this.loadLeads();
},
async loadLeads() {
this.loading = true;
this.error = null;
try {
const params = new URLSearchParams({
page: this.pagination.page,
per_page: this.pagination.per_page,
});
if (this.minScore > 0) params.set('min_score', this.minScore);
if (this.filterTier) params.set('lead_tier', this.filterTier);
if (this.filterChannel) params.set('channel', this.filterChannel);
if (this.filterIssue) params.set('reason_flag', this.filterIssue);
if (this.filterHasEmail) params.set('has_email', this.filterHasEmail);
const response = await apiClient.get('/admin/prospecting/leads?' + params);
this.leads = response.items || [];
this.pagination.total = response.total || 0;
this.pagination.pages = response.pages || 0;
} catch (err) {
this.error = err.message;
leadsLog.error('Failed to load leads', err);
} finally {
this.loading = false;
}
},
async exportCSV() {
try {
const params = new URLSearchParams();
if (this.minScore > 0) params.set('min_score', this.minScore);
if (this.filterTier) params.set('lead_tier', this.filterTier);
if (this.filterChannel) params.set('channel', this.filterChannel);
const url = '/admin/prospecting/leads/export/csv?' + params;
window.open('/api/v1' + url, '_blank');
Utils.showToast('CSV export started', 'success');
} catch (err) {
Utils.showToast('Export failed: ' + err.message, 'error');
}
},
sendCampaign(lead) {
window.location.href = '/admin/prospecting/prospects/' + lead.id + '#campaigns';
},
get startIndex() {
if (this.pagination.total === 0) return 0;
return (this.pagination.page - 1) * this.pagination.per_page + 1;
},
get endIndex() {
const end = this.pagination.page * this.pagination.per_page;
return end > this.pagination.total ? this.pagination.total : end;
},
get totalPages() {
return this.pagination.pages;
},
get pageNumbers() {
const pages = [];
const total = this.totalPages;
const current = this.pagination.page;
if (total <= 7) { for (let i = 1; i <= total; i++) pages.push(i); return pages; }
pages.push(1);
if (current > 3) pages.push('...');
for (let i = Math.max(2, current - 1); i <= Math.min(total - 1, current + 1); i++) pages.push(i);
if (current < total - 2) pages.push('...');
pages.push(total);
return pages;
},
goToPage(page) {
if (page === '...' || page < 1 || page > this.totalPages) return;
this.pagination.page = page;
this.loadLeads();
},
nextPage() {
if (this.pagination.page < this.totalPages) { this.pagination.page++; this.loadLeads(); }
},
previousPage() {
if (this.pagination.page > 1) { this.pagination.page--; this.loadLeads(); }
},
tierBadgeClass(tier) {
const classes = {
top_priority: 'text-red-700 bg-red-100 dark:text-red-100 dark:bg-red-700',
quick_win: 'text-orange-700 bg-orange-100 dark:text-orange-100 dark:bg-orange-700',
strategic: 'text-blue-700 bg-blue-100 dark:text-blue-100 dark:bg-blue-700',
low_priority: 'text-gray-700 bg-gray-100 dark:text-gray-100 dark:bg-gray-700',
};
return classes[tier] || classes.low_priority;
},
scoreColor(score) {
if (score == null) return 'text-gray-400';
if (score >= 70) return 'text-red-600';
if (score >= 50) return 'text-orange-600';
if (score >= 30) return 'text-blue-600';
return 'text-gray-600';
},
};
}