Files
orion/static/admin/js/letzshop-vendor-directory.js
Samir Boulahtit ccfbbcb804 feat: add Letzshop vendor directory with sync and admin management
- Add LetzshopVendorCache model to store cached vendor data from Letzshop API
- Create LetzshopVendorSyncService for syncing vendor directory
- Add Celery task for background vendor sync
- Create admin page at /admin/letzshop/vendor-directory with:
  - Stats dashboard (total, claimed, unclaimed vendors)
  - Searchable/filterable vendor list
  - "Sync Now" button to trigger sync
  - Ability to create platform vendors from Letzshop cache
- Add API endpoints for vendor directory management
- Add Pydantic schemas for API responses

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 20:35:46 +01:00

205 lines
6.5 KiB
JavaScript

// static/admin/js/letzshop-vendor-directory.js
/**
* Admin Letzshop Vendor Directory page logic
* Browse and import vendors from Letzshop marketplace
*/
const letzshopVendorDirectoryLog = window.LogConfig.loggers.letzshopVendorDirectory ||
window.LogConfig.createLogger('letzshopVendorDirectory', false);
letzshopVendorDirectoryLog.info('Loading...');
function letzshopVendorDirectory() {
letzshopVendorDirectoryLog.info('letzshopVendorDirectory() called');
return {
// Inherit base layout state
...data(),
// Set page identifier for sidebar highlighting
currentPage: 'letzshop-vendor-directory',
// Data
vendors: [],
stats: {},
companies: [],
total: 0,
page: 1,
limit: 20,
hasMore: false,
// State
loading: true,
syncing: false,
creating: false,
error: '',
successMessage: '',
// Filters
filters: {
search: '',
city: '',
category: '',
only_unclaimed: false,
},
// Modals
showDetailModal: false,
showCreateModal: false,
selectedVendor: null,
createVendorData: {
slug: '',
name: '',
company_id: '',
},
createError: '',
// Init
async init() {
// Guard against multiple initialization
if (window._letzshopVendorDirectoryInitialized) return;
window._letzshopVendorDirectoryInitialized = true;
letzshopVendorDirectoryLog.info('init() called');
await Promise.all([
this.loadStats(),
this.loadVendors(),
this.loadCompanies(),
]);
},
// API calls
async loadStats() {
try {
const data = await apiClient.get('/admin/letzshop/vendor-directory/stats');
if (data.success) {
this.stats = data.stats;
}
} catch (e) {
letzshopVendorDirectoryLog.error('Failed to load stats:', e);
}
},
async loadVendors() {
this.loading = true;
this.error = '';
try {
const params = new URLSearchParams({
page: this.page,
limit: this.limit,
});
if (this.filters.search) params.append('search', this.filters.search);
if (this.filters.city) params.append('city', this.filters.city);
if (this.filters.category) params.append('category', this.filters.category);
if (this.filters.only_unclaimed) params.append('only_unclaimed', 'true');
const data = await apiClient.get(`/admin/letzshop/vendor-directory/vendors?${params}`);
if (data.success) {
this.vendors = data.vendors;
this.total = data.total;
this.hasMore = data.has_more;
} else {
this.error = data.detail || 'Failed to load vendors';
}
} catch (e) {
this.error = 'Failed to load vendors';
letzshopVendorDirectoryLog.error('Failed to load vendors:', e);
} finally {
this.loading = false;
}
},
async loadCompanies() {
try {
const data = await apiClient.get('/admin/companies?limit=100');
if (data.companies) {
this.companies = data.companies;
}
} catch (e) {
letzshopVendorDirectoryLog.error('Failed to load companies:', e);
}
},
async triggerSync() {
this.syncing = true;
this.error = '';
this.successMessage = '';
try {
const data = await apiClient.post('/admin/letzshop/vendor-directory/sync');
if (data.success) {
this.successMessage = data.message + (data.mode === 'celery' ? ` (Task ID: ${data.task_id})` : '');
// Reload data after a delay to allow sync to complete
setTimeout(() => {
this.loadStats();
this.loadVendors();
}, 3000);
} else {
this.error = data.detail || 'Failed to trigger sync';
}
} catch (e) {
this.error = 'Failed to trigger sync';
letzshopVendorDirectoryLog.error('Failed to trigger sync:', e);
} finally {
this.syncing = false;
}
},
async createVendor() {
if (!this.createVendorData.company_id || !this.createVendorData.slug) return;
this.creating = true;
this.createError = '';
try {
const data = await apiClient.post(
`/admin/letzshop/vendor-directory/vendors/${this.createVendorData.slug}/create-vendor?company_id=${this.createVendorData.company_id}`
);
if (data.success) {
this.showCreateModal = false;
this.successMessage = data.message;
this.loadVendors();
this.loadStats();
} else {
this.createError = data.detail || 'Failed to create vendor';
}
} catch (e) {
this.createError = 'Failed to create vendor';
letzshopVendorDirectoryLog.error('Failed to create vendor:', e);
} finally {
this.creating = false;
}
},
// Modal handlers
showVendorDetail(vendor) {
this.selectedVendor = vendor;
this.showDetailModal = true;
},
openCreateVendorModal(vendor) {
this.createVendorData = {
slug: vendor.slug,
name: vendor.name,
company_id: '',
};
this.createError = '';
this.showCreateModal = true;
},
// Utilities
formatDate(dateStr) {
if (!dateStr) return '-';
const date = new Date(dateStr);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
},
};
}
letzshopVendorDirectoryLog.info('Loaded');