admin panel migration to jinja
This commit is contained in:
@@ -1,247 +1,258 @@
|
||||
// static/admin/js/vendors.js
|
||||
// Admin Vendor Creation Component
|
||||
function vendorCreation() {
|
||||
|
||||
// Log levels: 0 = None, 1 = Error, 2 = Warning, 3 = Info, 4 = Debug
|
||||
const VENDORS_LOG_LEVEL = 3;
|
||||
|
||||
const vendorsLog = {
|
||||
error: (...args) => VENDORS_LOG_LEVEL >= 1 && console.error('❌ [VENDORS ERROR]', ...args),
|
||||
warn: (...args) => VENDORS_LOG_LEVEL >= 2 && console.warn('⚠️ [VENDORS WARN]', ...args),
|
||||
info: (...args) => VENDORS_LOG_LEVEL >= 3 && console.info('ℹ️ [VENDORS INFO]', ...args),
|
||||
debug: (...args) => VENDORS_LOG_LEVEL >= 4 && console.log('🔍 [VENDORS DEBUG]', ...args)
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// VENDOR LIST FUNCTION
|
||||
// ============================================
|
||||
function adminVendors() {
|
||||
return {
|
||||
currentUser: {},
|
||||
formData: {
|
||||
vendor_code: '',
|
||||
name: '',
|
||||
subdomain: '',
|
||||
description: '',
|
||||
owner_email: '',
|
||||
business_phone: '',
|
||||
website: '',
|
||||
business_address: '',
|
||||
tax_number: ''
|
||||
// Inherit base layout functionality from init-alpine.js
|
||||
...data(),
|
||||
|
||||
// Vendors page specific state
|
||||
currentPage: 'vendors',
|
||||
vendors: [],
|
||||
stats: {
|
||||
total: 0,
|
||||
verified: 0,
|
||||
pending: 0,
|
||||
inactive: 0
|
||||
},
|
||||
loading: false,
|
||||
errors: {},
|
||||
showCredentials: false,
|
||||
credentials: null,
|
||||
error: null,
|
||||
|
||||
init() {
|
||||
if (!this.checkAuth()) {
|
||||
// Pagination state
|
||||
currentPage: 1,
|
||||
itemsPerPage: 10,
|
||||
|
||||
// Initialize
|
||||
async init() {
|
||||
vendorsLog.info('=== VENDORS PAGE INITIALIZING ===');
|
||||
|
||||
// Prevent multiple initializations
|
||||
if (window._vendorsInitialized) {
|
||||
vendorsLog.warn('Vendors page already initialized, skipping...');
|
||||
return;
|
||||
}
|
||||
window._vendorsInitialized = true;
|
||||
|
||||
await this.loadVendors();
|
||||
await this.loadStats();
|
||||
|
||||
vendorsLog.info('=== VENDORS PAGE INITIALIZATION COMPLETE ===');
|
||||
},
|
||||
|
||||
checkAuth() {
|
||||
if (!Auth.isAuthenticated()) {
|
||||
// ← CHANGED: Use new Jinja2 route
|
||||
window.location.href = '/admin/login';
|
||||
return false;
|
||||
}
|
||||
|
||||
const user = Auth.getCurrentUser();
|
||||
if (!user || user.role !== 'admin') {
|
||||
Utils.showToast('Access denied. Admin privileges required.', 'error');
|
||||
Auth.logout();
|
||||
// ← CHANGED: Use new Jinja2 route
|
||||
window.location.href = '/admin/login';
|
||||
return false;
|
||||
}
|
||||
|
||||
this.currentUser = user;
|
||||
return true;
|
||||
// Computed: Get paginated vendors for current page
|
||||
get paginatedVendors() {
|
||||
const start = (this.currentPage - 1) * this.itemsPerPage;
|
||||
const end = start + this.itemsPerPage;
|
||||
return this.vendors.slice(start, end);
|
||||
},
|
||||
|
||||
async handleLogout() {
|
||||
const confirmed = await Utils.confirm(
|
||||
'Are you sure you want to logout?',
|
||||
'Confirm Logout'
|
||||
);
|
||||
|
||||
if (confirmed) {
|
||||
Auth.logout();
|
||||
Utils.showToast('Logged out successfully', 'success', 2000);
|
||||
setTimeout(() => {
|
||||
// ← CHANGED: Use new Jinja2 route
|
||||
window.location.href = '/admin/login';
|
||||
}, 500);
|
||||
}
|
||||
// Computed: Total number of pages
|
||||
get totalPages() {
|
||||
return Math.ceil(this.vendors.length / this.itemsPerPage);
|
||||
},
|
||||
|
||||
// ... rest of the methods stay the same ...
|
||||
|
||||
// Auto-format vendor code (uppercase)
|
||||
formatVendorCode() {
|
||||
this.formData.vendor_code = this.formData.vendor_code
|
||||
.toUpperCase()
|
||||
.replace(/[^A-Z0-9_-]/g, '');
|
||||
// Computed: Start index for pagination display
|
||||
get startIndex() {
|
||||
if (this.vendors.length === 0) return 0;
|
||||
return (this.currentPage - 1) * this.itemsPerPage + 1;
|
||||
},
|
||||
|
||||
// Auto-format subdomain (lowercase)
|
||||
formatSubdomain() {
|
||||
this.formData.subdomain = this.formData.subdomain
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9-]/g, '');
|
||||
// Computed: End index for pagination display
|
||||
get endIndex() {
|
||||
const end = this.currentPage * this.itemsPerPage;
|
||||
return end > this.vendors.length ? this.vendors.length : end;
|
||||
},
|
||||
|
||||
clearErrors() {
|
||||
this.errors = {};
|
||||
// Computed: Generate page numbers array with ellipsis
|
||||
get pageNumbers() {
|
||||
const pages = [];
|
||||
const totalPages = this.totalPages;
|
||||
const current = this.currentPage;
|
||||
|
||||
if (totalPages <= 7) {
|
||||
// Show all pages if 7 or fewer
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
// Always show first page
|
||||
pages.push(1);
|
||||
|
||||
if (current > 3) {
|
||||
pages.push('...');
|
||||
}
|
||||
|
||||
// Show pages around current page
|
||||
const start = Math.max(2, current - 1);
|
||||
const end = Math.min(totalPages - 1, current + 1);
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
|
||||
if (current < totalPages - 2) {
|
||||
pages.push('...');
|
||||
}
|
||||
|
||||
// Always show last page
|
||||
pages.push(totalPages);
|
||||
}
|
||||
|
||||
return pages;
|
||||
},
|
||||
|
||||
validateForm() {
|
||||
this.clearErrors();
|
||||
let isValid = true;
|
||||
|
||||
// Required fields validation
|
||||
if (!this.formData.vendor_code.trim()) {
|
||||
this.errors.vendor_code = 'Vendor code is required';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (!this.formData.name.trim()) {
|
||||
this.errors.name = 'Vendor name is required';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (!this.formData.subdomain.trim()) {
|
||||
this.errors.subdomain = 'Subdomain is required';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (!this.formData.owner_email.trim()) {
|
||||
this.errors.owner_email = 'Owner email is required';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Email validation
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (this.formData.owner_email && !emailRegex.test(this.formData.owner_email)) {
|
||||
this.errors.owner_email = 'Invalid email format';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Subdomain validation (must start and end with alphanumeric)
|
||||
const subdomainRegex = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
||||
if (this.formData.subdomain && this.formData.subdomain.length > 1 &&
|
||||
!subdomainRegex.test(this.formData.subdomain)) {
|
||||
this.errors.subdomain = 'Subdomain must start and end with a letter or number';
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
},
|
||||
|
||||
async handleSubmit() {
|
||||
if (!this.validateForm()) {
|
||||
Utils.showToast('Please fix validation errors', 'error');
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Load vendors list
|
||||
async loadVendors() {
|
||||
vendorsLog.info('Loading vendors list...');
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
// Prepare data (remove empty fields)
|
||||
const submitData = {};
|
||||
for (const [key, value] of Object.entries(this.formData)) {
|
||||
if (value !== '' && value !== null && value !== undefined) {
|
||||
submitData[key] = value;
|
||||
}
|
||||
const startTime = Date.now();
|
||||
const response = await apiClient.get('/admin/vendors');
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
// Handle different response structures
|
||||
this.vendors = response.vendors || response.items || response || [];
|
||||
|
||||
vendorsLog.info(`Vendors loaded in ${duration}ms`, {
|
||||
count: this.vendors.length,
|
||||
hasVendors: this.vendors.length > 0
|
||||
});
|
||||
|
||||
if (this.vendors.length > 0) {
|
||||
vendorsLog.debug('First vendor:', this.vendors[0]);
|
||||
}
|
||||
|
||||
console.log('Submitting vendor data:', submitData);
|
||||
|
||||
const response = await apiClient.post('/admin/vendors', submitData);
|
||||
|
||||
console.log('Vendor creation response:', response);
|
||||
|
||||
// Store credentials - be flexible with response structure
|
||||
this.credentials = {
|
||||
vendor_code: response.vendor_code || this.formData.vendor_code,
|
||||
subdomain: response.subdomain || this.formData.subdomain,
|
||||
name: response.name || this.formData.name,
|
||||
owner_username: response.owner_username || `${this.formData.subdomain}_owner`,
|
||||
owner_email: response.owner_email || this.formData.owner_email,
|
||||
temporary_password: response.temporary_password || 'PASSWORD_NOT_RETURNED',
|
||||
login_url: response.login_url ||
|
||||
`http://localhost:8000/vendor/${this.formData.subdomain}/login` ||
|
||||
`${this.formData.subdomain}.platform.com/vendor/login`
|
||||
};
|
||||
|
||||
console.log('Stored credentials:', this.credentials);
|
||||
|
||||
// Check if password was returned
|
||||
if (!response.temporary_password) {
|
||||
console.warn('⚠️ Warning: temporary_password not returned from API');
|
||||
console.warn('Full API response:', response);
|
||||
Utils.showToast('Vendor created but password not returned. Check server logs.', 'warning', 5000);
|
||||
}
|
||||
|
||||
// Show credentials display
|
||||
this.showCredentials = true;
|
||||
|
||||
// Success notification
|
||||
Utils.showToast('Vendor created successfully!', 'success');
|
||||
|
||||
// Scroll to top to see credentials
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
// Reset to first page when data is loaded
|
||||
this.currentPage = 1;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating vendor:', error);
|
||||
|
||||
// Check for specific validation errors
|
||||
if (error.message.includes('vendor_code') || error.message.includes('Vendor code')) {
|
||||
this.errors.vendor_code = 'Vendor code already exists';
|
||||
} else if (error.message.includes('subdomain')) {
|
||||
this.errors.subdomain = 'Subdomain already exists';
|
||||
} else if (error.message.includes('email')) {
|
||||
this.errors.owner_email = 'Email already in use';
|
||||
}
|
||||
|
||||
Utils.showToast(
|
||||
error.message || 'Failed to create vendor',
|
||||
'error'
|
||||
);
|
||||
vendorsLog.error('Failed to load vendors:', error);
|
||||
this.error = error.message || 'Failed to load vendors';
|
||||
Utils.showToast('Failed to load vendors', 'error');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
this.formData = {
|
||||
vendor_code: '',
|
||||
name: '',
|
||||
subdomain: '',
|
||||
description: '',
|
||||
owner_email: '',
|
||||
business_phone: '',
|
||||
website: '',
|
||||
business_address: '',
|
||||
tax_number: ''
|
||||
};
|
||||
this.clearErrors();
|
||||
this.showCredentials = false;
|
||||
this.credentials = null;
|
||||
// Load statistics
|
||||
async loadStats() {
|
||||
vendorsLog.info('Loading vendor statistics...');
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
const response = await apiClient.get('/admin/vendors/stats');
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
this.stats = response;
|
||||
vendorsLog.info(`Stats loaded in ${duration}ms`, this.stats);
|
||||
|
||||
} catch (error) {
|
||||
vendorsLog.error('Failed to load stats:', error);
|
||||
// Don't show error toast for stats, just log it
|
||||
}
|
||||
},
|
||||
|
||||
copyToClipboard(text, label) {
|
||||
if (!text) {
|
||||
Utils.showToast('Nothing to copy', 'error');
|
||||
// Pagination: Go to specific page
|
||||
goToPage(page) {
|
||||
if (page === '...' || page < 1 || page > this.totalPages) {
|
||||
return;
|
||||
}
|
||||
vendorsLog.info('Going to page:', page);
|
||||
this.currentPage = page;
|
||||
},
|
||||
|
||||
// Pagination: Go to next page
|
||||
nextPage() {
|
||||
if (this.currentPage < this.totalPages) {
|
||||
vendorsLog.info('Going to next page');
|
||||
this.currentPage++;
|
||||
}
|
||||
},
|
||||
|
||||
// Pagination: Go to previous page
|
||||
previousPage() {
|
||||
if (this.currentPage > 1) {
|
||||
vendorsLog.info('Going to previous page');
|
||||
this.currentPage--;
|
||||
}
|
||||
},
|
||||
|
||||
// Format date (matches dashboard pattern)
|
||||
formatDate(dateString) {
|
||||
if (!dateString) {
|
||||
vendorsLog.debug('formatDate called with empty dateString');
|
||||
return '-';
|
||||
}
|
||||
const formatted = Utils.formatDate(dateString);
|
||||
vendorsLog.debug(`Date formatted: ${dateString} -> ${formatted}`);
|
||||
return formatted;
|
||||
},
|
||||
|
||||
// View vendor details
|
||||
viewVendor(vendorCode) {
|
||||
vendorsLog.info('Navigating to vendor details:', vendorCode);
|
||||
const url = `/admin/vendors/${vendorCode}`;
|
||||
vendorsLog.debug('Navigation URL:', url);
|
||||
window.location.href = url;
|
||||
},
|
||||
|
||||
// Edit vendor
|
||||
editVendor(vendorCode) {
|
||||
vendorsLog.info('Navigating to vendor edit:', vendorCode);
|
||||
const url = `/admin/vendors/${vendorCode}/edit`;
|
||||
vendorsLog.debug('Navigation URL:', url);
|
||||
window.location.href = url;
|
||||
},
|
||||
|
||||
// Delete vendor
|
||||
async deleteVendor(vendor) {
|
||||
vendorsLog.info('Delete vendor requested:', vendor.vendor_code);
|
||||
|
||||
if (!confirm(`Are you sure you want to delete vendor "${vendor.name}"?\n\nThis action cannot be undone.`)) {
|
||||
vendorsLog.info('Delete cancelled by user');
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
Utils.showToast(`${label} copied to clipboard`, 'success', 2000);
|
||||
}).catch((err) => {
|
||||
console.error('Failed to copy:', err);
|
||||
// Fallback for older browsers
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.left = '-999999px';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
Utils.showToast(`${label} copied to clipboard`, 'success', 2000);
|
||||
} catch (err) {
|
||||
Utils.showToast('Failed to copy to clipboard', 'error');
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
});
|
||||
try {
|
||||
vendorsLog.info('Deleting vendor:', vendor.vendor_code);
|
||||
await apiClient.delete(`/admin/vendors/${vendor.vendor_code}`);
|
||||
|
||||
Utils.showToast('Vendor deleted successfully', 'success');
|
||||
vendorsLog.info('Vendor deleted successfully');
|
||||
|
||||
// Reload data
|
||||
await this.loadVendors();
|
||||
await this.loadStats();
|
||||
|
||||
} catch (error) {
|
||||
vendorsLog.error('Failed to delete vendor:', error);
|
||||
Utils.showToast(error.message || 'Failed to delete vendor', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
// Refresh vendors list
|
||||
async refresh() {
|
||||
vendorsLog.info('=== VENDORS REFRESH TRIGGERED ===');
|
||||
await this.loadVendors();
|
||||
await this.loadStats();
|
||||
Utils.showToast('Vendors list refreshed', 'success');
|
||||
vendorsLog.info('=== VENDORS REFRESH COMPLETE ===');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
vendorsLog.info('Vendors module loaded');
|
||||
Reference in New Issue
Block a user