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:
@@ -20,9 +20,9 @@ function codeQualityViolations() {
|
||||
violations: [],
|
||||
pagination: {
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
per_page: 20,
|
||||
total: 0,
|
||||
total_pages: 0
|
||||
pages: 0
|
||||
},
|
||||
filters: {
|
||||
validator_type: '',
|
||||
@@ -33,6 +33,18 @@ function codeQualityViolations() {
|
||||
},
|
||||
|
||||
async init() {
|
||||
// Guard against multiple initialization
|
||||
if (window._codeQualityViolationsInitialized) {
|
||||
codeQualityViolationsLog.warn('Already initialized, skipping');
|
||||
return;
|
||||
}
|
||||
window._codeQualityViolationsInitialized = true;
|
||||
|
||||
// Load platform settings for rows per page
|
||||
if (window.PlatformSettings) {
|
||||
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
||||
}
|
||||
|
||||
// Load filters from URL params
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
this.filters.validator_type = params.get('validator_type') || '';
|
||||
@@ -52,7 +64,7 @@ function codeQualityViolations() {
|
||||
// Build query params
|
||||
const params = {
|
||||
page: this.pagination.page.toString(),
|
||||
page_size: this.pagination.page_size.toString()
|
||||
page_size: this.pagination.per_page.toString()
|
||||
};
|
||||
|
||||
if (this.filters.validator_type) params.validator_type = this.filters.validator_type;
|
||||
@@ -64,12 +76,8 @@ function codeQualityViolations() {
|
||||
const data = await apiClient.get('/admin/code-quality/violations', params);
|
||||
|
||||
this.violations = data.violations;
|
||||
this.pagination = {
|
||||
page: data.page,
|
||||
page_size: data.page_size,
|
||||
total: data.total,
|
||||
total_pages: data.total_pages
|
||||
};
|
||||
this.pagination.total = data.total;
|
||||
this.pagination.pages = data.total_pages;
|
||||
|
||||
// Update URL with current filters (without reloading)
|
||||
this.updateURL();
|
||||
@@ -93,7 +101,7 @@ function codeQualityViolations() {
|
||||
},
|
||||
|
||||
async nextPage() {
|
||||
if (this.pagination.page < this.pagination.total_pages) {
|
||||
if (this.pagination.page < this.pagination.pages) {
|
||||
this.pagination.page++;
|
||||
await this.loadViolations();
|
||||
}
|
||||
@@ -101,18 +109,18 @@ function codeQualityViolations() {
|
||||
|
||||
// Computed: Total number of pages
|
||||
get totalPages() {
|
||||
return this.pagination.total_pages;
|
||||
return this.pagination.pages;
|
||||
},
|
||||
|
||||
// Computed: Start index for pagination display
|
||||
get startIndex() {
|
||||
if (this.pagination.total === 0) return 0;
|
||||
return (this.pagination.page - 1) * this.pagination.page_size + 1;
|
||||
return (this.pagination.page - 1) * this.pagination.per_page + 1;
|
||||
},
|
||||
|
||||
// Computed: End index for pagination display
|
||||
get endIndex() {
|
||||
const end = this.pagination.page * this.pagination.page_size;
|
||||
const end = this.pagination.page * this.pagination.per_page;
|
||||
return end > this.pagination.total ? this.pagination.total : end;
|
||||
},
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ function adminCompanies() {
|
||||
// Pagination state
|
||||
pagination: {
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
per_page: 20,
|
||||
total: 0,
|
||||
pages: 0
|
||||
},
|
||||
@@ -51,6 +51,11 @@ function adminCompanies() {
|
||||
}
|
||||
window._companiesInitialized = true;
|
||||
|
||||
// Load platform settings for rows per page
|
||||
if (window.PlatformSettings) {
|
||||
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
||||
}
|
||||
|
||||
companiesLog.group('Loading companies data');
|
||||
await this.loadCompanies();
|
||||
companiesLog.groupEnd();
|
||||
|
||||
@@ -23,7 +23,6 @@ function adminCustomers() {
|
||||
|
||||
// Data
|
||||
customers: [],
|
||||
vendors: [],
|
||||
stats: {
|
||||
total: 0,
|
||||
active: 0,
|
||||
@@ -34,11 +33,13 @@ function adminCustomers() {
|
||||
avg_order_value: 0
|
||||
},
|
||||
|
||||
// Pagination
|
||||
page: 1,
|
||||
limit: 20,
|
||||
total: 0,
|
||||
skip: 0,
|
||||
// Pagination (standard structure matching pagination macro)
|
||||
pagination: {
|
||||
page: 1,
|
||||
per_page: 20,
|
||||
total: 0,
|
||||
pages: 0
|
||||
},
|
||||
|
||||
// Filters
|
||||
filters: {
|
||||
@@ -47,51 +48,216 @@ function adminCustomers() {
|
||||
vendor_id: ''
|
||||
},
|
||||
|
||||
// Selected vendor (for prominent display and filtering)
|
||||
selectedVendor: null,
|
||||
|
||||
// Tom Select instance
|
||||
vendorSelectInstance: null,
|
||||
|
||||
// Computed: total pages
|
||||
get totalPages() {
|
||||
return Math.ceil(this.total / this.limit);
|
||||
return this.pagination.pages;
|
||||
},
|
||||
|
||||
// Computed: Start index for pagination display
|
||||
get startIndex() {
|
||||
if (this.pagination.total === 0) return 0;
|
||||
return (this.pagination.page - 1) * this.pagination.per_page + 1;
|
||||
},
|
||||
|
||||
// Computed: End index for pagination display
|
||||
get endIndex() {
|
||||
const end = this.pagination.page * this.pagination.per_page;
|
||||
return end > this.pagination.total ? this.pagination.total : end;
|
||||
},
|
||||
|
||||
// Computed: Page numbers for pagination
|
||||
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;
|
||||
},
|
||||
|
||||
async init() {
|
||||
customersLog.debug('Customers page initialized');
|
||||
|
||||
// Load vendors for filter dropdown
|
||||
await this.loadVendors();
|
||||
// Load platform settings for rows per page
|
||||
if (window.PlatformSettings) {
|
||||
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
||||
}
|
||||
|
||||
// Load initial data
|
||||
await Promise.all([
|
||||
this.loadCustomers(),
|
||||
this.loadStats()
|
||||
]);
|
||||
// Initialize Tom Select for vendor filter
|
||||
this.initVendorSelect();
|
||||
|
||||
// Check localStorage for saved vendor
|
||||
const savedVendorId = localStorage.getItem('customers_selected_vendor_id');
|
||||
if (savedVendorId) {
|
||||
customersLog.debug('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 but not customers (restoreSavedVendor will do that)
|
||||
await this.loadStats();
|
||||
} else {
|
||||
// No saved vendor - load all data
|
||||
await Promise.all([
|
||||
this.loadCustomers(),
|
||||
this.loadStats()
|
||||
]);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Load vendors for filter dropdown
|
||||
* Restore saved vendor from localStorage
|
||||
*/
|
||||
async loadVendors() {
|
||||
async restoreSavedVendor(vendorId) {
|
||||
try {
|
||||
const response = await apiClient.get('/admin/vendors?limit=100');
|
||||
this.vendors = response.vendors || [];
|
||||
const vendor = await apiClient.get(`/admin/vendors/${vendorId}`);
|
||||
if (this.vendorSelectInstance && vendor) {
|
||||
// Add the vendor as an option and select it
|
||||
this.vendorSelectInstance.addOption({
|
||||
id: vendor.id,
|
||||
name: vendor.name,
|
||||
vendor_code: vendor.vendor_code
|
||||
});
|
||||
this.vendorSelectInstance.setValue(vendor.id, true);
|
||||
|
||||
// Set the filter state
|
||||
this.selectedVendor = vendor;
|
||||
this.filters.vendor_id = vendor.id;
|
||||
|
||||
customersLog.debug('Restored vendor:', vendor.name);
|
||||
|
||||
// Load customers with the vendor filter applied
|
||||
await this.loadCustomers();
|
||||
}
|
||||
} catch (error) {
|
||||
customersLog.error('Failed to load vendors:', error);
|
||||
this.vendors = [];
|
||||
customersLog.error('Failed to restore saved vendor, clearing localStorage:', error);
|
||||
localStorage.removeItem('customers_selected_vendor_id');
|
||||
// Load unfiltered customers as fallback
|
||||
await this.loadCustomers();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize Tom Select for vendor autocomplete
|
||||
*/
|
||||
initVendorSelect() {
|
||||
const selectEl = this.$refs.vendorSelect;
|
||||
if (!selectEl) {
|
||||
customersLog.warn('Vendor select element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for Tom Select to be available
|
||||
if (typeof TomSelect === 'undefined') {
|
||||
customersLog.warn('TomSelect not loaded, retrying in 100ms');
|
||||
setTimeout(() => this.initVendorSelect(), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
this.vendorSelectInstance = new TomSelect(selectEl, {
|
||||
valueField: 'id',
|
||||
labelField: 'name',
|
||||
searchField: ['name', 'vendor_code'],
|
||||
placeholder: 'Filter by vendor...',
|
||||
allowEmptyOption: true,
|
||||
load: async (query, callback) => {
|
||||
try {
|
||||
const response = await apiClient.get('/admin/vendors', {
|
||||
search: query,
|
||||
limit: 50
|
||||
});
|
||||
callback(response.vendors || []);
|
||||
} catch (error) {
|
||||
customersLog.error('Failed to search vendors:', error);
|
||||
callback([]);
|
||||
}
|
||||
},
|
||||
render: {
|
||||
option: (data, escape) => {
|
||||
return `<div class="flex items-center justify-between py-1">
|
||||
<span>${escape(data.name)}</span>
|
||||
<span class="text-xs text-gray-400 font-mono">${escape(data.vendor_code || '')}</span>
|
||||
</div>`;
|
||||
},
|
||||
item: (data, escape) => {
|
||||
return `<div>${escape(data.name)}</div>`;
|
||||
}
|
||||
},
|
||||
onChange: (value) => {
|
||||
if (value) {
|
||||
const vendor = this.vendorSelectInstance.options[value];
|
||||
this.selectedVendor = vendor;
|
||||
this.filters.vendor_id = value;
|
||||
// Save to localStorage
|
||||
localStorage.setItem('customers_selected_vendor_id', value.toString());
|
||||
} else {
|
||||
this.selectedVendor = null;
|
||||
this.filters.vendor_id = '';
|
||||
// Clear from localStorage
|
||||
localStorage.removeItem('customers_selected_vendor_id');
|
||||
}
|
||||
this.pagination.page = 1;
|
||||
this.loadCustomers();
|
||||
this.loadStats();
|
||||
}
|
||||
});
|
||||
|
||||
customersLog.debug('Vendor select initialized');
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear vendor filter
|
||||
*/
|
||||
clearVendorFilter() {
|
||||
if (this.vendorSelectInstance) {
|
||||
this.vendorSelectInstance.clear();
|
||||
}
|
||||
this.selectedVendor = null;
|
||||
this.filters.vendor_id = '';
|
||||
// Clear from localStorage
|
||||
localStorage.removeItem('customers_selected_vendor_id');
|
||||
this.pagination.page = 1;
|
||||
this.loadCustomers();
|
||||
this.loadStats();
|
||||
},
|
||||
|
||||
/**
|
||||
* Load customers with current filters
|
||||
*/
|
||||
async loadCustomers() {
|
||||
this.loadingCustomers = true;
|
||||
this.error = '';
|
||||
this.skip = (this.page - 1) * this.limit;
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
skip: this.skip.toString(),
|
||||
limit: this.limit.toString()
|
||||
skip: ((this.pagination.page - 1) * this.pagination.per_page).toString(),
|
||||
limit: this.pagination.per_page.toString()
|
||||
});
|
||||
|
||||
if (this.filters.search) {
|
||||
@@ -108,7 +274,8 @@ function adminCustomers() {
|
||||
|
||||
const response = await apiClient.get(`/admin/customers?${params}`);
|
||||
this.customers = response.customers || [];
|
||||
this.total = response.total || 0;
|
||||
this.pagination.total = response.total || 0;
|
||||
this.pagination.pages = Math.ceil(this.pagination.total / this.pagination.per_page);
|
||||
} catch (error) {
|
||||
customersLog.error('Failed to load customers:', error);
|
||||
this.error = error.message || 'Failed to load customers';
|
||||
@@ -139,7 +306,7 @@ function adminCustomers() {
|
||||
* Reset pagination and reload
|
||||
*/
|
||||
async resetAndLoad() {
|
||||
this.page = 1;
|
||||
this.pagination.page = 1;
|
||||
await Promise.all([
|
||||
this.loadCustomers(),
|
||||
this.loadStats()
|
||||
@@ -147,34 +314,33 @@ function adminCustomers() {
|
||||
},
|
||||
|
||||
/**
|
||||
* Go to specific page
|
||||
* Go to previous page
|
||||
*/
|
||||
goToPage(p) {
|
||||
this.page = p;
|
||||
this.loadCustomers();
|
||||
previousPage() {
|
||||
if (this.pagination.page > 1) {
|
||||
this.pagination.page--;
|
||||
this.loadCustomers();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get array of page numbers to display
|
||||
* Go to next page
|
||||
*/
|
||||
getPageNumbers() {
|
||||
const total = this.totalPages;
|
||||
const current = this.page;
|
||||
const maxVisible = 5;
|
||||
|
||||
if (total <= maxVisible) {
|
||||
return Array.from({length: total}, (_, i) => i + 1);
|
||||
nextPage() {
|
||||
if (this.pagination.page < this.totalPages) {
|
||||
this.pagination.page++;
|
||||
this.loadCustomers();
|
||||
}
|
||||
},
|
||||
|
||||
const half = Math.floor(maxVisible / 2);
|
||||
let start = Math.max(1, current - half);
|
||||
let end = Math.min(total, start + maxVisible - 1);
|
||||
|
||||
if (end - start < maxVisible - 1) {
|
||||
start = Math.max(1, end - maxVisible + 1);
|
||||
/**
|
||||
* Go to specific page
|
||||
*/
|
||||
goToPage(pageNum) {
|
||||
if (typeof pageNum === 'number' && pageNum !== this.pagination.page) {
|
||||
this.pagination.page = pageNum;
|
||||
this.loadCustomers();
|
||||
}
|
||||
|
||||
return Array.from({length: end - start + 1}, (_, i) => start + i);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -115,6 +115,11 @@ function adminImports() {
|
||||
}
|
||||
window._adminImportsInitialized = true;
|
||||
|
||||
// Load platform settings for rows per page
|
||||
if (window.PlatformSettings) {
|
||||
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
||||
}
|
||||
|
||||
// IMPORTANT: Call parent init first
|
||||
const parentInit = data().init;
|
||||
if (parentInit) {
|
||||
|
||||
@@ -257,4 +257,64 @@ function headerMessages() {
|
||||
}
|
||||
|
||||
// Export to window
|
||||
window.headerMessages = headerMessages;
|
||||
window.headerMessages = headerMessages;
|
||||
|
||||
/**
|
||||
* Platform Settings Utility
|
||||
* Provides cached access to platform-wide settings
|
||||
*/
|
||||
const PlatformSettings = {
|
||||
// Cache key and TTL
|
||||
CACHE_KEY: 'platform_settings_cache',
|
||||
CACHE_TTL: 5 * 60 * 1000, // 5 minutes
|
||||
|
||||
/**
|
||||
* Get cached settings or fetch from API
|
||||
*/
|
||||
async get() {
|
||||
try {
|
||||
const cached = localStorage.getItem(this.CACHE_KEY);
|
||||
if (cached) {
|
||||
const { data, timestamp } = JSON.parse(cached);
|
||||
if (Date.now() - timestamp < this.CACHE_TTL) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from API
|
||||
const response = await apiClient.get('/admin/settings/display/public');
|
||||
const settings = {
|
||||
rows_per_page: response.rows_per_page || 20
|
||||
};
|
||||
|
||||
// Cache the result
|
||||
localStorage.setItem(this.CACHE_KEY, JSON.stringify({
|
||||
data: settings,
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
|
||||
return settings;
|
||||
} catch (error) {
|
||||
console.warn('Failed to load platform settings, using defaults:', error);
|
||||
return { rows_per_page: 20 };
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get rows per page setting
|
||||
*/
|
||||
async getRowsPerPage() {
|
||||
const settings = await this.get();
|
||||
return settings.rows_per_page;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the cache (call after saving settings)
|
||||
*/
|
||||
clearCache() {
|
||||
localStorage.removeItem(this.CACHE_KEY);
|
||||
}
|
||||
};
|
||||
|
||||
// Export to window
|
||||
window.PlatformSettings = PlatformSettings;
|
||||
@@ -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) {
|
||||
|
||||
@@ -32,7 +32,7 @@ function adminLogs() {
|
||||
},
|
||||
pagination: {
|
||||
page: 1,
|
||||
per_page: 50,
|
||||
per_page: 20,
|
||||
total: 0,
|
||||
pages: 0
|
||||
},
|
||||
@@ -86,7 +86,20 @@ function adminLogs() {
|
||||
},
|
||||
|
||||
async init() {
|
||||
// Guard against multiple initialization
|
||||
if (window._adminLogsInitialized) {
|
||||
logsLog.warn('Already initialized, skipping');
|
||||
return;
|
||||
}
|
||||
window._adminLogsInitialized = true;
|
||||
|
||||
logsLog.info('=== LOGS PAGE INITIALIZING ===');
|
||||
|
||||
// Load platform settings for rows per page
|
||||
if (window.PlatformSettings) {
|
||||
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
||||
}
|
||||
|
||||
await this.loadStats();
|
||||
await this.loadLogs();
|
||||
},
|
||||
|
||||
@@ -43,16 +43,19 @@ function adminMarketplaceProducts() {
|
||||
is_digital: ''
|
||||
},
|
||||
|
||||
// Selected vendor (for prominent display and filtering)
|
||||
selectedVendor: null,
|
||||
|
||||
// Tom Select instance
|
||||
vendorSelectInstance: null,
|
||||
|
||||
// Available marketplaces for filter dropdown
|
||||
marketplaces: [],
|
||||
|
||||
// Available source vendors for filter dropdown
|
||||
sourceVendors: [],
|
||||
|
||||
// Pagination
|
||||
pagination: {
|
||||
page: 1,
|
||||
per_page: 50,
|
||||
per_page: 20,
|
||||
total: 0,
|
||||
pages: 0
|
||||
},
|
||||
@@ -127,24 +130,171 @@ function adminMarketplaceProducts() {
|
||||
}
|
||||
window._adminMarketplaceProductsInitialized = true;
|
||||
|
||||
// Load data in parallel
|
||||
await Promise.all([
|
||||
this.loadStats(),
|
||||
this.loadMarketplaces(),
|
||||
this.loadSourceVendors(),
|
||||
this.loadTargetVendors(),
|
||||
this.loadProducts()
|
||||
]);
|
||||
// Load platform settings for rows per page
|
||||
if (window.PlatformSettings) {
|
||||
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
||||
}
|
||||
|
||||
// Initialize Tom Select for vendor filter
|
||||
this.initVendorSelect();
|
||||
|
||||
// Check localStorage for saved vendor
|
||||
const savedVendorId = localStorage.getItem('marketplace_products_selected_vendor_id');
|
||||
if (savedVendorId) {
|
||||
adminMarketplaceProductsLog.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 other data but not products (restoreSavedVendor will do that)
|
||||
await Promise.all([
|
||||
this.loadStats(),
|
||||
this.loadMarketplaces(),
|
||||
this.loadTargetVendors()
|
||||
]);
|
||||
} else {
|
||||
// No saved vendor - load all data including unfiltered products
|
||||
await Promise.all([
|
||||
this.loadStats(),
|
||||
this.loadMarketplaces(),
|
||||
this.loadTargetVendors(),
|
||||
this.loadProducts()
|
||||
]);
|
||||
}
|
||||
|
||||
adminMarketplaceProductsLog.info('Marketplace Products initialization complete');
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore saved vendor from localStorage
|
||||
*/
|
||||
async restoreSavedVendor(vendorId) {
|
||||
try {
|
||||
const vendor = await apiClient.get(`/admin/vendors/${vendorId}`);
|
||||
if (this.vendorSelectInstance && vendor) {
|
||||
// Add the vendor as an option and select it
|
||||
this.vendorSelectInstance.addOption({
|
||||
id: vendor.id,
|
||||
name: vendor.name,
|
||||
vendor_code: vendor.vendor_code
|
||||
});
|
||||
this.vendorSelectInstance.setValue(vendor.id, true);
|
||||
|
||||
// Set the filter state
|
||||
this.selectedVendor = vendor;
|
||||
this.filters.vendor_name = vendor.name;
|
||||
|
||||
adminMarketplaceProductsLog.info('Restored vendor:', vendor.name);
|
||||
|
||||
// Load products with the vendor filter applied
|
||||
await this.loadProducts();
|
||||
}
|
||||
} catch (error) {
|
||||
adminMarketplaceProductsLog.warn('Failed to restore saved vendor, clearing localStorage:', error);
|
||||
localStorage.removeItem('marketplace_products_selected_vendor_id');
|
||||
// Load unfiltered products as fallback
|
||||
await this.loadProducts();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize Tom Select for vendor autocomplete
|
||||
*/
|
||||
initVendorSelect() {
|
||||
const selectEl = this.$refs.vendorSelect;
|
||||
if (!selectEl) {
|
||||
adminMarketplaceProductsLog.warn('Vendor select element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for Tom Select to be available
|
||||
if (typeof TomSelect === 'undefined') {
|
||||
adminMarketplaceProductsLog.warn('TomSelect not loaded, retrying in 100ms');
|
||||
setTimeout(() => this.initVendorSelect(), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
this.vendorSelectInstance = new TomSelect(selectEl, {
|
||||
valueField: 'id',
|
||||
labelField: 'name',
|
||||
searchField: ['name', 'vendor_code'],
|
||||
placeholder: 'Filter by vendor...',
|
||||
allowEmptyOption: true,
|
||||
load: async (query, callback) => {
|
||||
try {
|
||||
const response = await apiClient.get('/admin/vendors', {
|
||||
search: query,
|
||||
limit: 50
|
||||
});
|
||||
callback(response.vendors || []);
|
||||
} catch (error) {
|
||||
adminMarketplaceProductsLog.error('Failed to search vendors:', error);
|
||||
callback([]);
|
||||
}
|
||||
},
|
||||
render: {
|
||||
option: (data, escape) => {
|
||||
return `<div class="flex items-center justify-between py-1">
|
||||
<span>${escape(data.name)}</span>
|
||||
<span class="text-xs text-gray-400 font-mono">${escape(data.vendor_code || '')}</span>
|
||||
</div>`;
|
||||
},
|
||||
item: (data, escape) => {
|
||||
return `<div>${escape(data.name)}</div>`;
|
||||
}
|
||||
},
|
||||
onChange: (value) => {
|
||||
if (value) {
|
||||
const vendor = this.vendorSelectInstance.options[value];
|
||||
this.selectedVendor = vendor;
|
||||
this.filters.vendor_name = vendor.name;
|
||||
// Save to localStorage
|
||||
localStorage.setItem('marketplace_products_selected_vendor_id', value.toString());
|
||||
} else {
|
||||
this.selectedVendor = null;
|
||||
this.filters.vendor_name = '';
|
||||
// Clear from localStorage
|
||||
localStorage.removeItem('marketplace_products_selected_vendor_id');
|
||||
}
|
||||
this.pagination.page = 1;
|
||||
this.loadProducts();
|
||||
this.loadStats();
|
||||
}
|
||||
});
|
||||
|
||||
adminMarketplaceProductsLog.info('Vendor select initialized');
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear vendor filter
|
||||
*/
|
||||
clearVendorFilter() {
|
||||
if (this.vendorSelectInstance) {
|
||||
this.vendorSelectInstance.clear();
|
||||
}
|
||||
this.selectedVendor = null;
|
||||
this.filters.vendor_name = '';
|
||||
// Clear from localStorage
|
||||
localStorage.removeItem('marketplace_products_selected_vendor_id');
|
||||
this.pagination.page = 1;
|
||||
this.loadProducts();
|
||||
this.loadStats();
|
||||
},
|
||||
|
||||
/**
|
||||
* Load product statistics
|
||||
*/
|
||||
async loadStats() {
|
||||
try {
|
||||
const response = await apiClient.get('/admin/products/stats');
|
||||
const params = new URLSearchParams();
|
||||
if (this.filters.marketplace) {
|
||||
params.append('marketplace', this.filters.marketplace);
|
||||
}
|
||||
if (this.filters.vendor_name) {
|
||||
params.append('vendor_name', this.filters.vendor_name);
|
||||
}
|
||||
const url = params.toString() ? `/admin/products/stats?${params}` : '/admin/products/stats';
|
||||
const response = await apiClient.get(url);
|
||||
this.stats = response;
|
||||
adminMarketplaceProductsLog.info('Loaded stats:', this.stats);
|
||||
} catch (error) {
|
||||
@@ -165,19 +315,6 @@ function adminMarketplaceProducts() {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Load available source vendors for filter (from marketplace products)
|
||||
*/
|
||||
async loadSourceVendors() {
|
||||
try {
|
||||
const response = await apiClient.get('/admin/products/vendors');
|
||||
this.sourceVendors = response.vendors || [];
|
||||
adminMarketplaceProductsLog.info('Loaded source vendors:', this.sourceVendors);
|
||||
} catch (error) {
|
||||
adminMarketplaceProductsLog.error('Failed to load source vendors:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Load target vendors for copy functionality (actual vendor accounts)
|
||||
*/
|
||||
|
||||
@@ -51,7 +51,7 @@ function adminMarketplace() {
|
||||
jobs: [],
|
||||
pagination: {
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
per_page: 20,
|
||||
total: 0,
|
||||
pages: 0
|
||||
},
|
||||
@@ -118,6 +118,11 @@ function adminMarketplace() {
|
||||
}
|
||||
window._adminMarketplaceInitialized = true;
|
||||
|
||||
// Load platform settings for rows per page
|
||||
if (window.PlatformSettings) {
|
||||
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
||||
}
|
||||
|
||||
// Ensure form defaults are set (in case spread didn't work)
|
||||
if (!this.importForm.marketplace) {
|
||||
this.importForm.marketplace = 'Letzshop';
|
||||
|
||||
@@ -58,7 +58,7 @@ function adminOrders() {
|
||||
// Pagination
|
||||
pagination: {
|
||||
page: 1,
|
||||
per_page: 50,
|
||||
per_page: 20,
|
||||
total: 0,
|
||||
pages: 0
|
||||
},
|
||||
@@ -143,6 +143,11 @@ function adminOrders() {
|
||||
}
|
||||
window._adminOrdersInitialized = true;
|
||||
|
||||
// Load platform settings for rows per page
|
||||
if (window.PlatformSettings) {
|
||||
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
||||
}
|
||||
|
||||
// Initialize Tom Select for vendor filter
|
||||
this.initVendorSelect();
|
||||
|
||||
|
||||
@@ -17,7 +17,10 @@ function adminSettings() {
|
||||
saving: false,
|
||||
error: null,
|
||||
successMessage: null,
|
||||
activeTab: 'logging',
|
||||
activeTab: 'display',
|
||||
displaySettings: {
|
||||
rows_per_page: 20
|
||||
},
|
||||
logSettings: {
|
||||
log_level: 'INFO',
|
||||
log_file_max_size_mb: 10,
|
||||
@@ -41,6 +44,7 @@ function adminSettings() {
|
||||
try {
|
||||
settingsLog.info('=== SETTINGS PAGE INITIALIZING ===');
|
||||
await Promise.all([
|
||||
this.loadDisplaySettings(),
|
||||
this.loadLogSettings(),
|
||||
this.loadShippingSettings()
|
||||
]);
|
||||
@@ -54,11 +58,52 @@ function adminSettings() {
|
||||
this.error = null;
|
||||
this.successMessage = null;
|
||||
await Promise.all([
|
||||
this.loadDisplaySettings(),
|
||||
this.loadLogSettings(),
|
||||
this.loadShippingSettings()
|
||||
]);
|
||||
},
|
||||
|
||||
async loadDisplaySettings() {
|
||||
try {
|
||||
const data = await apiClient.get('/admin/settings/display/rows-per-page');
|
||||
this.displaySettings.rows_per_page = data.rows_per_page || 20;
|
||||
settingsLog.info('Display settings loaded:', this.displaySettings);
|
||||
} catch (error) {
|
||||
settingsLog.error('Failed to load display settings:', error);
|
||||
// Use default value on error
|
||||
this.displaySettings.rows_per_page = 20;
|
||||
}
|
||||
},
|
||||
|
||||
async saveDisplaySettings() {
|
||||
this.saving = true;
|
||||
this.error = null;
|
||||
this.successMessage = null;
|
||||
|
||||
try {
|
||||
const data = await apiClient.put(`/admin/settings/display/rows-per-page?rows=${this.displaySettings.rows_per_page}`);
|
||||
this.successMessage = data.message || 'Display settings saved successfully';
|
||||
|
||||
// Clear the cached platform settings so pages pick up the new value
|
||||
if (window.PlatformSettings) {
|
||||
window.PlatformSettings.clearCache();
|
||||
}
|
||||
|
||||
// Auto-hide success message after 5 seconds
|
||||
setTimeout(() => {
|
||||
this.successMessage = null;
|
||||
}, 5000);
|
||||
|
||||
settingsLog.info('Display settings saved successfully');
|
||||
} catch (error) {
|
||||
settingsLog.error('Failed to save display settings:', error);
|
||||
this.error = error.response?.data?.detail || 'Failed to save display settings';
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
|
||||
async loadLogSettings() {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
@@ -28,7 +28,7 @@ function adminUsers() {
|
||||
},
|
||||
pagination: {
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
per_page: 20,
|
||||
total: 0,
|
||||
pages: 0
|
||||
},
|
||||
@@ -36,17 +36,22 @@ function adminUsers() {
|
||||
// Initialization
|
||||
async init() {
|
||||
usersLog.info('=== USERS PAGE INITIALIZING ===');
|
||||
|
||||
|
||||
// Prevent multiple initializations
|
||||
if (window._usersInitialized) {
|
||||
usersLog.warn('Users page already initialized, skipping...');
|
||||
return;
|
||||
}
|
||||
window._usersInitialized = true;
|
||||
|
||||
|
||||
// Load platform settings for rows per page
|
||||
if (window.PlatformSettings) {
|
||||
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
||||
}
|
||||
|
||||
await this.loadUsers();
|
||||
await this.loadStats();
|
||||
|
||||
|
||||
usersLog.info('=== USERS PAGE INITIALIZATION COMPLETE ===');
|
||||
},
|
||||
|
||||
|
||||
@@ -43,13 +43,16 @@ function adminVendorProducts() {
|
||||
is_featured: ''
|
||||
},
|
||||
|
||||
// Available vendors for filter dropdown
|
||||
vendors: [],
|
||||
// Selected vendor (for prominent display and filtering)
|
||||
selectedVendor: null,
|
||||
|
||||
// Tom Select instance
|
||||
vendorSelectInstance: null,
|
||||
|
||||
// Pagination
|
||||
pagination: {
|
||||
page: 1,
|
||||
per_page: 50,
|
||||
per_page: 20,
|
||||
total: 0,
|
||||
pages: 0
|
||||
},
|
||||
@@ -121,22 +124,162 @@ function adminVendorProducts() {
|
||||
}
|
||||
window._adminVendorProductsInitialized = true;
|
||||
|
||||
// Load data in parallel
|
||||
await Promise.all([
|
||||
this.loadStats(),
|
||||
this.loadVendors(),
|
||||
this.loadProducts()
|
||||
]);
|
||||
// Load platform settings for rows per page
|
||||
if (window.PlatformSettings) {
|
||||
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
||||
}
|
||||
|
||||
// Initialize Tom Select for vendor filter
|
||||
this.initVendorSelect();
|
||||
|
||||
// Check localStorage for saved vendor
|
||||
const savedVendorId = localStorage.getItem('vendor_products_selected_vendor_id');
|
||||
if (savedVendorId) {
|
||||
adminVendorProductsLog.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 but not products (restoreSavedVendor will do that)
|
||||
await this.loadStats();
|
||||
} else {
|
||||
// No saved vendor - load all data including unfiltered products
|
||||
await Promise.all([
|
||||
this.loadStats(),
|
||||
this.loadProducts()
|
||||
]);
|
||||
}
|
||||
|
||||
adminVendorProductsLog.info('Vendor Products initialization complete');
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore saved vendor from localStorage
|
||||
*/
|
||||
async restoreSavedVendor(vendorId) {
|
||||
try {
|
||||
const vendor = await apiClient.get(`/admin/vendors/${vendorId}`);
|
||||
if (this.vendorSelectInstance && vendor) {
|
||||
// Add the vendor as an option and select it
|
||||
this.vendorSelectInstance.addOption({
|
||||
id: vendor.id,
|
||||
name: vendor.name,
|
||||
vendor_code: vendor.vendor_code
|
||||
});
|
||||
this.vendorSelectInstance.setValue(vendor.id, true);
|
||||
|
||||
// Set the filter state
|
||||
this.selectedVendor = vendor;
|
||||
this.filters.vendor_id = vendor.id;
|
||||
|
||||
adminVendorProductsLog.info('Restored vendor:', vendor.name);
|
||||
|
||||
// Load products with the vendor filter applied
|
||||
await this.loadProducts();
|
||||
}
|
||||
} catch (error) {
|
||||
adminVendorProductsLog.warn('Failed to restore saved vendor, clearing localStorage:', error);
|
||||
localStorage.removeItem('vendor_products_selected_vendor_id');
|
||||
// Load unfiltered products as fallback
|
||||
await this.loadProducts();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize Tom Select for vendor autocomplete
|
||||
*/
|
||||
initVendorSelect() {
|
||||
const selectEl = this.$refs.vendorSelect;
|
||||
if (!selectEl) {
|
||||
adminVendorProductsLog.warn('Vendor select element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for Tom Select to be available
|
||||
if (typeof TomSelect === 'undefined') {
|
||||
adminVendorProductsLog.warn('TomSelect not loaded, retrying in 100ms');
|
||||
setTimeout(() => this.initVendorSelect(), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
this.vendorSelectInstance = new TomSelect(selectEl, {
|
||||
valueField: 'id',
|
||||
labelField: 'name',
|
||||
searchField: ['name', 'vendor_code'],
|
||||
placeholder: 'Filter by vendor...',
|
||||
allowEmptyOption: true,
|
||||
load: async (query, callback) => {
|
||||
try {
|
||||
const response = await apiClient.get('/admin/vendors', {
|
||||
search: query,
|
||||
limit: 50
|
||||
});
|
||||
callback(response.vendors || []);
|
||||
} catch (error) {
|
||||
adminVendorProductsLog.error('Failed to search vendors:', error);
|
||||
callback([]);
|
||||
}
|
||||
},
|
||||
render: {
|
||||
option: (data, escape) => {
|
||||
return `<div class="flex items-center justify-between py-1">
|
||||
<span>${escape(data.name)}</span>
|
||||
<span class="text-xs text-gray-400 font-mono">${escape(data.vendor_code || '')}</span>
|
||||
</div>`;
|
||||
},
|
||||
item: (data, escape) => {
|
||||
return `<div>${escape(data.name)}</div>`;
|
||||
}
|
||||
},
|
||||
onChange: (value) => {
|
||||
if (value) {
|
||||
const vendor = this.vendorSelectInstance.options[value];
|
||||
this.selectedVendor = vendor;
|
||||
this.filters.vendor_id = value;
|
||||
// Save to localStorage
|
||||
localStorage.setItem('vendor_products_selected_vendor_id', value.toString());
|
||||
} else {
|
||||
this.selectedVendor = null;
|
||||
this.filters.vendor_id = '';
|
||||
// Clear from localStorage
|
||||
localStorage.removeItem('vendor_products_selected_vendor_id');
|
||||
}
|
||||
this.pagination.page = 1;
|
||||
this.loadProducts();
|
||||
this.loadStats();
|
||||
}
|
||||
});
|
||||
|
||||
adminVendorProductsLog.info('Vendor select initialized');
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear vendor filter
|
||||
*/
|
||||
clearVendorFilter() {
|
||||
if (this.vendorSelectInstance) {
|
||||
this.vendorSelectInstance.clear();
|
||||
}
|
||||
this.selectedVendor = null;
|
||||
this.filters.vendor_id = '';
|
||||
// Clear from localStorage
|
||||
localStorage.removeItem('vendor_products_selected_vendor_id');
|
||||
this.pagination.page = 1;
|
||||
this.loadProducts();
|
||||
this.loadStats();
|
||||
},
|
||||
|
||||
/**
|
||||
* Load product statistics
|
||||
*/
|
||||
async loadStats() {
|
||||
try {
|
||||
const response = await apiClient.get('/admin/vendor-products/stats');
|
||||
const params = new URLSearchParams();
|
||||
if (this.filters.vendor_id) {
|
||||
params.append('vendor_id', this.filters.vendor_id);
|
||||
}
|
||||
const url = params.toString() ? `/admin/vendor-products/stats?${params}` : '/admin/vendor-products/stats';
|
||||
const response = await apiClient.get(url);
|
||||
this.stats = response;
|
||||
adminVendorProductsLog.info('Loaded stats:', this.stats);
|
||||
} catch (error) {
|
||||
@@ -144,19 +287,6 @@ function adminVendorProducts() {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Load available vendors for filter
|
||||
*/
|
||||
async loadVendors() {
|
||||
try {
|
||||
const response = await apiClient.get('/admin/vendor-products/vendors');
|
||||
this.vendors = response.vendors || [];
|
||||
adminVendorProductsLog.info('Loaded vendors:', this.vendors.length);
|
||||
} catch (error) {
|
||||
adminVendorProductsLog.error('Failed to load vendors:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Load products with filtering and pagination
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,13 @@ function adminVendorThemes() {
|
||||
vendors: [],
|
||||
selectedVendorCode: '',
|
||||
|
||||
// Selected vendor for filter (Tom Select)
|
||||
selectedVendor: null,
|
||||
vendorSelector: null,
|
||||
|
||||
// Search/filter
|
||||
searchQuery: '',
|
||||
|
||||
async init() {
|
||||
// Guard against multiple initialization
|
||||
if (window._adminVendorThemesInitialized) {
|
||||
@@ -31,13 +38,86 @@ function adminVendorThemes() {
|
||||
}
|
||||
window._adminVendorThemesInitialized = true;
|
||||
|
||||
// Call parent init first
|
||||
const parentInit = data().init;
|
||||
if (parentInit) {
|
||||
await parentInit.call(this);
|
||||
vendorThemesLog.info('Vendor Themes init() called');
|
||||
|
||||
// Initialize vendor selector (Tom Select)
|
||||
this.$nextTick(() => {
|
||||
this.initVendorSelector();
|
||||
});
|
||||
|
||||
// Check localStorage for saved vendor
|
||||
const savedVendorId = localStorage.getItem('vendor_themes_selected_vendor_id');
|
||||
if (savedVendorId) {
|
||||
vendorThemesLog.info('Restoring saved vendor:', savedVendorId);
|
||||
await this.loadVendors();
|
||||
// Restore vendor after vendors are loaded
|
||||
setTimeout(async () => {
|
||||
await this.restoreSavedVendor(parseInt(savedVendorId));
|
||||
}, 200);
|
||||
} else {
|
||||
await this.loadVendors();
|
||||
}
|
||||
|
||||
await this.loadVendors();
|
||||
vendorThemesLog.info('Vendor Themes 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;
|
||||
|
||||
vendorThemesLog.info('Restored vendor:', vendor.name);
|
||||
}
|
||||
} catch (error) {
|
||||
vendorThemesLog.warn('Failed to restore saved vendor, clearing localStorage:', error);
|
||||
localStorage.removeItem('vendor_themes_selected_vendor_id');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize vendor selector with Tom Select
|
||||
*/
|
||||
initVendorSelector() {
|
||||
if (!this.$refs.vendorSelect) {
|
||||
vendorThemesLog.warn('Vendor select element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
this.vendorSelector = initVendorSelector(this.$refs.vendorSelect, {
|
||||
placeholder: 'Search vendor...',
|
||||
onSelect: (vendor) => {
|
||||
vendorThemesLog.info('Vendor selected:', vendor);
|
||||
this.selectedVendor = vendor;
|
||||
// Save to localStorage
|
||||
localStorage.setItem('vendor_themes_selected_vendor_id', vendor.id.toString());
|
||||
},
|
||||
onClear: () => {
|
||||
vendorThemesLog.info('Vendor filter cleared');
|
||||
this.selectedVendor = null;
|
||||
// Clear from localStorage
|
||||
localStorage.removeItem('vendor_themes_selected_vendor_id');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear vendor filter
|
||||
*/
|
||||
clearVendorFilter() {
|
||||
if (this.vendorSelector) {
|
||||
this.vendorSelector.clear();
|
||||
}
|
||||
this.selectedVendor = null;
|
||||
// Clear from localStorage
|
||||
localStorage.removeItem('vendor_themes_selected_vendor_id');
|
||||
},
|
||||
|
||||
async loadVendors() {
|
||||
@@ -56,6 +136,28 @@ function adminVendorThemes() {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Computed: Filtered vendors based on search and selected vendor
|
||||
*/
|
||||
get filteredVendors() {
|
||||
let filtered = this.vendors;
|
||||
|
||||
// If a vendor is selected via Tom Select, show only that vendor
|
||||
if (this.selectedVendor) {
|
||||
filtered = this.vendors.filter(v => v.id === this.selectedVendor.id);
|
||||
}
|
||||
// Otherwise filter by search query
|
||||
else if (this.searchQuery) {
|
||||
const query = this.searchQuery.toLowerCase();
|
||||
filtered = this.vendors.filter(v =>
|
||||
v.name.toLowerCase().includes(query) ||
|
||||
(v.vendor_code && v.vendor_code.toLowerCase().includes(query))
|
||||
);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
},
|
||||
|
||||
navigateToTheme() {
|
||||
if (!this.selectedVendorCode) {
|
||||
return;
|
||||
|
||||
@@ -35,7 +35,7 @@ function adminVendors() {
|
||||
// Pagination state (server-side)
|
||||
pagination: {
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
per_page: 20,
|
||||
total: 0,
|
||||
pages: 0
|
||||
},
|
||||
@@ -51,6 +51,11 @@ function adminVendors() {
|
||||
}
|
||||
window._vendorsInitialized = true;
|
||||
|
||||
// Load platform settings for rows per page
|
||||
if (window.PlatformSettings) {
|
||||
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
||||
}
|
||||
|
||||
vendorsLog.group('Loading vendors data');
|
||||
await this.loadVendors();
|
||||
await this.loadStats();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 || (() => {})
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user