Service layer: - Remove db.commit() calls from credentials_service.py (SVC-006) - Move transaction control to API endpoint level - Rename client.py -> client_service.py (NAM-002) - Rename credentials.py -> credentials_service.py (NAM-002) JavaScript: - Use centralized logger in admin letzshop.js (JS-001) - Replace console.log/error with LogConfig logger Frontend templates: - Use page_header_flex macro for page header (FE-007) - Use error_state macro for error display (FE-003) - Use table_wrapper macro for vendors table (FE-005) - Use modal macro for configuration and orders modals (FE-004) All 31 Letzshop tests pass. Architecture validation: 0 errors, 0 warnings. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
282 lines
9.1 KiB
JavaScript
282 lines
9.1 KiB
JavaScript
// static/admin/js/letzshop.js
|
|
/**
|
|
* Admin Letzshop management page logic
|
|
*/
|
|
|
|
// Use centralized logger (with fallback)
|
|
const letzshopLog = window.LogConfig?.createLogger?.('letzshop') ||
|
|
window.LogConfig?.loggers?.letzshop ||
|
|
{ info: () => {}, warn: () => {}, error: () => {}, debug: () => {} };
|
|
|
|
letzshopLog.info('Loading...');
|
|
|
|
function adminLetzshop() {
|
|
letzshopLog.info('adminLetzshop() called');
|
|
|
|
return {
|
|
// Inherit base layout state
|
|
...data(),
|
|
|
|
// Set page identifier
|
|
currentPage: 'letzshop',
|
|
|
|
// Loading states
|
|
loading: false,
|
|
savingConfig: false,
|
|
loadingOrders: false,
|
|
|
|
// Messages
|
|
error: '',
|
|
successMessage: '',
|
|
|
|
// Vendors data
|
|
vendors: [],
|
|
totalVendors: 0,
|
|
page: 1,
|
|
limit: 50,
|
|
|
|
// Filters
|
|
filters: {
|
|
configuredOnly: false
|
|
},
|
|
|
|
// Stats
|
|
stats: {
|
|
total: 0,
|
|
configured: 0,
|
|
autoSync: 0,
|
|
pendingOrders: 0
|
|
},
|
|
|
|
// Configuration modal
|
|
showConfigModal: false,
|
|
selectedVendor: null,
|
|
vendorCredentials: null,
|
|
configForm: {
|
|
api_key: '',
|
|
auto_sync_enabled: false,
|
|
sync_interval_minutes: 15
|
|
},
|
|
showApiKey: false,
|
|
|
|
// Orders modal
|
|
showOrdersModal: false,
|
|
vendorOrders: [],
|
|
|
|
async init() {
|
|
// Guard against multiple initialization
|
|
if (window._adminLetzshopInitialized) {
|
|
return;
|
|
}
|
|
window._adminLetzshopInitialized = true;
|
|
|
|
letzshopLog.info('Initializing...');
|
|
await this.loadVendors();
|
|
},
|
|
|
|
/**
|
|
* Load vendors with Letzshop status
|
|
*/
|
|
async loadVendors() {
|
|
this.loading = true;
|
|
this.error = '';
|
|
|
|
try {
|
|
const params = new URLSearchParams({
|
|
skip: ((this.page - 1) * this.limit).toString(),
|
|
limit: this.limit.toString(),
|
|
configured_only: this.filters.configuredOnly.toString()
|
|
});
|
|
|
|
const response = await apiClient.get(`/admin/letzshop/vendors?${params}`);
|
|
this.vendors = response.vendors || [];
|
|
this.totalVendors = response.total || 0;
|
|
|
|
// Calculate stats
|
|
this.stats.total = this.totalVendors;
|
|
this.stats.configured = this.vendors.filter(v => v.is_configured).length;
|
|
this.stats.autoSync = this.vendors.filter(v => v.auto_sync_enabled).length;
|
|
this.stats.pendingOrders = this.vendors.reduce((sum, v) => sum + (v.pending_orders || 0), 0);
|
|
|
|
letzshopLog.info('Loaded vendors:', this.vendors.length);
|
|
} catch (error) {
|
|
letzshopLog.error('Failed to load vendors:', error);
|
|
this.error = error.message || 'Failed to load vendors';
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Refresh all data
|
|
*/
|
|
async refreshData() {
|
|
await this.loadVendors();
|
|
this.successMessage = 'Data refreshed';
|
|
setTimeout(() => this.successMessage = '', 3000);
|
|
},
|
|
|
|
/**
|
|
* Open configuration modal for a vendor
|
|
*/
|
|
async openConfigModal(vendor) {
|
|
this.selectedVendor = vendor;
|
|
this.vendorCredentials = null;
|
|
this.configForm = {
|
|
api_key: '',
|
|
auto_sync_enabled: vendor.auto_sync_enabled || false,
|
|
sync_interval_minutes: 15
|
|
};
|
|
this.showApiKey = false;
|
|
this.showConfigModal = true;
|
|
|
|
// Load existing credentials if configured
|
|
if (vendor.is_configured) {
|
|
try {
|
|
const response = await apiClient.get(`/admin/letzshop/vendors/${vendor.vendor_id}/credentials`);
|
|
this.vendorCredentials = response;
|
|
this.configForm.auto_sync_enabled = response.auto_sync_enabled;
|
|
this.configForm.sync_interval_minutes = response.sync_interval_minutes || 15;
|
|
} catch (error) {
|
|
if (error.status !== 404) {
|
|
letzshopLog.error('Failed to load credentials:', error);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Save vendor configuration
|
|
*/
|
|
async saveVendorConfig() {
|
|
if (!this.configForm.api_key && !this.vendorCredentials) {
|
|
this.error = 'Please enter an API key';
|
|
return;
|
|
}
|
|
|
|
this.savingConfig = true;
|
|
this.error = '';
|
|
|
|
try {
|
|
const payload = {
|
|
auto_sync_enabled: this.configForm.auto_sync_enabled,
|
|
sync_interval_minutes: parseInt(this.configForm.sync_interval_minutes)
|
|
};
|
|
|
|
if (this.configForm.api_key) {
|
|
payload.api_key = this.configForm.api_key;
|
|
}
|
|
|
|
await apiClient.post(
|
|
`/admin/letzshop/vendors/${this.selectedVendor.vendor_id}/credentials`,
|
|
payload
|
|
);
|
|
|
|
this.showConfigModal = false;
|
|
this.successMessage = 'Configuration saved successfully';
|
|
await this.loadVendors();
|
|
} catch (error) {
|
|
letzshopLog.error('Failed to save config:', error);
|
|
this.error = error.message || 'Failed to save configuration';
|
|
} finally {
|
|
this.savingConfig = false;
|
|
setTimeout(() => this.successMessage = '', 5000);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Delete vendor configuration
|
|
*/
|
|
async deleteVendorConfig() {
|
|
if (!confirm('Are you sure you want to remove Letzshop configuration for this vendor?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await apiClient.delete(`/admin/letzshop/vendors/${this.selectedVendor.vendor_id}/credentials`);
|
|
this.showConfigModal = false;
|
|
this.successMessage = 'Configuration removed';
|
|
await this.loadVendors();
|
|
} catch (error) {
|
|
letzshopLog.error('Failed to delete config:', error);
|
|
this.error = error.message || 'Failed to remove configuration';
|
|
}
|
|
setTimeout(() => this.successMessage = '', 5000);
|
|
},
|
|
|
|
/**
|
|
* Test connection for a vendor
|
|
*/
|
|
async testConnection(vendor) {
|
|
this.error = '';
|
|
|
|
try {
|
|
const response = await apiClient.post(`/admin/letzshop/vendors/${vendor.vendor_id}/test`);
|
|
|
|
if (response.success) {
|
|
this.successMessage = `Connection successful for ${vendor.vendor_name} (${response.response_time_ms?.toFixed(0)}ms)`;
|
|
} else {
|
|
this.error = response.error_details || 'Connection failed';
|
|
}
|
|
} catch (error) {
|
|
letzshopLog.error('Connection test failed:', error);
|
|
this.error = error.message || 'Connection test failed';
|
|
}
|
|
setTimeout(() => this.successMessage = '', 5000);
|
|
},
|
|
|
|
/**
|
|
* Trigger sync for a vendor
|
|
*/
|
|
async triggerSync(vendor) {
|
|
this.error = '';
|
|
|
|
try {
|
|
const response = await apiClient.post(`/admin/letzshop/vendors/${vendor.vendor_id}/sync`);
|
|
|
|
if (response.success) {
|
|
this.successMessage = response.message || 'Sync completed';
|
|
await this.loadVendors();
|
|
} else {
|
|
this.error = response.message || 'Sync failed';
|
|
}
|
|
} catch (error) {
|
|
letzshopLog.error('Sync failed:', error);
|
|
this.error = error.message || 'Sync failed';
|
|
}
|
|
setTimeout(() => this.successMessage = '', 5000);
|
|
},
|
|
|
|
/**
|
|
* View orders for a vendor
|
|
*/
|
|
async viewOrders(vendor) {
|
|
this.selectedVendor = vendor;
|
|
this.vendorOrders = [];
|
|
this.loadingOrders = true;
|
|
this.showOrdersModal = true;
|
|
|
|
try {
|
|
const response = await apiClient.get(`/admin/letzshop/vendors/${vendor.vendor_id}/orders?limit=100`);
|
|
this.vendorOrders = response.orders || [];
|
|
} catch (error) {
|
|
letzshopLog.error('Failed to load orders:', error);
|
|
this.error = error.message || 'Failed to load orders';
|
|
} finally {
|
|
this.loadingOrders = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Format date for display
|
|
*/
|
|
formatDate(dateStr) {
|
|
if (!dateStr) return 'N/A';
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
}
|
|
};
|
|
}
|
|
|
|
letzshopLog.info('Module loaded');
|