adding developer tools in admin panel, adding vendor management
This commit is contained in:
140
static/admin/js/components.js
Normal file
140
static/admin/js/components.js
Normal 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');
|
||||
210
static/admin/js/icons-page.js
Normal file
210
static/admin/js/icons-page.js
Normal 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');
|
||||
131
static/admin/js/testing-hub.js
Normal file
131
static/admin/js/testing-hub.js
Normal 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');
|
||||
135
static/admin/js/vendor-detail.js
Normal file
135
static/admin/js/vendor-detail.js
Normal 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');
|
||||
@@ -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--;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user