+
\ No newline at end of file
diff --git a/frontend-structure.txt b/frontend-structure.txt
index c479116a..c05a520a 100644
--- a/frontend-structure.txt
+++ b/frontend-structure.txt
@@ -1,19 +1,45 @@
Frontend Folder Structure
-Generated: 18/10/2025 13:53:32.04
+Generated: 25/10/2025 16:45:08.55
==============================================================================
Folder PATH listing for volume Data2
-Volume serial number is 00000011 A008:CC27
-E:\LETZSHOP-IMPORT\STATIC
+Volume serial number is 0000007B A008:CC27
+E:\FASTAPI-MULTITENANT-ECOMMERCE\STATIC
+---admin
-| dashboard.html
-| login.html
-| marketplace.html
-| monitoring.html
-| users.html
-| vendor-edit.html
-| vendors.html
-|
+| | marketplace.html
+| | monitoring.html
+| | users.html
+| | vendor-edit.html
+| | vendors.html
+| |
+| +---css
+| | tailwind.output.css
+| |
+| +---img
+| | create-account-office-dark.jpeg
+| | create-account-office.jpeg
+| | forgot-password-office-dark.jpeg
+| | forgot-password-office.jpeg
+| | login-office-dark.jpeg
+| | login-office.jpeg
+| |
+| +---js
+| | analytics.js
+| | components.js
+| | dashboard.js
+| | icons-page.js
+| | init-alpine.js
+| | login.js
+| | monitoring.js
+| | testing-hub.js
+| | users.js
+| | vendor-detail.js
+| | vendor-edit.js
+| | vendors.js
+| |
+| \---partials
+| base-layout.html
+|
+---css
| +---admin
| | admin.css
@@ -21,6 +47,8 @@ E:\LETZSHOP-IMPORT\STATIC
| +---shared
| | auth.css
| | base.css
+| | components.css
+| | modals.css
| | responsive-utilities.css
| |
| +---shop
@@ -29,17 +57,11 @@ E:\LETZSHOP-IMPORT\STATIC
| vendor.css
|
+---js
-| +---admin
-| | analytics.js
-| | dashboard.js
-| | login.js
-| | monitoring.js
-| | vendor-edit.js
-| | vendors.js
-| |
| +---shared
-| | api-client.js
+| | alpine-components.js
| | media-upload.js
+| | modal-system.js
+| | modal-templates.js
| | notification.js
| | search.js
| | vendor-context.js
@@ -50,6 +72,7 @@ E:\LETZSHOP-IMPORT\STATIC
| | catalog.js
| | checkout.js
| | search.js
+| | shop-layout-templates.js
| |
| \---vendor
| dashboard.js
@@ -59,6 +82,13 @@ E:\LETZSHOP-IMPORT\STATIC
| orders.js
| payments.js
| products.js
+| vendor-layout-templates.js
+|
++---shared
+| \---js
+| api-client.js
+| icons.js
+| utils.js
|
+---shop
| | cart.html
diff --git a/scripts/show-frontend-structure.ps1 b/scripts/show-frontend-structure.ps1
deleted file mode 100644
index b3ab6f04..00000000
--- a/scripts/show-frontend-structure.ps1
+++ /dev/null
@@ -1,54 +0,0 @@
-# show-frontend-structure.ps1
-# Displays the frontend folder structure
-
-param(
- [string]$Path = "static",
- [string]$OutputFile = "frontend-structure.txt"
-)
-
-function Show-Tree {
- param(
- [string]$Path,
- [string]$Indent = "",
- [bool]$IsLast = $true,
- [System.IO.StreamWriter]$Writer
- )
-
- $item = Get-Item $Path
- $prefix = if ($Indent -eq "") { "" } else { if ($IsLast) { "└── " } else { "├── " } }
-
- $line = $Indent + $prefix + $item.Name
- $Writer.WriteLine($line)
- Write-Host $line
-
- if ($item.PSIsContainer) {
- $items = Get-ChildItem $Path | Sort-Object Name
- $count = $items.Count
-
- for ($i = 0; $i -lt $count; $i++) {
- $newIndent = $Indent + $(if ($Indent -eq "") { "" } else { if ($IsLast) { " " } else { "│ " } })
- $isLast = ($i -eq ($count - 1))
- Show-Tree -Path $items[$i].FullName -Indent $newIndent -IsLast $isLast -Writer $Writer
- }
- }
-}
-
-Write-Host "Generating frontend structure..." -ForegroundColor Green
-Write-Host "Output will be saved to: $OutputFile" -ForegroundColor Yellow
-Write-Host ""
-
-$writer = [System.IO.StreamWriter]::new($OutputFile)
-$writer.WriteLine("Frontend Folder Structure")
-$writer.WriteLine("Generated: $(Get-Date)")
-$writer.WriteLine("=" * 80)
-$writer.WriteLine("")
-
-Show-Tree -Path $Path -Writer $writer
-
-$writer.Close()
-
-Write-Host ""
-Write-Host "✅ Structure saved to $OutputFile" -ForegroundColor Green
-Write-Host ""
-Write-Host "Opening file..." -ForegroundColor Cyan
-Start-Process notepad.exe $OutputFile
\ No newline at end of file
diff --git a/static/admin/js/components.js b/static/admin/js/components.js
new file mode 100644
index 00000000..6d6f4ce0
--- /dev/null
+++ b/static/admin/js/components.js
@@ -0,0 +1,140 @@
+// static/admin/js/components.js
+
+// Setup logging
+const COMPONENTS_LOG_LEVEL = 3;
+
+const componentsLog = {
+ error: (...args) => COMPONENTS_LOG_LEVEL >= 1 && console.error('❌ [COMPONENTS ERROR]', ...args),
+ warn: (...args) => COMPONENTS_LOG_LEVEL >= 2 && console.warn('⚠️ [COMPONENTS WARN]', ...args),
+ info: (...args) => COMPONENTS_LOG_LEVEL >= 3 && console.info('ℹ️ [COMPONENTS INFO]', ...args),
+ debug: (...args) => COMPONENTS_LOG_LEVEL >= 4 && console.log('🔍 [COMPONENTS DEBUG]', ...args)
+};
+
+/**
+ * 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
+ activeSection: 'forms',
+
+ // Component sections
+ sections: [
+ { id: 'forms', name: 'Forms', icon: 'clipboard-list' },
+ { id: 'buttons', name: 'Buttons', icon: 'cursor-click' },
+ { id: 'cards', name: 'Cards', icon: 'collection' },
+ { id: 'badges', name: 'Badges', icon: 'tag' },
+ { id: 'tables', name: 'Tables', icon: 'table' },
+ { id: 'modals', name: 'Modals', icon: 'window' },
+ { id: 'alerts', name: 'Alerts', icon: 'exclamation' }
+ ],
+
+ // 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'
+ },
+
+ // ✅ 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();
+ });
+
+ 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);
+ Utils.showToast('Code copied to clipboard!', 'success');
+ componentsLog.debug('Code copied to clipboard');
+ } catch (error) {
+ componentsLog.error('Failed to copy code:', error);
+ 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.'
+ };
+ Utils.showToast(messages[type] || messages.info, type);
+ }
+ };
+}
+
+componentsLog.info('Components module loaded');
\ No newline at end of file
diff --git a/static/admin/js/icons-page.js b/static/admin/js/icons-page.js
new file mode 100644
index 00000000..4e3bb0eb
--- /dev/null
+++ b/static/admin/js/icons-page.js
@@ -0,0 +1,210 @@
+// static/admin/js/icons-page.js
+
+// Setup logging
+const ICONS_PAGE_LOG_LEVEL = 3;
+
+const iconsLog = {
+ error: (...args) => ICONS_PAGE_LOG_LEVEL >= 1 && console.error('❌ [ICONS PAGE ERROR]', ...args),
+ warn: (...args) => ICONS_PAGE_LOG_LEVEL >= 2 && console.warn('⚠️ [ICONS PAGE WARN]', ...args),
+ info: (...args) => ICONS_PAGE_LOG_LEVEL >= 3 && console.info('ℹ️ [ICONS PAGE INFO]', ...args),
+ debug: (...args) => ICONS_PAGE_LOG_LEVEL >= 4 && console.log('🔍 [ICONS PAGE DEBUG]', ...args)
+};
+
+/**
+ * Icons Browser Alpine.js Component
+ * Browse and search all available icons
+ */
+function adminIcons() {
+ return {
+ // ✅ CRITICAL: Inherit base layout functionality
+ ...data(),
+
+ // ✅ CRITICAL: Set page identifier
+ currentPage: 'icons',
+
+ // Search and filter
+ searchQuery: '',
+ activeCategory: 'all',
+
+ // Icon categories
+ categories: [
+ { id: 'all', name: 'All Icons', icon: 'collection' },
+ { id: 'navigation', name: 'Navigation', icon: 'menu' },
+ { id: 'user', name: 'User & Profile', icon: 'user' },
+ { id: 'actions', name: 'Actions', icon: 'lightning-bolt' },
+ { id: 'ecommerce', name: 'E-commerce', icon: 'shopping-bag' },
+ { id: 'inventory', name: 'Inventory', icon: 'cube' },
+ { id: 'communication', name: 'Communication', icon: 'mail' },
+ { id: 'files', name: 'Files', icon: 'document' },
+ { id: 'settings', name: 'Settings', icon: 'cog' },
+ { id: 'status', name: 'Status', icon: 'check-circle' },
+ { id: 'testing', name: 'Testing', icon: 'beaker' }
+ ],
+
+ // All icons organized by category
+ iconsByCategory: {},
+ allIcons: [],
+ filteredIcons: [],
+
+ // Selected icon for detail view
+ selectedIcon: null,
+
+ // ✅ CRITICAL: Proper initialization with guard
+ async init() {
+ iconsLog.info('=== ICONS PAGE INITIALIZING ===');
+
+ // Prevent multiple initializations
+ if (window._iconsPageInitialized) {
+ iconsLog.warn('Icons page already initialized, skipping...');
+ return;
+ }
+ window._iconsPageInitialized = true;
+
+ // Load icons from global Icons object
+ this.loadIcons();
+
+ iconsLog.info('=== ICONS PAGE INITIALIZATION COMPLETE ===');
+ },
+
+ /**
+ * Load icons from global Icons object
+ */
+ loadIcons() {
+ if (!window.Icons) {
+ iconsLog.error('Icons object not found! Make sure icons.js is loaded.');
+ return;
+ }
+
+ // Get all icon names
+ this.allIcons = Object.keys(window.Icons).map(name => ({
+ name: name,
+ category: this.categorizeIcon(name)
+ }));
+
+ // Organize by category
+ this.iconsByCategory = this.allIcons.reduce((acc, icon) => {
+ if (!acc[icon.category]) {
+ acc[icon.category] = [];
+ }
+ acc[icon.category].push(icon);
+ return acc;
+ }, {});
+
+ // Initial filter
+ this.filterIcons();
+
+ iconsLog.info(`Loaded ${this.allIcons.length} icons across ${Object.keys(this.iconsByCategory).length} categories`);
+ },
+
+ /**
+ * Categorize icon based on name
+ */
+ categorizeIcon(iconName) {
+ const categoryMap = {
+ navigation: ['home', 'menu', 'search', 'arrow', 'chevron'],
+ user: ['user', 'identification', 'badge'],
+ actions: ['edit', 'delete', 'plus', 'check', 'close', 'refresh', 'duplicate', 'eye', 'filter', 'dots'],
+ ecommerce: ['shopping', 'credit-card', 'currency', 'gift', 'tag', 'truck', 'receipt'],
+ inventory: ['cube', 'collection', 'photograph', 'chart'],
+ communication: ['mail', 'phone', 'chat', 'bell', 'inbox'],
+ files: ['document', 'folder', 'download', 'upload'],
+ settings: ['cog', 'adjustments', 'calendar', 'moon', 'sun'],
+ status: ['exclamation', 'information', 'spinner', 'star', 'heart', 'flag'],
+ testing: ['view-grid', 'beaker', 'clipboard-list', 'check-circle', 'lightning-bolt', 'clock', 'lock-closed', 'database', 'light-bulb', 'book-open', 'play']
+ };
+
+ for (const [category, keywords] of Object.entries(categoryMap)) {
+ if (keywords.some(keyword => iconName.includes(keyword))) {
+ return category;
+ }
+ }
+ return 'navigation'; // default
+ },
+
+ /**
+ * Filter icons based on search and category
+ */
+ filterIcons() {
+ let icons = this.allIcons;
+
+ // Filter by category
+ if (this.activeCategory !== 'all') {
+ icons = icons.filter(icon => icon.category === this.activeCategory);
+ }
+
+ // Filter by search query
+ if (this.searchQuery.trim()) {
+ const query = this.searchQuery.toLowerCase();
+ icons = icons.filter(icon => icon.name.toLowerCase().includes(query));
+ }
+
+ this.filteredIcons = icons;
+ iconsLog.debug(`Filtered to ${icons.length} icons`);
+ },
+
+ /**
+ * Set active category
+ */
+ setCategory(categoryId) {
+ iconsLog.info('Setting category:', categoryId);
+ this.activeCategory = categoryId;
+ this.filterIcons();
+ },
+
+ /**
+ * Select icon for detail view
+ */
+ selectIcon(iconName) {
+ iconsLog.info('Selected icon:', iconName);
+ this.selectedIcon = iconName;
+ },
+
+ /**
+ * Copy icon usage code to clipboard
+ */
+ async copyIconUsage(iconName) {
+ const code = `x-html="$icon('${iconName}', 'w-5 h-5')"`;
+ try {
+ await navigator.clipboard.writeText(code);
+ Utils.showToast(`'${iconName}' code copied!`, 'success');
+ iconsLog.debug('Icon usage code copied:', iconName);
+ } catch (error) {
+ iconsLog.error('Failed to copy code:', error);
+ Utils.showToast('Failed to copy code', 'error');
+ }
+ },
+
+ /**
+ * Copy icon name to clipboard
+ */
+ async copyIconName(iconName) {
+ try {
+ await navigator.clipboard.writeText(iconName);
+ Utils.showToast(`'${iconName}' copied!`, 'success');
+ iconsLog.debug('Icon name copied:', iconName);
+ } catch (error) {
+ iconsLog.error('Failed to copy name:', error);
+ Utils.showToast('Failed to copy name', 'error');
+ }
+ },
+
+ /**
+ * Get category info
+ */
+ getCategoryInfo(categoryId) {
+ return this.categories.find(c => c.id === categoryId) || this.categories[0];
+ },
+
+ /**
+ * Get icon count for category
+ */
+ getCategoryCount(categoryId) {
+ if (categoryId === 'all') {
+ return this.allIcons.length;
+ }
+ return this.iconsByCategory[categoryId]?.length || 0;
+ }
+ };
+}
+
+iconsLog.info('Icons page module loaded');
\ No newline at end of file
diff --git a/static/admin/js/testing-hub.js b/static/admin/js/testing-hub.js
new file mode 100644
index 00000000..6122a79d
--- /dev/null
+++ b/static/admin/js/testing-hub.js
@@ -0,0 +1,131 @@
+// static/admin/js/testing-hub.js
+
+// Setup logging
+const TESTING_HUB_LOG_LEVEL = 3;
+
+const testingLog = {
+ error: (...args) => TESTING_HUB_LOG_LEVEL >= 1 && console.error('❌ [TESTING HUB ERROR]', ...args),
+ warn: (...args) => TESTING_HUB_LOG_LEVEL >= 2 && console.warn('⚠️ [TESTING HUB WARN]', ...args),
+ info: (...args) => TESTING_HUB_LOG_LEVEL >= 3 && console.info('ℹ️ [TESTING HUB INFO]', ...args),
+ debug: (...args) => TESTING_HUB_LOG_LEVEL >= 4 && console.log('🔍 [TESTING HUB DEBUG]', ...args)
+};
+
+/**
+ * Testing Hub Alpine.js Component
+ * Central hub for all test suites and QA tools
+ */
+function adminTestingHub() {
+ return {
+ // ✅ CRITICAL: Inherit base layout functionality
+ ...data(),
+
+ // ✅ CRITICAL: Set page identifier
+ currentPage: 'testing',
+
+ // Test suites data
+ testSuites: [
+ {
+ id: 'auth-flow',
+ name: 'Authentication Flow',
+ description: 'Test login, logout, token expiration, redirects, and protected route access.',
+ url: '/admin/test/auth-flow',
+ icon: 'lock-closed',
+ color: 'blue',
+ testCount: 6,
+ features: [
+ 'Login with valid/invalid credentials',
+ 'Token expiration handling',
+ 'Protected route access & redirects',
+ 'localStorage state monitoring'
+ ]
+ },
+ {
+ id: 'vendors-users',
+ name: 'Data Migration & CRUD',
+ description: 'Test vendor and user creation, listing, editing, deletion, and data migration scenarios.',
+ url: '/admin/test/vendors-users-migration',
+ icon: 'database',
+ color: 'orange',
+ testCount: 10,
+ features: [
+ 'Vendor CRUD operations',
+ 'User management & roles',
+ 'Data migration validation',
+ 'Form validation & error handling'
+ ]
+ }
+ ],
+
+ // Stats
+ stats: {
+ totalSuites: 2,
+ totalTests: 16,
+ coverage: 'Auth, CRUD',
+ avgDuration: '< 5 min'
+ },
+
+ // Loading state
+ loading: false,
+
+ // ✅ CRITICAL: Proper initialization with guard
+ async init() {
+ testingLog.info('=== TESTING HUB INITIALIZING ===');
+
+ // Prevent multiple initializations
+ if (window._testingHubInitialized) {
+ testingLog.warn('Testing hub already initialized, skipping...');
+ return;
+ }
+ window._testingHubInitialized = true;
+
+ // Calculate stats
+ this.calculateStats();
+
+ testingLog.info('=== TESTING HUB INITIALIZATION COMPLETE ===');
+ },
+
+ /**
+ * Calculate test statistics
+ */
+ calculateStats() {
+ this.stats.totalSuites = this.testSuites.length;
+ this.stats.totalTests = this.testSuites.reduce((sum, suite) => sum + suite.testCount, 0);
+ testingLog.debug('Stats calculated:', this.stats);
+ },
+
+ /**
+ * Get color classes for test suite cards
+ */
+ getColorClasses(color) {
+ const colorMap = {
+ blue: {
+ gradient: 'from-blue-500 to-blue-600',
+ button: 'bg-blue-600 hover:bg-blue-700'
+ },
+ orange: {
+ gradient: 'from-orange-500 to-orange-600',
+ button: 'bg-orange-600 hover:bg-orange-700'
+ },
+ green: {
+ gradient: 'from-green-500 to-green-600',
+ button: 'bg-green-600 hover:bg-green-700'
+ },
+ purple: {
+ gradient: 'from-purple-500 to-purple-600',
+ button: 'bg-purple-600 hover:bg-purple-700'
+ }
+ };
+ return colorMap[color] || colorMap.blue;
+ },
+
+ /**
+ * Navigate to test suite
+ */
+ goToTest(url) {
+ testingLog.info('Navigating to test suite:', url);
+ window.location.href = url;
+ }
+ };
+}
+
+testingLog.info('Testing hub module loaded');
\ No newline at end of file
diff --git a/static/admin/js/vendor-detail.js b/static/admin/js/vendor-detail.js
new file mode 100644
index 00000000..754eb72b
--- /dev/null
+++ b/static/admin/js/vendor-detail.js
@@ -0,0 +1,135 @@
+// static/admin/js/vendor-detail.js
+
+// Log levels: 0 = None, 1 = Error, 2 = Warning, 3 = Info, 4 = Debug
+const VENDOR_DETAIL_LOG_LEVEL = 3;
+
+const detailLog = {
+ error: (...args) => VENDOR_DETAIL_LOG_LEVEL >= 1 && console.error('❌ [VENDOR_DETAIL ERROR]', ...args),
+ warn: (...args) => VENDOR_DETAIL_LOG_LEVEL >= 2 && console.warn('⚠️ [VENDOR_DETAIL WARN]', ...args),
+ info: (...args) => VENDOR_DETAIL_LOG_LEVEL >= 3 && console.info('ℹ️ [VENDOR_DETAIL INFO]', ...args),
+ debug: (...args) => VENDOR_DETAIL_LOG_LEVEL >= 4 && console.log('🔍 [VENDOR_DETAIL DEBUG]', ...args)
+};
+
+function adminVendorDetail() {
+ return {
+ // Inherit base layout functionality from init-alpine.js
+ ...data(),
+
+ // Vendor detail page specific state
+ currentPage: 'vendor-detail',
+ vendor: null,
+ loading: false,
+ error: null,
+ vendorCode: null,
+
+ // Initialize
+ async init() {
+ detailLog.info('=== VENDOR DETAIL PAGE INITIALIZING ===');
+
+ // Prevent multiple initializations
+ if (window._vendorDetailInitialized) {
+ detailLog.warn('Vendor detail page already initialized, skipping...');
+ return;
+ }
+ window._vendorDetailInitialized = true;
+
+ // Get vendor code from URL
+ const path = window.location.pathname;
+ const match = path.match(/\/admin\/vendors\/([^\/]+)$/);
+
+ if (match) {
+ this.vendorCode = match[1];
+ detailLog.info('Viewing vendor:', this.vendorCode);
+ await this.loadVendor();
+ } else {
+ detailLog.error('No vendor code in URL');
+ this.error = 'Invalid vendor URL';
+ Utils.showToast('Invalid vendor URL', 'error');
+ }
+
+ detailLog.info('=== VENDOR DETAIL PAGE INITIALIZATION COMPLETE ===');
+ },
+
+ // Load vendor data
+ async loadVendor() {
+ detailLog.info('Loading vendor details...');
+ this.loading = true;
+ this.error = null;
+
+ try {
+ const startTime = Date.now();
+ const response = await apiClient.get(`/admin/vendors/${this.vendorCode}`);
+ const duration = Date.now() - startTime;
+
+ this.vendor = response;
+
+ detailLog.info(`Vendor loaded in ${duration}ms`, {
+ vendor_code: this.vendor.vendor_code,
+ name: this.vendor.name,
+ is_verified: this.vendor.is_verified,
+ is_active: this.vendor.is_active
+ });
+ detailLog.debug('Full vendor data:', this.vendor);
+
+ } catch (error) {
+ detailLog.error('Failed to load vendor:', error);
+ this.error = error.message || 'Failed to load vendor details';
+ Utils.showToast('Failed to load vendor details', 'error');
+ } finally {
+ this.loading = false;
+ }
+ },
+
+ // Format date (matches dashboard pattern)
+ formatDate(dateString) {
+ if (!dateString) {
+ detailLog.debug('formatDate called with empty dateString');
+ return '-';
+ }
+ const formatted = Utils.formatDate(dateString);
+ detailLog.debug(`Date formatted: ${dateString} -> ${formatted}`);
+ return formatted;
+ },
+
+ // Delete vendor
+ async deleteVendor() {
+ detailLog.info('Delete vendor requested:', this.vendorCode);
+
+ if (!confirm(`Are you sure you want to delete vendor "${this.vendor.name}"?\n\nThis action cannot be undone and will delete:\n- All products\n- All orders\n- All customers\n- All team members`)) {
+ detailLog.info('Delete cancelled by user');
+ return;
+ }
+
+ // Second confirmation for safety
+ if (!confirm(`FINAL CONFIRMATION\n\nType the vendor code to confirm: ${this.vendor.vendor_code}\n\nAre you absolutely sure?`)) {
+ detailLog.info('Delete cancelled by user (second confirmation)');
+ return;
+ }
+
+ try {
+ detailLog.info('Deleting vendor:', this.vendorCode);
+ await apiClient.delete(`/admin/vendors/${this.vendorCode}?confirm=true`);
+
+ Utils.showToast('Vendor deleted successfully', 'success');
+ detailLog.info('Vendor deleted successfully');
+
+ // Redirect to vendors list
+ setTimeout(() => window.location.href = '/admin/vendors', 1500);
+
+ } catch (error) {
+ detailLog.error('Failed to delete vendor:', error);
+ Utils.showToast(error.message || 'Failed to delete vendor', 'error');
+ }
+ },
+
+ // Refresh vendor data
+ async refresh() {
+ detailLog.info('=== VENDOR REFRESH TRIGGERED ===');
+ await this.loadVendor();
+ Utils.showToast('Vendor details refreshed', 'success');
+ detailLog.info('=== VENDOR REFRESH COMPLETE ===');
+ }
+ };
+}
+
+detailLog.info('Vendor detail module loaded');
\ No newline at end of file
diff --git a/static/admin/js/vendors.js b/static/admin/js/vendors.js
index 3bb5c88a..62be60af 100644
--- a/static/admin/js/vendors.js
+++ b/static/admin/js/vendors.js
@@ -18,8 +18,10 @@ function adminVendors() {
// Inherit base layout functionality from init-alpine.js
...data(),
- // Vendors page specific state
+ // ✅ CRITICAL: Page identifier for sidebar active state
currentPage: 'vendors',
+
+ // Vendors page specific state
vendors: [],
stats: {
total: 0,
@@ -30,8 +32,8 @@ function adminVendors() {
loading: false,
error: null,
- // Pagination state
- currentPage: 1,
+ // Pagination state (renamed from currentPage to avoid conflict)
+ page: 1, // ✅ FIXED: Was 'currentPage' which conflicted with sidebar
itemsPerPage: 10,
// Initialize
@@ -53,7 +55,7 @@ function adminVendors() {
// Computed: Get paginated vendors for current page
get paginatedVendors() {
- const start = (this.currentPage - 1) * this.itemsPerPage;
+ const start = (this.page - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
return this.vendors.slice(start, end);
},
@@ -66,12 +68,12 @@ function adminVendors() {
// Computed: Start index for pagination display
get startIndex() {
if (this.vendors.length === 0) return 0;
- return (this.currentPage - 1) * this.itemsPerPage + 1;
+ return (this.page - 1) * this.itemsPerPage + 1;
},
// Computed: End index for pagination display
get endIndex() {
- const end = this.currentPage * this.itemsPerPage;
+ const end = this.page * this.itemsPerPage;
return end > this.vendors.length ? this.vendors.length : end;
},
@@ -79,7 +81,7 @@ function adminVendors() {
get pageNumbers() {
const pages = [];
const totalPages = this.totalPages;
- const current = this.currentPage;
+ const current = this.page;
if (totalPages <= 7) {
// Show all pages if 7 or fewer
@@ -137,7 +139,7 @@ function adminVendors() {
}
// Reset to first page when data is loaded
- this.currentPage = 1;
+ this.page = 1;
} catch (error) {
vendorsLog.error('Failed to load vendors:', error);
@@ -167,27 +169,27 @@ function adminVendors() {
},
// Pagination: Go to specific page
- goToPage(page) {
- if (page === '...' || page < 1 || page > this.totalPages) {
+ goToPage(pageNum) {
+ if (pageNum === '...' || pageNum < 1 || pageNum > this.totalPages) {
return;
}
- vendorsLog.info('Going to page:', page);
- this.currentPage = page;
+ vendorsLog.info('Going to page:', pageNum);
+ this.page = pageNum;
},
// Pagination: Go to next page
nextPage() {
- if (this.currentPage < this.totalPages) {
+ if (this.page < this.totalPages) {
vendorsLog.info('Going to next page');
- this.currentPage++;
+ this.page++;
}
},
// Pagination: Go to previous page
previousPage() {
- if (this.currentPage > 1) {
+ if (this.page > 1) {
vendorsLog.info('Going to previous page');
- this.currentPage--;
+ this.page--;
}
},
diff --git a/static/shared/js/icons.js b/static/shared/js/icons.js
index 0f8257d6..c8455bd6 100644
--- a/static/shared/js/icons.js
+++ b/static/shared/js/icons.js
@@ -53,50 +53,34 @@ const Icons = {
'cube': `
`,
'collection': `
`,
'photograph': `
`,
- 'color-swatch': `
`,
- 'template': `
`,
- 'clipboard-list': `
`,
-
- // Analytics & Reports
- 'chart': `
`,
- 'trending-up': `
`,
- 'trending-down': `
`,
- 'presentation-chart-line': `
`,
- 'calculator': `
`,
-
+ 'chart-bar': `
`,
+ 'chart-pie': `
`,
+
// Communication
- 'bell': `
`,
'mail': `
`,
- 'chat': `
`,
- 'annotation': `
`,
'phone': `
`,
-
- // System & Settings
- 'cog': `
`,
- 'sun': `
`,
- 'moon': `
`,
- 'database': `
`,
- 'server': `
`,
- 'shield-check': `
`,
- 'key': `
`,
- 'lock-closed': `
`,
- 'lock-open': `
`,
-
- // Document & File
+ 'chat': `
`,
+ 'bell': `
`,
+ 'inbox': `
`,
+
+ // Files & Documents
'document': `
`,
'folder': `
`,
'folder-open': `
`,
'download': `
`,
'upload': `
`,
-
- // Time & Calendar
+
+ // Settings & Tools
+ 'cog': `
`,
+ 'adjustments': `
`,
'calendar': `
`,
- 'clock': `
`,
-
+ 'moon': `
`,
+ 'sun': `
`,
+
// Location
'location-marker': `
`,
'globe': `
`,
-
+
// Status & Indicators
'exclamation': `
`,
'information-circle': `
`,
@@ -104,11 +88,26 @@ const Icons = {
'star': `
`,
'heart': `
`,
'flag': `
`,
-
+
// Links & External
'external-link': `
`,
'link': `
`,
- 'logout': `
`
+ 'logout': `
`,
+
+ // Developer Tools (Testing Section)
+ 'view-grid': `
`,
+ 'beaker': `
`,
+
+ // Testing & QA Icons
+ 'clipboard-list': `
`,
+ 'check-circle': `
`,
+ 'lightning-bolt': `
`,
+ 'clock': `
`,
+ 'lock-closed': `
`,
+ 'database': `
`,
+ 'light-bulb': `
`,
+ 'book-open': `
`,
+ 'play': `
`
};
/**
@@ -133,7 +132,7 @@ function icon(name, classes = 'w-5 h-5') {
document.addEventListener('alpine:init', () => {
// ✅ CORRECT: Return the function directly, not wrapped in another function
Alpine.magic('icon', () => icon);
-
+
console.log('✅ Alpine $icon magic helper registered');
});
@@ -147,4 +146,4 @@ window.icon = icon;
window.Icons = Icons;
console.log('📦 Icon system loaded');
-console.log('📊 Total icons available:', Object.keys(Icons).length);
+console.log('📊 Total icons available:', Object.keys(Icons).length);
\ No newline at end of file