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

@@ -187,83 +187,8 @@ const Utils = {
// ============================================================================
// Platform Settings
// ============================================================================
/**
* Platform settings cache and loader
*/
const PlatformSettings = {
_settings: null,
_loading: false,
_loadPromise: null,
/**
* Load platform display settings from API
* @returns {Promise<Object>} Platform settings
*/
async load() {
// Return cached settings if available
if (this._settings) {
return this._settings;
}
// Return existing promise if already loading
if (this._loadPromise) {
return this._loadPromise;
}
this._loading = true;
this._loadPromise = (async () => {
try {
const response = await fetch('/api/v1/admin/settings/display/public');
if (response.ok) {
this._settings = await response.json();
} else {
// Default settings
this._settings = { rows_per_page: 20 };
}
} catch (error) {
console.warn('Failed to load platform settings, using defaults:', error);
this._settings = { rows_per_page: 20 };
}
this._loading = false;
return this._settings;
})();
return this._loadPromise;
},
/**
* Get rows per page setting
* @returns {number} Rows per page (default: 20)
*/
getRowsPerPage() {
return this._settings?.rows_per_page || 20;
},
/**
* Get rows per page synchronously (returns cached or default)
* @returns {number} Rows per page
*/
get rowsPerPage() {
return this._settings?.rows_per_page || 20;
},
/**
* Reset cached settings (call after updating settings)
*/
reset() {
this._settings = null;
this._loadPromise = null;
}
};
// Load settings on page load
document.addEventListener('DOMContentLoaded', () => {
PlatformSettings.load();
});
// Make available globally
window.PlatformSettings = PlatformSettings;
// Note: PlatformSettings is defined in init-alpine.js for admin pages
window.Utils = Utils;
// Export for modules

View File

@@ -66,7 +66,7 @@ function waitForTomSelect(callback, maxRetries = 20, retryDelay = 100) {
* @param {number} options.minChars - Minimum characters before search (default: 2)
* @param {number} options.maxOptions - Maximum options to show (default: 50)
* @param {string} options.placeholder - Placeholder text
* @param {string} options.apiEndpoint - API endpoint for search (default: '/api/v1/admin/vendors')
* @param {string} options.apiEndpoint - API endpoint for search (default: '/admin/vendors')
* @returns {Object} Controller object with setValue() and clear() methods
*/
function initVendorSelector(selectElement, options = {}) {
@@ -79,7 +79,7 @@ function initVendorSelector(selectElement, options = {}) {
minChars: options.minChars || 2,
maxOptions: options.maxOptions || 50,
placeholder: options.placeholder || selectElement.getAttribute('placeholder') || 'Search vendor by name or code...',
apiEndpoint: options.apiEndpoint || '/api/v1/admin/vendors',
apiEndpoint: options.apiEndpoint || '/admin/vendors', // Note: apiClient adds /api/v1 prefix
onSelect: options.onSelect || (() => {}),
onClear: options.onClear || (() => {})
};