docs: update pagination documentation for server-side pattern

- Rewrite pagination.md for server-side pagination approach
- Update quick-start guide with new implementation pattern
- Document shared pattern across Vendors, Companies, Users pages
- Add examples for filters, search, and API integration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-03 21:37:37 +01:00
parent cfa396dfc4
commit f7e3382b10
2 changed files with 444 additions and 413 deletions

View File

@@ -1,297 +1,351 @@
# Vendor Page Pagination Implementation
# Admin List Pages - Pagination & Search Implementation
## 🎯 What's New
## Overview
Your vendors page now includes:
1.**Edit button** - Already present (pencil icon next to view icon)
2.**Pagination** - New pagination controls at the bottom of the table
All admin list pages (Vendors, Companies, Users) share a consistent pagination and search pattern using **server-side pagination** with Alpine.js.
---
## 📦 Files Updated
## Files Using This Pattern
### 1. vendors.html
**Changes:**
- Changed `x-for="vendor in vendors"` `x-for="vendor in paginatedVendors"`
- Added pagination footer with controls
- Added "Showing X-Y of Z" results display
### 2. vendors.js
**Changes:**
- Added pagination state: `currentPage`, `itemsPerPage`
- Added computed properties:
- `paginatedVendors` - Returns current page's vendors
- `totalPages` - Calculates total number of pages
- `startIndex` / `endIndex` - For "Showing X-Y" display
- `pageNumbers` - Generates smart page number array with ellipsis
- Added pagination methods:
- `goToPage(page)` - Navigate to specific page
- `nextPage()` - Go to next page
- `previousPage()` - Go to previous page
| Page | HTML Template | JavaScript |
|------|---------------|------------|
| Vendors | `templates/admin/vendors.html` | `static/admin/js/vendors.js` |
| Companies | `templates/admin/companies.html` | `static/admin/js/companies.js` |
| Users | `templates/admin/users.html` | `static/admin/js/users.js` |
---
## 🎨 Pagination Features
## State Structure
### Smart Page Number Display
The pagination intelligently shows page numbers:
### Filters Object
```javascript
filters: {
search: '', // Search query string
is_active: '', // 'true', 'false', or '' (all)
is_verified: '' // 'true', 'false', or '' (all) - vendors/companies only
role: '' // 'admin', 'vendor', or '' (all) - users only
}
```
**Example 1: Few pages (≤7)**
### 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:
### `paginatedVendors` / `paginatedCompanies` / `users`
Returns the items array (already paginated from server):
```javascript
get paginatedVendors() {
return this.vendors;
}
```
### `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.loadVendors(); // or loadCompanies(), loadUsers()
}, 300);
}
```
### Pagination Methods
```javascript
previousPage() {
if (this.pagination.page > 1) {
this.pagination.page--;
this.loadVendors();
}
}
nextPage() {
if (this.pagination.page < this.totalPages) {
this.pagination.page++;
this.loadVendors();
}
}
goToPage(pageNum) {
if (pageNum !== '...' && pageNum >= 1 && pageNum <= this.totalPages) {
this.pagination.page = pageNum;
this.loadVendors();
}
}
```
---
## API Integration
### Building Query Parameters
```javascript
async loadVendors() {
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/vendors?${params}`);
this.vendors = response.vendors;
this.pagination.total = response.total;
this.pagination.pages = Math.ceil(response.total / this.pagination.per_page);
}
```
### API Response Format
```json
{
"vendors": [...],
"total": 45,
"skip": 0,
"limit": 10
}
```
---
## HTML Template Structure
### Search & Filters Bar
```html
<div class="mb-6 p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<!-- Search Input -->
<div class="flex-1 max-w-md">
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
<span x-html="$icon('search', 'w-5 h-5 text-gray-400')"></span>
</span>
<input
type="text"
x-model="filters.search"
@input="debouncedSearch()"
placeholder="Search..."
class="w-full pl-10 pr-4 py-2 text-sm border rounded-lg..."
>
</div>
</div>
<!-- Filters -->
<div class="flex flex-wrap gap-3">
<select x-model="filters.is_active" @change="pagination.page = 1; loadVendors()">
<option value="">All Status</option>
<option value="true">Active</option>
<option value="false">Inactive</option>
</select>
<select x-model="filters.is_verified" @change="pagination.page = 1; loadVendors()">
<option value="">All Verification</option>
<option value="true">Verified</option>
<option value="false">Pending</option>
</select>
<button @click="loadVendors()">Refresh</button>
</div>
</div>
</div>
```
### Pagination Footer
```html
<div class="grid px-4 py-3 text-xs font-semibold tracking-wide text-gray-500 uppercase border-t bg-gray-50 sm:grid-cols-9">
<!-- Results Info -->
<span class="flex items-center col-span-3">
Showing <span x-text="startIndex"></span>-<span x-text="endIndex"></span>
of <span x-text="pagination.total"></span>
</span>
<span class="col-span-2"></span>
<!-- Pagination Controls -->
<span class="flex col-span-4 mt-2 sm:mt-auto sm:justify-end">
<nav>
<ul class="inline-flex items-center">
<!-- Previous -->
<li>
<button @click="previousPage()" :disabled="pagination.page === 1">
<!-- SVG arrow -->
</button>
</li>
<!-- Page Numbers -->
<template x-for="pageNum in pageNumbers" :key="pageNum">
<li>
<button
x-show="pageNum !== '...'"
@click="goToPage(pageNum)"
:class="pagination.page === pageNum ? 'bg-purple-600 text-white' : ''"
x-text="pageNum"
></button>
<span x-show="pageNum === '...'" class="px-3 py-1">...</span>
</li>
</template>
<!-- Next -->
<li>
<button @click="nextPage()" :disabled="pagination.page === totalPages">
<!-- SVG arrow -->
</button>
</li>
</ul>
</nav>
</span>
</div>
```
---
## Page Number Display Examples
**Few pages (<=7):**
```
← 1 2 3 4 5 6 7 →
```
**Example 2: Many pages, current = 1**
**Many pages, current = 1:**
```
1 2 3 ... 9 10 →
[1] 2 3 ... 10 →
```
**Example 3: Many pages, current = 5**
**Many pages, current = 5:**
```
← 1 ... 4 5 6 ... 10 →
← 1 ... 4 [5] 6 ... 10 →
```
**Example 4: Many pages, current = 10**
**Many pages, current = 10:**
```
← 1 ... 8 9 10 →
```
### Configurable Items Per Page
Default: 10 vendors per page
To change, edit in `vendors.js`:
```javascript
itemsPerPage: 10, // Change this number
← 1 ... 8 9 [10]
```
---
## 🔧 How It Works
## Visual Layout
### 1. Computed Properties (Alpine.js)
Alpine.js computes these values reactively:
```javascript
get paginatedVendors() {
const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
return this.vendors.slice(start, end);
}
```
When `currentPage` changes, `paginatedVendors` automatically updates!
### 2. Template Binding
The HTML template uses the computed property:
```html
<template x-for="vendor in paginatedVendors" :key="vendor.vendor_code">
<!-- Vendor row -->
</template>
```
### 3. Pagination Controls
Buttons call the pagination methods:
```html
<button @click="previousPage()" :disabled="currentPage === 1">
<button @click="goToPage(page)">
<button @click="nextPage()" :disabled="currentPage === totalPages">
┌──────────────────────────────────────────────────────────────────┐
│ Vendor Management [+ Create Vendor] │
├──────────────────────────────────────────────────────────────────┤
│ [Total] [Verified] [Pending] [Inactive] ← Stats Cards │
├──────────────────────────────────────────────────────────────────┤
│ [🔍 Search... ] [Status ▼] [Verified ▼] [Refresh] │
├──────────────────────────────────────────────────────────────────┤
│ Vendor │ Subdomain │ Status │ Created │ Actions │
├────────┼───────────┼──────────┼─────────┼────────────────────────┤
│ Acme │ acme │ Verified │ Jan 1 │ 👁 ✏️ 🗑 │
│ Beta │ beta │ Pending │ Jan 2 │ 👁 ✏️ 🗑 │
│ ... │ ... │ ... │ ... │ ... │
├──────────────────────────────────────────────────────────────────┤
│ Showing 1-10 of 45 ← 1 2 [3] 4 ... 9 → │
└──────────────────────────────────────────────────────────────────┘
```
---
## 📊 Visual Layout
```
┌──────────────────────────────────────────────────────────┐
│ Vendor Management [+ Create Vendor] │
├──────────────────────────────────────────────────────────┤
│ [Total] [Verified] [Pending] [Inactive] ← Stats Cards │
├──────────────────────────────────────────────────────────┤
│ Vendor │ Subdomain │ Status │ Created │ Actions │
├────────┼───────────┼────────┼─────────┼─────────────────┤
│ Acme │ acme │ ✓ │ Jan 1 │ 👁 ✏️ 🗑 │
│ Beta │ beta │ ⏰ │ Jan 2 │ 👁 ✏️ 🗑 │
│ ... │ ... │ ... │ ... │ ... │
├──────────────────────────────────────────────────────────┤
│ Showing 1-10 of 45 ← 1 2 [3] 4 ... 9 → │
└──────────────────────────────────────────────────────────┘
Pagination controls
```
---
## 🎭 Action Buttons Explained
Each vendor row has 3 action buttons:
1. **View Button** (👁 Eye icon - Blue)
- Shows vendor details
- Calls: `viewVendor(vendor.vendor_code)`
- Navigates to: `/admin/vendors/{code}`
2. **Edit Button** (✏️ Pencil icon - Purple) ⭐ NEW
- Opens edit form
- Calls: `editVendor(vendor.vendor_code)`
- Navigates to: `/admin/vendors/{code}/edit`
3. **Delete Button** (🗑 Trash icon - Red)
- Deletes vendor with confirmation
- Calls: `deleteVendor(vendor)`
- Shows confirmation dialog first
---
## 🧪 Testing the Pagination
### Test Scenarios
**Test 1: With few vendors (<10)**
- Pagination should not show if only 1 page
- All vendors visible on page 1
**Test 2: With many vendors (>10)**
- Pagination controls appear
- Can navigate between pages
- "Previous" disabled on page 1
- "Next" disabled on last page
**Test 3: Navigation**
- Click page numbers to jump to specific page
- Click "Previous" arrow to go back
- Click "Next" arrow to go forward
- Page numbers update dynamically
**Test 4: After Delete**
- Delete a vendor
- Data reloads
- Returns to page 1
- Pagination updates
---
## 🎨 Styling
The pagination uses the same Windmill theme as your dashboard:
- **Normal buttons**: Gray with hover effect
- **Current page**: Purple background (matches your theme)
- **Disabled buttons**: 50% opacity, cursor not-allowed
- **Hover effects**: Light gray background
- **Dark mode**: Full support
---
## 🔧 Customization Options
## Configuration
### Change Items Per Page
In `vendors.js`:
In the JavaScript file:
```javascript
itemsPerPage: 20, // Show 20 vendors per page
```
### Change Page Number Display Logic
In `vendors.js`, modify the `pageNumbers` computed property:
```javascript
get pageNumbers() {
// Modify this logic to change how page numbers appear
pagination: {
page: 1,
per_page: 25, // Change from 10 to 25
total: 0,
pages: 0
}
```
### Change Pagination Position
In `vendors.html`, the pagination footer is at line ~220.
Move this entire `<div class="grid px-4 py-3...">` section.
---
## 📝 Code Comparison
## Features Summary
### Before (No Pagination)
```javascript
// vendors.js
vendors: [], // All vendors displayed at once
// vendors.html
<template x-for="vendor in vendors">
```
### After (With Pagination)
```javascript
// vendors.js
vendors: [],
currentPage: 1,
itemsPerPage: 10,
get paginatedVendors() {
return this.vendors.slice(start, end);
}
// vendors.html
<template x-for="vendor in paginatedVendors">
```
---
## ✅ Installation Checklist
Replace these files in your project:
- [ ] `static/admin/js/vendors.js` → Use `vendors-with-pagination.js`
- [ ] `templates/admin/vendors.html` → Use `vendors-with-pagination.html`
- [ ] Clear browser cache (Ctrl+Shift+R)
- [ ] Test with >10 vendors to see pagination
- [ ] Test edit button works
- [ ] Test page navigation
- [ ] Test in dark mode
---
## 🆘 Troubleshooting
### Pagination not showing?
- Make sure you have more than 10 vendors
- Check `itemsPerPage` value in vendors.js
- Verify `paginatedVendors` is used in template
### Edit button not working?
- Check browser console for errors
- Verify `editVendor()` method exists in vendors.js
- Check the route exists on your backend: `/admin/vendors/{code}/edit`
### Page numbers not updating?
- This is a computed property - it should update automatically
- Check Alpine.js is loaded correctly
- Clear browser cache
### "Showing X-Y of Z" incorrect?
- Check `startIndex` and `endIndex` computed properties
- Verify `vendors.length` is correct
---
## 💡 Tips
1. **Performance**: Pagination is client-side. For 1000+ vendors, consider server-side pagination.
2. **State Preservation**: When returning from edit page, user goes back to page 1. To preserve page state, you'd need to:
- Store `currentPage` in localStorage
- Restore it on page load
3. **Search/Filter**: To add search, filter `vendors` array first, then paginate the filtered results.
4. **Sorting**: Add sorting before pagination to maintain consistent page content.
---
## 🎉 Features Summary
✅ Edit button added (pencil icon)
✅ Smart pagination (10 items per page)
✅ Dynamic page numbers with ellipsis
✅ Previous/Next navigation
✅ Disabled states for boundary pages
✅ "Showing X-Y of Z" display
✅ Dark mode support
✅ Fully responsive
✅ Follows Windmill design theme
Happy paginating! 📖
- Server-side pagination with `skip`/`limit` API params
- Debounced search (300ms delay)
- Multiple filter dropdowns
- Smart page number display with ellipsis
- Refresh button
- Contextual empty state messages
- Dark mode support
- Responsive design
- Consistent across all admin list pages