- 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>
171 lines
5.2 KiB
JavaScript
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;
|
|
}
|
|
}
|
|
};
|
|
}
|