admin panel migration to jinja

This commit is contained in:
2025-10-25 07:31:44 +02:00
parent 13ae656a49
commit 1a43a4250c
21 changed files with 1788 additions and 1599 deletions

140
static/admin/js/users.js Normal file
View File

@@ -0,0 +1,140 @@
function adminUsers() {
return {
// State
users: [],
loading: false,
filters: {
search: '',
role: '',
is_active: ''
},
stats: {
total: 0,
active: 0,
vendors: 0,
admins: 0
},
pagination: {
page: 1,
per_page: 10,
total: 0,
pages: 0
},
// Initialization
init() {
Logger.info('Users page initialized', 'USERS');
this.loadUsers();
this.loadStats();
},
// Load users from API
async loadUsers() {
this.loading = true;
try {
const params = new URLSearchParams({
page: this.pagination.page,
per_page: this.pagination.per_page,
...this.filters
});
const response = await ApiClient.get(`/admin/users?${params}`);
if (response.items) {
this.users = response.items;
this.pagination.total = response.total;
this.pagination.pages = response.pages;
}
} catch (error) {
Logger.error('Failed to load users', 'USERS', error);
Utils.showToast('Failed to load users', 'error');
} finally {
this.loading = false;
}
},
// Load statistics
async loadStats() {
try {
const response = await ApiClient.get('/admin/users/stats');
if (response) {
this.stats = response;
}
} catch (error) {
Logger.error('Failed to load stats', 'USERS', error);
}
},
// Search with debounce
debouncedSearch: Utils.debounce(function() {
this.pagination.page = 1;
this.loadUsers();
}, 500),
// Pagination
nextPage() {
if (this.pagination.page < this.pagination.pages) {
this.pagination.page++;
this.loadUsers();
}
},
previousPage() {
if (this.pagination.page > 1) {
this.pagination.page--;
this.loadUsers();
}
},
// Actions
viewUser(user) {
Logger.info('View user', 'USERS', user);
// TODO: Open view modal
},
editUser(user) {
Logger.info('Edit user', 'USERS', user);
// TODO: Open edit modal
},
async toggleUserStatus(user) {
const action = user.is_active ? 'deactivate' : 'activate';
if (!confirm(`Are you sure you want to ${action} ${user.username}?`)) {
return;
}
try {
await ApiClient.put(`/admin/users/${user.id}/status`, {
is_active: !user.is_active
});
Utils.showToast(`User ${action}d successfully`, 'success');
this.loadUsers();
this.loadStats();
} catch (error) {
Logger.error(`Failed to ${action} user`, 'USERS', error);
Utils.showToast(`Failed to ${action} user`, 'error');
}
},
async deleteUser(user) {
if (!confirm(`Are you sure you want to delete ${user.username}? This action cannot be undone.`)) {
return;
}
try {
await ApiClient.delete(`/admin/users/${user.id}`);
Utils.showToast('User deleted successfully', 'success');
this.loadUsers();
this.loadStats();
} catch (error) {
Logger.error('Failed to delete user', 'USERS', error);
Utils.showToast('Failed to delete user', 'error');
}
},
openCreateModal() {
Logger.info('Open create user modal', 'USERS');
// TODO: Open create modal
}
};
}

View File

