feat: add PlatformSettings for pagination and vendor filter improvements

Platform Settings:
- Add PlatformSettings utility in init-alpine.js with 5-min cache
- Add Display tab in /admin/settings for rows_per_page config
- Integrate PlatformSettings.getRowsPerPage() in all paginated pages
- Standardize default per_page to 20 across all admin pages
- Add documentation at docs/frontend/shared/platform-settings.md

Architecture Rules:
- Add JS-010: enforce PlatformSettings usage for pagination
- Add JS-011: enforce standard pagination structure
- Add JS-012: detect double /api/v1 prefix in apiClient calls
- Implement all rules in validate_architecture.py

Vendor Filter (Tom Select):
- Add vendor filter to marketplace-products, vendor-products,
  customers, inventory, and vendor-themes pages
- Add selectedVendor display panel with clear button
- Add localStorage persistence for vendor selection
- Fix double /api/v1 prefix in vendor-selector.js

Bug Fixes:
- Remove duplicate PlatformSettings from utils.js
- Fix customers.js pagination structure (page_size → per_page)
- Fix code-quality-violations.js pagination structure

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-22 22:39:34 +01:00
parent 1274135091
commit 6f8434f200
27 changed files with 1966 additions and 303 deletions

View File

@@ -47,13 +47,16 @@ function adminInventory() {
// Available locations for filter dropdown
locations: [],
// Selected vendor (for prominent display and filtering)
selectedVendor: null,
// Vendor selector controller (Tom Select)
vendorSelector: null,
// Pagination
pagination: {
page: 1,
per_page: 50,
per_page: 20,
total: 0,
pages: 0
},
@@ -131,21 +134,68 @@ function adminInventory() {
}
window._adminInventoryInitialized = true;
// Load platform settings for rows per page
if (window.PlatformSettings) {
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
}
// Initialize vendor selector (Tom Select)
this.$nextTick(() => {
this.initVendorSelector();
});
// Load data in parallel
await Promise.all([
this.loadStats(),
this.loadLocations(),
this.loadInventory()
]);
// Check localStorage for saved vendor
const savedVendorId = localStorage.getItem('inventory_selected_vendor_id');
if (savedVendorId) {
adminInventoryLog.info('Restoring saved vendor:', savedVendorId);
// Restore vendor after a short delay to ensure TomSelect is ready
setTimeout(async () => {
await this.restoreSavedVendor(parseInt(savedVendorId));
}, 200);
// Load stats and locations but not inventory (restoreSavedVendor will do that)
await Promise.all([
this.loadStats(),
this.loadLocations()
]);
} else {
// No saved vendor - load all data
await Promise.all([
this.loadStats(),
this.loadLocations(),
this.loadInventory()
]);
}
adminInventoryLog.info('Inventory initialization complete');
},
/**
* Restore saved vendor from localStorage
*/
async restoreSavedVendor(vendorId) {
try {
const vendor = await apiClient.get(`/admin/vendors/${vendorId}`);
if (this.vendorSelector && vendor) {
// Use the vendor selector's setValue method
this.vendorSelector.setValue(vendor.id, vendor);
// Set the filter state
this.selectedVendor = vendor;
this.filters.vendor_id = vendor.id;
adminInventoryLog.info('Restored vendor:', vendor.name);
// Load inventory with the vendor filter applied
await this.loadInventory();
}
} catch (error) {
adminInventoryLog.warn('Failed to restore saved vendor, clearing localStorage:', error);
localStorage.removeItem('inventory_selected_vendor_id');
// Load unfiltered inventory as fallback
await this.loadInventory();
}
},
/**
* Initialize vendor selector with Tom Select
*/
@@ -159,27 +209,57 @@ function adminInventory() {
placeholder: 'Filter by vendor...',
onSelect: (vendor) => {
adminInventoryLog.info('Vendor selected:', vendor);
this.selectedVendor = vendor;
this.filters.vendor_id = vendor.id;
// Save to localStorage
localStorage.setItem('inventory_selected_vendor_id', vendor.id.toString());
this.pagination.page = 1;
this.loadLocations();
this.loadInventory();
this.loadStats();
},
onClear: () => {
adminInventoryLog.info('Vendor filter cleared');
this.selectedVendor = null;
this.filters.vendor_id = '';
// Clear from localStorage
localStorage.removeItem('inventory_selected_vendor_id');
this.pagination.page = 1;
this.loadLocations();
this.loadInventory();
this.loadStats();
}
});
},
/**
* Clear vendor filter
*/
clearVendorFilter() {
if (this.vendorSelector) {
this.vendorSelector.clear();
}
this.selectedVendor = null;
this.filters.vendor_id = '';
// Clear from localStorage
localStorage.removeItem('inventory_selected_vendor_id');
this.pagination.page = 1;
this.loadLocations();
this.loadInventory();
this.loadStats();
},
/**
* Load inventory statistics
*/
async loadStats() {
try {
const response = await apiClient.get('/admin/inventory/stats');
const params = new URLSearchParams();
if (this.filters.vendor_id) {
params.append('vendor_id', this.filters.vendor_id);
}
const url = params.toString() ? `/admin/inventory/stats?${params}` : '/admin/inventory/stats';
const response = await apiClient.get(url);
this.stats = response;
adminInventoryLog.info('Loaded stats:', this.stats);
} catch (error) {