feat(admin): separate platform CRUD from CMS, add entity selector macro
Some checks failed
CI / ruff (push) Successful in 11s
CI / docs (push) Has been cancelled
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled

- Move platforms menu from CMS to Platform Admin section with create/edit
- Add platform create page, API endpoint, and service method
- Remove CMS-specific content from platform list and detail pages
- Create shared entity_selector + entity_selected_badge Jinja macros
- Create entity-selector.js generalizing store-selector.js for any entity
- Add Tom Select merchant filter to stores page with localStorage persistence
- Migrate store-products page to use shared macros (remove 53 lines of duped CSS)
- Fix broken icons: puzzle→puzzle-piece, building-storefront→store, language→translate, server→cube

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 22:40:15 +01:00
parent fa758b7e31
commit 45260b6b82
22 changed files with 943 additions and 267 deletions

View File

@@ -28,11 +28,16 @@ function adminStores() {
showDeleteStoreModal: false,
storeToDelete: null,
// Merchant filter (Tom Select)
selectedMerchant: null,
merchantSelectInstance: null,
// Search and filters
filters: {
search: '',
is_active: '',
is_verified: ''
is_verified: '',
merchant_id: ''
},
// Pagination state (server-side)
@@ -62,9 +67,16 @@ function adminStores() {
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
}
// Initialize merchant selector (Tom Select)
this.$nextTick(() => {
this.initMerchantSelect();
});
storesLog.group('Loading stores data');
await this.loadStores();
await this.loadStats();
await Promise.all([
this.loadStores(),
this.loadStats(),
]);
storesLog.groupEnd();
storesLog.info('=== STORES PAGE INITIALIZATION COMPLETE ===');
@@ -163,6 +175,9 @@ function adminStores() {
if (this.filters.is_verified !== '') {
params.append('is_verified', this.filters.is_verified);
}
if (this.filters.merchant_id !== '') {
params.append('merchant_id', this.filters.merchant_id);
}
const url = `/admin/stores?${params}`;
window.LogConfig.logApiCall('GET', url, null, 'request');
@@ -230,6 +245,64 @@ function adminStores() {
}
},
// Initialize merchant selector (Tom Select autocomplete)
initMerchantSelect() {
if (!window.initEntitySelector) {
storesLog.warn('initEntitySelector not available yet, retrying...');
setTimeout(() => this.initMerchantSelect(), 200);
return;
}
this.merchantSelectInstance = initEntitySelector(this.$refs.merchantSelect, {
apiEndpoint: '/admin/merchants',
responseKey: 'merchants',
searchFields: ['name'],
codeField: null,
placeholder: 'Filter by merchant...',
noResultsText: 'No merchants found',
onSelect: (merchant) => {
storesLog.info('Merchant selected:', merchant);
this.selectedMerchant = merchant;
this.filters.merchant_id = merchant.id;
this.pagination.page = 1;
localStorage.setItem('stores_selected_merchant_id', merchant.id);
localStorage.setItem('stores_selected_merchant_data', JSON.stringify(merchant));
this.loadStores();
},
onClear: () => {
this.clearMerchantFilter();
}
});
// Restore from localStorage
const savedMerchantId = localStorage.getItem('stores_selected_merchant_id');
if (savedMerchantId) {
const savedData = JSON.parse(localStorage.getItem('stores_selected_merchant_data') || 'null');
if (savedData) {
this.selectedMerchant = savedData;
this.filters.merchant_id = parseInt(savedMerchantId);
// Wait for Tom Select to init, then set value
setTimeout(() => {
this.merchantSelectInstance?.setValue(parseInt(savedMerchantId), savedData);
}, 500);
}
}
},
// Clear merchant filter
clearMerchantFilter() {
storesLog.info('Clearing merchant filter');
this.selectedMerchant = null;
this.filters.merchant_id = '';
this.pagination.page = 1;
localStorage.removeItem('stores_selected_merchant_id');
localStorage.removeItem('stores_selected_merchant_data');
if (this.merchantSelectInstance) {
this.merchantSelectInstance.clear();
}
this.loadStores();
},
// Pagination: Go to specific page
goToPage(pageNum) {
if (pageNum === '...' || pageNum < 1 || pageNum > this.totalPages) {