adding developer tools in admin panel, adding vendor management

This commit is contained in:
2025-10-25 18:07:02 +02:00
parent 7bb69a9a96
commit 49890d4cbe
19 changed files with 3456 additions and 226 deletions

View File

@@ -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');

View File

@@ -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');

View File

@@ -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');

View File

@@ -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');

View File

@@ -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--;
}
},