// static/admin/js/components.js // ✅ Use centralized logger - ONE LINE! // Create custom logger for components page const componentsLog = window.LogConfig.createLogger('COMPONENTS'); /** * Components Library Alpine.js Component * UI components reference with live examples */ function adminComponents() { return { // ✅ CRITICAL: Inherit base layout functionality ...data(), // ✅ CRITICAL: Set page identifier currentPage: 'components', // Active section for navigation (matches first section on page) activeSection: 'ecommerce', // Component sections sections: [ { id: 'ecommerce', name: 'E-commerce', icon: 'shopping-cart' }, { id: 'macros', name: 'Macros', icon: 'template' }, { id: 'pagination', name: 'Pagination', icon: 'dots-horizontal' }, { id: 'tabs', name: 'Tabs', icon: 'view-boards' }, { id: 'forms', name: 'Forms', icon: 'clipboard-list' }, { id: 'buttons', name: 'Buttons', icon: 'cursor-click' }, { id: 'dropdowns', name: 'Dropdowns', icon: 'chevron-down' }, { id: 'cards', name: 'Cards', icon: 'collection' }, { id: 'badges', name: 'Badges', icon: 'tag' }, { id: 'tables', name: 'Tables', icon: 'table' }, { id: 'modals', name: 'Modals', icon: 'view-grid-add' }, { id: 'alerts', name: 'Alerts', icon: 'exclamation' }, { id: 'charts', name: 'Charts', icon: 'chart-pie' } ], // Tab demo state demoActiveTab: 'tab1', // Number stepper demo state demoQuantitySm: 3, demoQuantityMd: 5, demoQuantityLg: 500, // E-commerce demo state demoProducts: [ { id: 1, name: 'Premium Wireless Headphones', url: '#', image_url: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=300&h=300&fit=crop', price: 149.99, sale_price: 119.99, rating: 4.5, review_count: 127, stock: 15, is_new: false, in_wishlist: false }, { id: 2, name: 'Smart Watch Pro', url: '#', image_url: 'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=300&h=300&fit=crop', price: 299.99, sale_price: null, rating: 4.8, review_count: 89, stock: 8, is_new: true, in_wishlist: true }, { id: 3, name: 'Portable Bluetooth Speaker', url: '#', image_url: 'https://images.unsplash.com/photo-1608043152269-423dbba4e7e1?w=300&h=300&fit=crop', price: 79.99, sale_price: null, rating: 4.2, review_count: 45, stock: 0, is_new: false, in_wishlist: false } ], demoCart: { items: [ { id: 1, product_id: 1, name: 'Premium Wireless Headphones', url: '#', image_url: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=300&h=300&fit=crop', price: 119.99, quantity: 1, variant_name: 'Black', max_quantity: 15 }, { id: 2, product_id: 2, name: 'Smart Watch Pro', url: '#', image_url: 'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=300&h=300&fit=crop', price: 299.99, quantity: 2, variant_name: 'Silver', max_quantity: 8 } ], item_count: 3, subtotal: 719.97, discount: 0, shipping: 0, tax: 0, total: 719.97, promo_code: null }, showDemoCart: false, demoQuantity: 1, addingToCart: false, addedToCart: false, // E-commerce demo methods demoAddToCart(product) { this.addingToCart = true; setTimeout(() => { this.addingToCart = false; this.addedToCart = true; setTimeout(() => { this.addedToCart = false; }, 2000); if (typeof Utils !== 'undefined' && Utils.showToast) { Utils.showToast('Added to cart!', 'success'); } }, 800); }, demoToggleWishlist(product) { product.in_wishlist = !product.in_wishlist; const action = product.in_wishlist ? 'Added to' : 'Removed from'; if (typeof Utils !== 'undefined' && Utils.showToast) { Utils.showToast(`${action} wishlist`, 'success'); } }, demoRemoveFromCart(itemId) { this.demoCart.items = this.demoCart.items.filter(i => i.id !== itemId); this.demoCart.item_count = this.demoCart.items.reduce((sum, i) => sum + i.quantity, 0); this.demoCart.subtotal = this.demoCart.items.reduce((sum, i) => sum + (i.price * i.quantity), 0); this.demoCart.total = this.demoCart.subtotal; }, // Product Detail Demo (Priority 3) demoProductDetail: { id: 1, name: 'Premium Wireless Headphones', sku: 'WH-1000XM5', price: 349.99, sale_price: 279.99, rating: 4.7, review_count: 1247, stock: 23, is_new: true, short_description: 'Industry-leading noise cancellation with exceptional sound quality. 30-hour battery life.', description: '
Experience the next level of silence with our Premium Wireless Headphones. Industry-leading noise cancellation technology lets you focus on what matters most.
With up to 30 hours of battery life, you can enjoy your favorite music all day long. The soft, pressure-relieving ear pads provide all-day comfort.
', features: [ 'Industry-leading noise cancellation', '30-hour battery life', 'Hi-Res Audio certified', 'Multipoint connection', 'Speak-to-chat technology' ], specifications: [ { name: 'Driver Size', value: '40mm' }, { name: 'Frequency Response', value: '4Hz - 40,000Hz' }, { name: 'Battery Life', value: '30 hours' }, { name: 'Charging Time', value: '3 hours' }, { name: 'Weight', value: '250g' }, { name: 'Connectivity', value: 'Bluetooth 5.2, 3.5mm' } ], images: [ { id: 1, url: 'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=800&h=800&fit=crop', alt: 'Headphones front view' }, { id: 2, url: 'https://images.unsplash.com/photo-1484704849700-f032a568e944?w=800&h=800&fit=crop', alt: 'Headphones side view' }, { id: 3, url: 'https://images.unsplash.com/photo-1583394838336-acd977736f90?w=800&h=800&fit=crop', alt: 'Headphones with case' }, { id: 4, url: 'https://images.unsplash.com/photo-1546435770-a3e426bf472b?w=800&h=800&fit=crop', alt: 'Person wearing headphones' } ], sizes: [ { id: 1, name: 'Standard', value: 'standard', stock: 23 }, { id: 2, name: 'Compact', value: 'compact', stock: 8 } ], colors: [ { id: 1, name: 'Black', value: 'black', color_hex: '#1a1a1a', stock: 15 }, { id: 2, name: 'Silver', value: 'silver', color_hex: '#C0C0C0', stock: 8 }, { id: 3, name: 'Midnight Blue', value: 'blue', color_hex: '#191970', stock: 0 } ], reviews: [ { id: 1, author_name: 'John D.', rating: 5, title: 'Best headphones I\'ve ever owned', content: 'The noise cancellation is incredible. I use these for work calls and they block out everything. Battery life is amazing too.', verified: true, created_at: '2025-01-15', helpful_count: 42 }, { id: 2, author_name: 'Sarah M.', rating: 4, title: 'Great sound, slightly tight fit', content: 'Sound quality is phenomenal. My only complaint is they feel a bit tight after a few hours. Otherwise perfect.', verified: true, created_at: '2025-01-10', helpful_count: 18 } ], rating_distribution: { 5: 68, 4: 22, 3: 6, 2: 3, 1: 1 }, category: { name: 'Headphones', slug: 'headphones', url: '/category/headphones' }, vendor: { name: 'AudioTech', slug: 'audiotech', url: '/vendor/audiotech' } }, selectedImage: 0, selectedSize: null, selectedColor: null, activeProductTab: 'description', // Navigation & Discovery Demo (Priority 4) demoCategories: [ { id: 1, name: 'Electronics', slug: 'electronics', url: '/category/electronics', product_count: 245, children: [ { id: 11, name: 'Audio', slug: 'audio', url: '/category/audio', product_count: 89, children: [ { id: 111, name: 'Headphones', slug: 'headphones', url: '/category/headphones', product_count: 45 }, { id: 112, name: 'Speakers', slug: 'speakers', url: '/category/speakers', product_count: 32 } ]}, { id: 12, name: 'Wearables', slug: 'wearables', url: '/category/wearables', product_count: 67 }, { id: 13, name: 'Accessories', slug: 'accessories', url: '/category/accessories', product_count: 89 } ] }, { id: 2, name: 'Clothing', slug: 'clothing', url: '/category/clothing', product_count: 512, children: [ { id: 21, name: 'Men', slug: 'men', url: '/category/men', product_count: 234 }, { id: 22, name: 'Women', slug: 'women', url: '/category/women', product_count: 278 } ] }, { id: 3, name: 'Home & Garden', slug: 'home-garden', url: '/category/home-garden', product_count: 189 } ], currentCategory: { id: 111, name: 'Headphones', slug: 'headphones' }, demoBreadcrumbs: [ { label: 'Electronics', url: '/category/electronics' }, { label: 'Audio', url: '/category/audio' }, { label: 'Headphones' } ], // Filter Demo State demoFilters: { categories: [ { id: 'headphones', name: 'Headphones', count: 45 }, { id: 'earbuds', name: 'Earbuds', count: 32 }, { id: 'speakers', name: 'Speakers', count: 28 } ], brands: [ { id: 'audiotech', name: 'AudioTech', count: 24 }, { id: 'soundmax', name: 'SoundMax', count: 18 }, { id: 'beatspro', name: 'BeatsPro', count: 15 }, { id: 'sonicwave', name: 'SonicWave', count: 12 } ], priceRange: { min: 0, max: 500 }, attributes: { color: [ { value: 'black', label: 'Black', hex: '#1a1a1a', count: 35 }, { value: 'white', label: 'White', hex: '#ffffff', count: 28 }, { value: 'silver', label: 'Silver', hex: '#C0C0C0', count: 22 }, { value: 'blue', label: 'Blue', hex: '#3B82F6', count: 15 } ], size: [ { value: 'compact', label: 'Compact', count: 18 }, { value: 'standard', label: 'Standard', count: 42 }, { value: 'over-ear', label: 'Over-ear', count: 25 } ] }, ratings: [ { value: 5, count: 12 }, { value: 4, count: 28 }, { value: 3, count: 15 }, { value: 2, count: 5 }, { value: 1, count: 2 } ] }, demoActiveFilters: { categories: [], brands: [], priceMin: undefined, priceMax: undefined, rating: undefined, attributes: {}, inStock: false }, demoSortBy: 'relevance', showMobileSearch: false, showMobileFilters: false, showCategoryDrawer: false, // Search Demo Methods demoFilterProducts() { componentsLog.debug('Filtering products with:', this.demoActiveFilters); if (typeof Utils !== 'undefined' && Utils.showToast) { Utils.showToast('Filters applied!', 'info'); } }, demoSortProducts() { componentsLog.debug('Sorting by:', this.demoSortBy); if (typeof Utils !== 'undefined' && Utils.showToast) { Utils.showToast('Sorted by: ' + this.demoSortBy, 'info'); } }, // Social Proof Demo (Priority 5) demoRating: 4.5, demoReviewCount: 127, demoRatingDistribution: { 5: 68, 4: 35, 3: 15, 2: 6, 3: 3 }, demoUserRating: 0, demoReviews: [ { id: 1, author_name: 'John D.', author_avatar: null, rating: 5, title: 'Best headphones I\'ve ever owned', content: 'The noise cancellation is incredible. I use these for work calls and they block out everything. Battery life is amazing too. Would highly recommend to anyone looking for premium audio quality.', verified: true, created_at: '2025-01-15', helpful_count: 42, images: [] }, { id: 2, author_name: 'Sarah M.', author_avatar: null, rating: 4, title: 'Great sound, slightly tight fit', content: 'Sound quality is phenomenal and the build quality feels premium. My only complaint is they feel a bit tight after a few hours of continuous use. Otherwise perfect for music lovers.', verified: true, created_at: '2025-01-10', helpful_count: 18, images: [] }, { id: 3, author_name: 'Mike R.', author_avatar: null, rating: 5, title: 'Worth every penny', content: 'These headphones are absolutely worth the investment. The sound stage is wide, bass is punchy but not overwhelming, and the noise cancellation is top-notch.', verified: false, created_at: '2025-01-05', helpful_count: 7, images: [] } ], demoReviewSort: 'newest', demoNewReview: { rating: 0, title: '', content: '', images: [] }, submittingReview: false, showReviewForm: false, // Review Demo Methods demoSubmitReview() { this.submittingReview = true; setTimeout(() => { this.submittingReview = false; this.showReviewForm = false; this.demoNewReview = { rating: 0, title: '', content: '', images: [] }; if (typeof Utils !== 'undefined' && Utils.showToast) { Utils.showToast('Review submitted successfully!', 'success'); } }, 1500); }, demoMarkHelpful(reviewId) { const review = this.demoReviews.find(r => r.id === reviewId); if (review) { review.helpful_count++; } if (typeof Utils !== 'undefined' && Utils.showToast) { Utils.showToast('Thanks for your feedback!', 'info'); } }, // Sample form data for examples exampleForm: { textInput: 'Sample text', email: 'user@example.com', textarea: 'Sample description text...', select: 'option1', checkbox: true, radio: 'option1', disabled: 'Read-only value' }, // Sample errors for validation examples exampleErrors: { email: 'Please enter a valid email address', required: 'This field is required' }, // Modal state variables for examples showExampleModal: false, showFormModal: false, showDetailsModal: false, showLogModal: false, exampleLog: null, // Confirm modal demo state showConfirmModalDemo: false, confirmModalVariant: 'danger', showDynamicConfirmDemo: false, dynamicItemName: '', // Code snippets for confirm modal macros codeSnippets: { confirmModal: `{% from 'shared/macros/modals.html' import confirm_modal %} {{ confirm_modal( 'deleteConfirm', 'Delete Item', 'Are you sure you want to delete this item?', 'deleteItem()', 'showDeleteModal', 'Delete', 'Cancel', 'danger' ) }}`, confirmModalDynamic: `{% from 'shared/macros/modals.html' import confirm_modal_dynamic %} {{ confirm_modal_dynamic( 'removePlatformModal', 'Remove Platform', "'Are you sure you want to remove \\\"' + (item?.name || '') + '\\\" ?'", 'confirmRemove()', 'showConfirmModal', 'Remove', 'Cancel', 'warning' ) }}` }, // Example log data for demo showErrorLogDemo() { this.exampleLog = { id: 1, level: 'ERROR', timestamp: new Date().toISOString(), logger_name: 'app.services.import', module: 'import_service', message: 'Failed to import product: Invalid GTIN format', exception_type: 'ValidationError', exception_message: 'GTIN must be 13 digits', stack_trace: 'Traceback (most recent call last):\n File "/app/services/import.py", line 42, in process\n validate_gtin(product.gtin)\n File "/app/validators.py", line 15, in validate_gtin\n raise ValidationError("GTIN must be 13 digits")\nValidationError: GTIN must be 13 digits' }; this.showLogModal = true; }, showWarningLogDemo() { this.exampleLog = { id: 2, level: 'WARNING', timestamp: new Date().toISOString(), logger_name: 'app.utils.cache', module: 'cache', message: 'Cache miss for key: product_list_vendor_5. Fetching from database.', exception_type: null, exception_message: null, stack_trace: null }; this.showLogModal = true; }, // ✅ CRITICAL: Proper initialization with guard async init() { componentsLog.info('=== COMPONENTS PAGE INITIALIZING ==='); // Prevent multiple initializations if (window._componentsInitialized) { componentsLog.warn('Components page already initialized, skipping...'); return; } window._componentsInitialized = true; // Set active section from URL hash if present this.setActiveSectionFromHash(); // Listen for hash changes window.addEventListener('hashchange', () => { this.setActiveSectionFromHash(); }); // Initialize charts after DOM is ready this.$nextTick(() => { this.initializeCharts(); }); componentsLog.info('=== COMPONENTS PAGE INITIALIZATION COMPLETE ==='); }, /** * Set active section from URL hash */ setActiveSectionFromHash() { const hash = window.location.hash.replace('#', ''); if (hash && this.sections.find(s => s.id === hash)) { this.activeSection = hash; componentsLog.debug('Set active section from hash:', hash); } }, /** * Navigate to section */ goToSection(sectionId) { componentsLog.info('Navigating to section:', sectionId); this.activeSection = sectionId; window.location.hash = sectionId; // Smooth scroll to section const element = document.getElementById(sectionId); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }, /** * Check if section is active */ isSectionActive(sectionId) { return this.activeSection === sectionId; }, /** * Copy code to clipboard */ async copyCode(code) { try { await navigator.clipboard.writeText(code); // Use the global Utils.showToast function if (typeof Utils !== 'undefined' && Utils.showToast) { Utils.showToast('Code copied to clipboard!', 'success'); } else { componentsLog.warn('Utils.showToast not available'); } componentsLog.debug('Code copied to clipboard'); } catch (error) { window.LogConfig.logError(error, 'Copy Code'); if (typeof Utils !== 'undefined' && Utils.showToast) { Utils.showToast('Failed to copy code', 'error'); } } }, /** * Show toast example */ showToastExample(type) { const messages = { success: 'Operation completed successfully!', error: 'An error occurred!', warning: 'Please review your input.', info: 'Here is some information.' }; componentsLog.info('Showing toast example:', type); if (typeof Utils !== 'undefined' && Utils.showToast) { Utils.showToast(messages[type] || messages.info, type); } else { componentsLog.error('Utils.showToast not available'); // noqa: JS-009 - Fallback for component showcase when Utils not loaded alert(messages[type] || messages.info); } }, /** * Initialize Chart.js charts */ initializeCharts() { try { // Check if Chart.js is loaded if (typeof Chart === 'undefined') { componentsLog.warn('Chart.js not loaded, skipping chart initialization'); return; } componentsLog.info('Initializing charts...'); componentsLog.time('Chart Initialization'); // Pie Chart const pieCanvas = document.getElementById('examplePieChart'); if (pieCanvas) { const pieConfig = { type: 'doughnut', data: { datasets: [{ data: [33, 33, 33], backgroundColor: ['#0694a2', '#7e3af2', '#1c64f2'], label: 'Dataset 1', }], labels: ['Shoes', 'Shirts', 'Bags'], }, options: { responsive: true, cutoutPercentage: 80, legend: { display: false, }, }, }; new Chart(pieCanvas, pieConfig); componentsLog.debug('Pie chart initialized'); } // Line Chart const lineCanvas = document.getElementById('exampleLineChart'); if (lineCanvas) { const lineConfig = { type: 'line', data: { labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], datasets: [{ label: 'Organic', backgroundColor: '#0694a2', borderColor: '#0694a2', data: [43, 48, 40, 54, 67, 73, 70], fill: false, }, { label: 'Paid', fill: false, backgroundColor: '#7e3af2', borderColor: '#7e3af2', data: [24, 50, 64, 74, 52, 51, 65], }], }, options: { responsive: true, legend: { display: false, }, tooltips: { mode: 'index', intersect: false, }, hover: { mode: 'nearest', intersect: true, }, scales: { x: { display: true, scaleLabel: { display: true, labelString: 'Month', }, }, y: { display: true, scaleLabel: { display: true, labelString: 'Value', }, }, }, }, }; new Chart(lineCanvas, lineConfig); componentsLog.debug('Line chart initialized'); } // Bar Chart const barCanvas = document.getElementById('exampleBarChart'); if (barCanvas) { const barConfig = { type: 'bar', data: { labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], datasets: [{ label: 'Shoes', backgroundColor: '#0694a2', borderColor: '#0694a2', borderWidth: 1, data: [43, 48, 40, 54, 67, 73, 70], }, { label: 'Bags', backgroundColor: '#7e3af2', borderColor: '#7e3af2', borderWidth: 1, data: [24, 50, 64, 74, 52, 51, 65], }], }, options: { responsive: true, legend: { display: false, }, }, }; new Chart(barCanvas, barConfig); componentsLog.debug('Bar chart initialized'); } componentsLog.timeEnd('Chart Initialization'); componentsLog.info('All charts initialized successfully'); } catch (error) { window.LogConfig.logError(error, 'Initialize Charts'); } } }; } componentsLog.info('Components module loaded');