# Admin List Pages - Pagination & Search Implementation ## Overview All admin list pages (Stores, Merchants, Users) share a consistent pagination and search pattern using **server-side pagination** with Alpine.js. --- ## Files Using This Pattern | Page | HTML Template | JavaScript | |------|---------------|------------| | Stores | `templates/admin/stores.html` | `static/admin/js/stores.js` | | Merchants | `templates/admin/merchants.html` | `static/admin/js/merchants.js` | | Users | `templates/admin/users.html` | `static/admin/js/users.js` | --- ## State Structure ### Filters Object ```javascript filters: { search: '', // Search query string is_active: '', // 'true', 'false', or '' (all) is_verified: '' // 'true', 'false', or '' (all) - stores/merchants only role: '' // 'admin', 'store', or '' (all) - users only } ``` ### Pagination Object ```javascript pagination: { page: 1, // Current page number per_page: 10, // Items per page total: 0, // Total items from API pages: 0 // Total pages (calculated) } ``` --- ## Computed Properties All three pages implement these computed properties: ### `paginatedStores` / `paginatedMerchants` / `users` Returns the items array (already paginated from server): ```javascript get paginatedStores() { return this.stores; } ``` ### `totalPages` ```javascript get totalPages() { return this.pagination.pages; } ``` ### `startIndex` ```javascript get startIndex() { if (this.pagination.total === 0) return 0; return (this.pagination.page - 1) * this.pagination.per_page + 1; } ``` ### `endIndex` ```javascript get endIndex() { const end = this.pagination.page * this.pagination.per_page; return end > this.pagination.total ? this.pagination.total : end; } ``` ### `pageNumbers` Generates smart page number array with ellipsis: ```javascript get pageNumbers() { const pages = []; const totalPages = this.totalPages; const current = this.pagination.page; if (totalPages <= 7) { for (let i = 1; i <= totalPages; i++) { pages.push(i); } } else { pages.push(1); if (current > 3) pages.push('...'); const start = Math.max(2, current - 1); const end = Math.min(totalPages - 1, current + 1); for (let i = start; i <= end; i++) { pages.push(i); } if (current < totalPages - 2) pages.push('...'); pages.push(totalPages); } return pages; } ``` --- ## Methods ### `debouncedSearch()` Triggers search after 300ms delay: ```javascript debouncedSearch() { if (this._searchTimeout) { clearTimeout(this._searchTimeout); } this._searchTimeout = setTimeout(() => { this.pagination.page = 1; this.loadStores(); // or loadMerchants(), loadUsers() }, 300); } ``` ### Pagination Methods ```javascript previousPage() { if (this.pagination.page > 1) { this.pagination.page--; this.loadStores(); } } nextPage() { if (this.pagination.page < this.totalPages) { this.pagination.page++; this.loadStores(); } } goToPage(pageNum) { if (pageNum !== '...' && pageNum >= 1 && pageNum <= this.totalPages) { this.pagination.page = pageNum; this.loadStores(); } } ``` --- ## API Integration ### Building Query Parameters ```javascript async loadStores() { const params = new URLSearchParams(); params.append('skip', (this.pagination.page - 1) * this.pagination.per_page); params.append('limit', this.pagination.per_page); if (this.filters.search) { params.append('search', this.filters.search); } if (this.filters.is_active !== '') { params.append('is_active', this.filters.is_active); } if (this.filters.is_verified !== '') { params.append('is_verified', this.filters.is_verified); } const response = await apiClient.get(`/admin/stores?${params}`); this.stores = response.stores; this.pagination.total = response.total; this.pagination.pages = Math.ceil(response.total / this.pagination.per_page); } ``` ### API Response Format ```json { "stores": [...], "total": 45, "skip": 0, "limit": 10 } ``` --- ## HTML Template Structure ### Search & Filters Bar ```html