Files
orion/static/admin/js/vendor-product-detail.js
Samir Boulahtit 9c60989f1d feat: add marketplace products admin UI with copy-to-vendor functionality
- Add admin marketplace products page to browse imported products
- Add admin vendor products page to manage vendor catalog
- Add product detail pages for both marketplace and vendor products
- Implement copy-to-vendor API to copy marketplace products to vendor catalogs
- Add vendor product service with CRUD operations
- Update sidebar navigation with new product management links
- Add integration and unit tests for new endpoints and services

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 22:36:04 +01:00

171 lines
5.2 KiB
JavaScript

// static/admin/js/vendor-product-detail.js
/**
* Admin vendor product detail page logic
* View and manage individual vendor catalog products
*/
const adminVendorProductDetailLog = window.LogConfig.loggers.adminVendorProductDetail ||
window.LogConfig.createLogger('adminVendorProductDetail', false);
adminVendorProductDetailLog.info('Loading...');
function adminVendorProductDetail() {
adminVendorProductDetailLog.info('adminVendorProductDetail() called');
// Extract product ID from URL
const pathParts = window.location.pathname.split('/');
const productId = parseInt(pathParts[pathParts.length - 1]);
return {
// Inherit base layout state
...data(),
// Set page identifier
currentPage: 'vendor-products',
// Product ID from URL
productId: productId,
// Loading states
loading: true,
error: '',
// Product data
product: null,
// Modals
showRemoveModal: false,
removing: false,
async init() {
adminVendorProductDetailLog.info('Vendor Product Detail init() called, ID:', this.productId);
// Guard against multiple initialization
if (window._adminVendorProductDetailInitialized) {
adminVendorProductDetailLog.warn('Already initialized, skipping');
return;
}
window._adminVendorProductDetailInitialized = true;
// Load product data
await this.loadProduct();
adminVendorProductDetailLog.info('Vendor Product Detail initialization complete');
},
/**
* Load product details
*/
async loadProduct() {
this.loading = true;
this.error = '';
try {
const response = await apiClient.get(`/admin/vendor-products/${this.productId}`);
this.product = response;
adminVendorProductDetailLog.info('Loaded product:', this.product.id);
} catch (error) {
adminVendorProductDetailLog.error('Failed to load product:', error);
this.error = error.message || 'Failed to load product details';
} finally {
this.loading = false;
}
},
/**
* Open edit modal (placeholder for future implementation)
*/
openEditModal() {
window.dispatchEvent(new CustomEvent('toast', {
detail: { message: 'Edit functionality coming soon', type: 'info' }
}));
},
/**
* Toggle active status
*/
async toggleActive() {
// TODO: Implement PATCH endpoint for status update
window.dispatchEvent(new CustomEvent('toast', {
detail: {
message: 'Status toggle functionality coming soon',
type: 'info'
}
}));
},
/**
* Confirm remove
*/
confirmRemove() {
this.showRemoveModal = true;
},
/**
* Execute remove
*/
async executeRemove() {
this.removing = true;
try {
await apiClient.delete(`/admin/vendor-products/${this.productId}`);
adminVendorProductDetailLog.info('Product removed:', this.productId);
window.dispatchEvent(new CustomEvent('toast', {
detail: {
message: 'Product removed from catalog successfully',
type: 'success'
}
}));
// Redirect to vendor products list
setTimeout(() => {
window.location.href = '/admin/vendor-products';
}, 1000);
} catch (error) {
adminVendorProductDetailLog.error('Failed to remove product:', error);
window.dispatchEvent(new CustomEvent('toast', {
detail: { message: error.message || 'Failed to remove product', type: 'error' }
}));
} finally {
this.removing = false;
this.showRemoveModal = false;
}
},
/**
* Format price for display
*/
formatPrice(price, currency = 'EUR') {
if (price === null || price === undefined) return '-';
const numPrice = typeof price === 'string' ? parseFloat(price) : price;
if (isNaN(numPrice)) return price;
return new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: currency || 'EUR'
}).format(numPrice);
},
/**
* Format date for display
*/
formatDate(dateString) {
if (!dateString) return '-';
try {
const date = new Date(dateString);
return date.toLocaleDateString('en-GB', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
} catch (e) {
return dateString;
}
}
};
}