@@ -1,338 +1,206 @@
// static/js/admin/vendor-edit.js
// static/admin/js/vendor-edit.js
function vendorEdit() {
// Log levels: 0 = None, 1 = Error, 2 = Warning, 3 = Info, 4 = Debug
const VENDOR_EDIT_LOG_LEVEL = 3;
const editLog = {
error: (...args) => VENDOR_EDIT_LOG_LEVEL >= 1 && console.error('❌ [VENDOR_EDIT ERROR]', ...args),
warn: (...args) => VENDOR_EDIT_LOG_LEVEL >= 2 && console.warn('⚠️ [VENDOR_EDIT WARN]', ...args),
info: (...args) => VENDOR_EDIT_LOG_LEVEL >= 3 && console.info(' [VENDOR_EDIT INFO]', ...args),
debug: (...args) => VENDOR_EDIT_LOG_LEVEL >= 4 && console.log('🔍 [VENDOR_EDIT DEBUG]', ...args)
};
function adminVendorEdit() {
return {
currentUser: {},
vendor: {},
// Inherit base layout functionality from init-alpine.js
...data(),
// Vendor edit page specific state
currentPage: 'vendor-edit',
vendor: null,
formData: {},
errors: {},
loadingVendor: true,
loadingVendor: false,
saving: false,
vendorId: null,
vendorCode: null,
// Confirmation modal
confirmModal: {
show: false,
title: '',
message: '',
warning: '',
buttonText: '',
buttonClass: 'btn-primary',
onConfirm: () => {},
onCancel: null
},
// Initialize
async init() {
editLog.info('=== VENDOR EDIT PAGE INITIALIZING ===');
// Success modal
successModal: {
show: false,
title: '',
message: '',
details: null,
note: ''
},
// Transfer ownership
showTransferOwnership: false,
transferring: false,
transferData: {
new_owner_user_id: null,
transfer_reason: '',
confirm_transfer: false
},
init() {
console.log('=== Vendor Edit Initialization ===');
// Check authentication
if (!Auth.isAuthenticated() || !Auth.isAdmin()) {
console.log('Not authenticated as admin, redirecting to login');
window.location.href = '/admin/login';
// Prevent multiple initializations
if (window._vendorEditInitialized) {
editLog.warn('Vendor edit page already initialized, skipping...');
return;
}
window._vendorEditInitialized = true;
this.currentUser = Auth.getCurrentUser();
console.log('Current user:', this.currentUser.username);
// Get vendor code from URL
const path = window.location.pathname;
const match = path.match(/\/admin\/vendors\/([^\/]+)\/edit/);
// Get vendor ID from URL
const urlParams = new URLSearchParams(window.location.search);
this.vendorId = urlParams.get('id');
if (!this.vendorId) {
console.error('No vendor ID in URL');
alert('No vendor ID provided');
window.location.href = '/admin/dashboard.html#vendors';
return;
if (match) {
this.vendorCode = match[1];
editLog.info('Editing vendor:', this.vendorCode);
await this.loadVendor();
} else {
editLog.error('No vendor code in URL');
Utils.showToast('Invalid vendor URL', 'error');
setTimeout(() => window.location.href = '/admin/vendors', 2000);
}
console.log('Vendor ID:', this.vendorId);
// Load vendor details
this.loadVendor();
editLog.info('=== VENDOR EDIT PAGE INITIALIZATION COMPLETE ===');
},
// Load vendor data
async loadVendor() {
editLog.info('Loading vendor data...');
this.loadingVendor = true;
try {
console.log('Loading vendor with ID:', this.vendorId);
this.vendor = await apiClient.get(`/admin/vendors/${this.vendorId}`);
console.log('✅ Vendor loaded:', this.vendor.vendor_code);
console.log('Owner email:', this.vendor.owner_email);
console.log('Contact email:', this.vendor.contact_email);
// Populate form data
try {
const startTime = Date.now();
const response = await apiClient.get(`/admin/vendors/${this.vendorCode}`);
const duration = Date.now() - startTime;
this.vendor = response;
// Initialize form data
this.formData = {
name: this.vendor.name,
subdomain: this.vendor.subdomain,
description: this.vendor.description || '',
contact_email: this.vendor.contact_email || '',
contact_phone: this.vendor.contact_phone || '',
website: this.vendor.website || '',
business_address: this.vendor.business_address || '',
tax_number: this.vendor.tax_number || ''
name: response.name || '',
subdomain: response.subdomain || '',
description: response.description || '',
contact_email: response.contact_email || '',
contact_phone: response.contact_phone || '',
website: response.website || '',
business_address: response.business_address || '',
tax_number: response.tax_number || ''
};
console.log('Form data populated');
editLog.info(`Vendor loaded in ${duration}ms`, {
vendor_code: this.vendor.vendor_code,
name: this.vendor.name
});
editLog.debug('Form data initialized:', this.formData);
} catch (error) {
console.error('Failed to load vendor:', error);
Utils.showToast('Failed to load vendor details: ' + (error.message || 'Unknown error'), 'error');
window.location.href = '/admin/dashboard';
editLog.error('Failed to load vendor:', error);
Utils.showToast('Failed to load vendor', 'error');
setTimeout(() => window.location.href = '/admin/vendors', 2000);
} finally {
this.loadingVendor = false;
}
},
// Format subdomain
formatSubdomain() {
this.formData.subdomain = this.formData.subdomain
.toLowerCase()
.replace(/[^a-z0-9-]/g, '');
editLog.debug('Subdomain formatted:', this.formData.subdomain);
},
// Submit form
async handleSubmit() {
console.log('Submitting vendor update...');
editLog.info('=== SUBMITTING VENDOR UPDATE ===');
editLog.debug('Form data:', this.formData);
this.errors = {};
this.saving = true;
try {
const updatedVendor = await apiClient.put(
`/admin/vendors/${this.vendorId}`,
const startTime = Date.now();
const response = await apiClient.put(
`/admin/vendors/${this.vendorCode}`,
this.formData
);
const duration = Date.now() - startTime;
console.log('✅ Vendor updated successfully');
Utils.showToast('Vendor updated successfully!', 'success');
this.vendor = updatedVendor;
this.vendor = response;
Utils.showToast('Vendor updated successfully', 'success');
editLog.info(`Vendor updated successfully in ${duration}ms`, response);
// Optionally redirect back to list
// setTimeout(() => window.location.href = '/admin/vendors', 1500);
// Refresh form data with latest values
this.formData.name = updatedVendor.name;
this.formData.subdomain = updatedVendor.subdomain;
this.formData.contact_email = updatedVendor.contact_email;
} catch (error) {
console.error('Failed to update vendor:', error);
editLog.error('Failed to update vendor:', error);
// Handle validation errors
if (error.details && error.details.validation_errors) {
error.details.validation_errors.forEach(err => {
const field = err.loc?.[1] || err.loc?.[0];
if (field) {
this.errors[field] = err.msg;
}
});
editLog.debug('Validation errors:', this.errors);
}
Utils.showToast(error.message || 'Failed to update vendor', 'error');
} finally {
this.saving = false;
editLog.info('=== VENDOR UPDATE COMPLETE ===');
}
},
showVerificationModal() {
const action = this.vendor.is_verified ? 'unverify' : 'verify';
const actionCap = this.vendor.is_verified ? 'Unverify' : 'Verify';
this.confirmModal = {
show: true,
title: `${actionCap} Vendor`,
message: `Are you sure you want to ${action} this vendor?`,
warning: this.vendor.is_verified
? 'Unverifying this vendor will prevent them from being publicly visible and may affect their operations.'
: 'Verifying this vendor will make them publicly visible and allow them to operate fully.',
buttonText: actionCap,
buttonClass: this.vendor.is_verified ? 'btn-warning' : 'btn-success',
onConfirm: () => this.toggleVerification(),
onCancel: null
};
},
// Toggle verification
async toggleVerification() {
const action = this.vendor.is_verified ? 'unverify' : 'verify';
console.log(`Toggling verification: ${action}`);
editLog.info(`Toggle verification: ${action}`);
if (!confirm(`Are you sure you want to ${action} this vendor?`)) {
editLog.info('Verification toggle cancelled by user');
return;
}
this.saving = true;
try {
const result = await apiClient.put(`/admin/vendors/${this.vendorId}/verify`);
this.vendor.is_verified = result.vendor.is_verified;
console.log('✅ Verification toggled');
Utils.showToast(result.message, 'success');
const response = await apiClient.put(
`/admin/vendors/${this.vendorCode}/verification`,
{ is_verified: !this.vendor.is_verified }
);
this.vendor = response;
Utils.showToast(`Vendor ${action}ed successfully`, 'success');
editLog.info(`Vendor ${action}ed successfully`);
} catch (error) {
console.error('❌ Failed to toggle verification:', error);
Utils.showToast('Failed to update verification status', 'error');
editLog.error(`Failed to ${action} vendor:`, error);
Utils.showToast(`Failed to ${action} vendor`, 'error');
} finally {
this.saving = false;
}
},
showStatusModal() {
// Toggle active status
async toggleActive() {
const action = this.vendor.is_active ? 'deactivate' : 'activate';
const actionCap = this.vendor.is_active ? 'Deactivate' : 'Activate';
editLog.info(`Toggle active status: ${action}`);
this.confirmModal = {
show: true,
title: `${actionCap} Vendor`,
message: `Are you sure you want to ${action} this vendor?`,
warning: this.vendor.is_active
? 'Deactivating this vendor will immediately suspend all their operations and make them invisible to customers.'
: 'Activating this vendor will restore their operations and make them visible again.',
buttonText: actionCap,
buttonClass: this.vendor.is_active ? 'btn-danger' : 'btn-success',
onConfirm: () => this.toggleStatus(),
onCancel: null
};
},
async toggleStatus() {
const action = this.vendor.is_active ? 'deactivate' : 'activate';
console.log(`Toggling status: ${action}`);
if (!confirm(`Are you sure you want to ${action} this vendor?\n\nThis will affect their operations.`)) {
editLog.info('Active status toggle cancelled by user');
return;
}
this.saving = true;
try {
const result = await apiClient.put(`/admin/vendors/${this.vendorId}/status`);
this.vendor.is_active = result.vendor.is_active;
console.log('✅ Status toggled');
Utils.showToast(result.message, 'success');
const response = await apiClient.put(
`/admin/vendors/${this.vendorCode}/status`,
{ is_active: !this.vendor.is_active }
);
this.vendor = response;
Utils.showToast(`Vendor ${action}d successfully`, 'success');
editLog.info(`Vendor ${action}d successfully`);
} catch (error) {
console.error('❌ Failed to toggle status:', error);
Utils.showToast('Failed to update vendor status', 'error');
editLog.error(`Failed to ${action} vendor:`, error);
Utils.showToast(`Failed to ${action} vendor`, 'error');
} finally {
this.saving = false;
}
},
async handleTransferOwnership() {
// Validate inputs
if (!this.transferData.confirm_transfer) {
Utils.showToast('Please confirm the ownership transfer', 'error');
return;
}
if (!this.transferData.new_owner_user_id) {
Utils.showToast('Please enter the new owner user ID', 'error');
return;
}
// Close the transfer modal first
this.showTransferOwnership = false;
// Wait a moment for modal to close
await new Promise(resolve => setTimeout(resolve, 300));
// Show final confirmation modal
this.confirmModal = {
show: true,
title: '⚠️ FINAL CONFIRMATION: Transfer Ownership',
message: `You are about to transfer ownership of "${this.vendor.name}" to user ID ${this.transferData.new_owner_user_id}.`,
warning: `Current Owner: ${this.vendor.owner_username} (${this.vendor.owner_email})\n\n` +
`This action will:\n` +
`• Assign full ownership rights to the new user\n` +
`• Demote the current owner to Manager role\n` +
`• Be permanently logged for audit purposes\n\n` +
`This action cannot be easily undone. Are you absolutely sure?`,
buttonText: '🔄 Yes, Transfer Ownership',
buttonClass: 'btn-danger',
onConfirm: () => this.executeTransferOwnership(),
onCancel: () => {
// If cancelled, reopen the transfer modal with preserved data
this.showTransferOwnership = true;
}
};
},
async executeTransferOwnership() {
console.log('Transferring ownership to user:', this.transferData.new_owner_user_id);
this.transferring = true;
this.saving = true;
try {
const result = await apiClient.post(
`/admin/vendors/${this.vendorId}/transfer-ownership`,
this.transferData
);
console.log('✅ Ownership transferred successfully');
// Show beautiful success modal
this.successModal = {
show: true,
title: 'Ownership Transfer Complete',
message: `The ownership of "${this.vendor.name}" has been successfully transferred.`,
details: {
oldOwner: {
username: result.old_owner.username,
email: result.old_owner.email
},
newOwner: {
username: result.new_owner.username,
email: result.new_owner.email
}
},
note: 'The transfer has been logged for audit purposes. The previous owner has been assigned the Manager role.'
};
Utils.showToast('Ownership transferred successfully', 'success');
// Reload vendor data to reflect new owner
await this.loadVendor();
// Reset transfer form data
this.transferData = {
new_owner_user_id: null,
transfer_reason: '',
confirm_transfer: false
};
} catch (error) {
console.error('❌ Failed to transfer ownership:', error);
const errorMsg = error.message || error.detail || 'Unknown error';
Utils.showToast(`Transfer failed: ${errorMsg}`, 'error');
// Show error in modal format (reuse success modal structure)
alert(`❌ Transfer Failed\n\n${errorMsg}\n\nPlease check the user ID and try again.`);
// Reopen transfer modal so user can try again
this.showTransferOwnership = true;
} finally {
this.transferring = false;
this.saving = false;
}
},
async handleLogout() {
// Show confirmation modal for logout
this.confirmModal = {
show: true,
title: '🚪 Confirm Logout',
message: 'Are you sure you want to logout from the Admin Portal?',
warning: 'You will need to login again to access the admin dashboard.',
buttonText: 'Yes, Logout',
buttonClass: 'btn-danger',
onConfirm: () => this.executeLogout(),
onCancel: null
}
};
},
}
async executeLogout() {
console.log('Logging out...');
// Show loading state briefly
this.saving = true;
// Clear authentication
Auth.logout();
// Show success message
Utils.showToast('Logged out successfully', 'success', 1000);
// Redirect to login after brief delay
setTimeout(() => {
window.location.href = '/admin/login';
}, 500);
},
};
}
editLog.info('Vendor edit module loaded');

View File

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