Files
orion/static/vendor/js/notifications.js
Samir Boulahtit 34d115dc58 fix: remove vendorCode from vendor API paths
Vendor API endpoints use JWT authentication, not URL path parameters.
The vendorCode should only be used for page URLs (navigation), not API calls.

Fixed API paths in 10 vendor JS files:
- analytics.js, customers.js, inventory.js, notifications.js
- order-detail.js, orders.js, products.js, profile.js
- settings.js, team.js

Added architecture rule JS-014 to prevent this pattern from recurring.
Added validation check _check_vendor_api_paths to validate_architecture.py.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 21:49:24 +01:00

281 lines
9.3 KiB
JavaScript

// static/vendor/js/notifications.js
/**
* Vendor notifications center page logic
* View and manage notifications
*/
const vendorNotificationsLog = window.LogConfig.loggers.vendorNotifications ||
window.LogConfig.createLogger('vendorNotifications', false);
vendorNotificationsLog.info('Loading...');
function vendorNotifications() {
vendorNotificationsLog.info('vendorNotifications() called');
return {
// Inherit base layout state
...data(),
// Set page identifier
currentPage: 'notifications',
// Loading states
loading: true,
error: '',
loadingNotifications: false,
// Notifications data
notifications: [],
stats: {
total: 0,
unread_count: 0
},
// Pagination
page: 1,
limit: 20,
skip: 0,
// Filters
filters: {
priority: '',
is_read: ''
},
// Settings
settings: null,
showSettingsModal: false,
settingsForm: {
email_notifications: true,
in_app_notifications: true
},
async init() {
vendorNotificationsLog.info('Notifications init() called');
// Guard against multiple initialization
if (window._vendorNotificationsInitialized) {
vendorNotificationsLog.warn('Already initialized, skipping');
return;
}
window._vendorNotificationsInitialized = true;
// IMPORTANT: Call parent init first to set vendorCode from URL
const parentInit = data().init;
if (parentInit) {
await parentInit.call(this);
}
try {
await this.loadNotifications();
} catch (error) {
vendorNotificationsLog.error('Init failed:', error);
this.error = 'Failed to initialize notifications page';
} finally {
this.loading = false;
}
vendorNotificationsLog.info('Notifications initialization complete');
},
/**
* Load notifications with current filters
*/
async loadNotifications() {
this.loadingNotifications = true;
this.error = '';
try {
this.skip = (this.page - 1) * this.limit;
const params = new URLSearchParams();
params.append('skip', this.skip);
params.append('limit', this.limit);
if (this.filters.is_read === 'false') {
params.append('unread_only', 'true');
}
const response = await apiClient.get(`/vendor/notifications?${params}`);
this.notifications = response.notifications || [];
this.stats.total = response.total || 0;
this.stats.unread_count = response.unread_count || 0;
vendorNotificationsLog.info(`Loaded ${this.notifications.length} notifications`);
} catch (error) {
vendorNotificationsLog.error('Failed to load notifications:', error);
this.error = error.message || 'Failed to load notifications';
} finally {
this.loadingNotifications = false;
}
},
/**
* Mark notification as read
*/
async markAsRead(notification) {
try {
await apiClient.put(`/vendor/notifications/${notification.id}/read`);
// Update local state
notification.is_read = true;
this.stats.unread_count = Math.max(0, this.stats.unread_count - 1);
Utils.showToast('Notification marked as read', 'success');
} catch (error) {
vendorNotificationsLog.error('Failed to mark as read:', error);
Utils.showToast(error.message || 'Failed to mark notification as read', 'error');
}
},
/**
* Mark all notifications as read
*/
async markAllAsRead() {
try {
await apiClient.put(`/vendor/notifications/mark-all-read`);
// Update local state
this.notifications.forEach(n => n.is_read = true);
this.stats.unread_count = 0;
Utils.showToast('All notifications marked as read', 'success');
} catch (error) {
vendorNotificationsLog.error('Failed to mark all as read:', error);
Utils.showToast(error.message || 'Failed to mark all as read', 'error');
}
},
/**
* Delete notification
*/
async deleteNotification(notificationId) {
if (!confirm('Are you sure you want to delete this notification?')) {
return;
}
try {
await apiClient.delete(`/vendor/notifications/${notificationId}`);
// Remove from local state
const wasUnread = this.notifications.find(n => n.id === notificationId && !n.is_read);
this.notifications = this.notifications.filter(n => n.id !== notificationId);
this.stats.total = Math.max(0, this.stats.total - 1);
if (wasUnread) {
this.stats.unread_count = Math.max(0, this.stats.unread_count - 1);
}
Utils.showToast('Notification deleted', 'success');
} catch (error) {
vendorNotificationsLog.error('Failed to delete notification:', error);
Utils.showToast(error.message || 'Failed to delete notification', 'error');
}
},
/**
* Open settings modal
*/
async openSettingsModal() {
try {
const response = await apiClient.get(`/vendor/notifications/settings`);
this.settingsForm = {
email_notifications: response.email_notifications !== false,
in_app_notifications: response.in_app_notifications !== false
};
this.showSettingsModal = true;
} catch (error) {
vendorNotificationsLog.error('Failed to load settings:', error);
Utils.showToast(error.message || 'Failed to load notification settings', 'error');
}
},
/**
* Save notification settings
*/
async saveSettings() {
try {
await apiClient.put(`/vendor/notifications/settings`, this.settingsForm);
Utils.showToast('Notification settings saved', 'success');
this.showSettingsModal = false;
} catch (error) {
vendorNotificationsLog.error('Failed to save settings:', error);
Utils.showToast(error.message || 'Failed to save settings', 'error');
}
},
/**
* Get notification icon based on type
*/
getNotificationIcon(type) {
const icons = {
'order_received': 'shopping-cart',
'order_shipped': 'truck',
'order_delivered': 'check-circle',
'low_stock': 'exclamation-triangle',
'import_complete': 'cloud-download',
'import_failed': 'x-circle',
'team_invite': 'user-plus',
'payment_received': 'credit-card',
'system': 'cog'
};
return icons[type] || 'bell';
},
/**
* Get priority color class
*/
getPriorityClass(priority) {
const classes = {
'critical': 'bg-red-100 text-red-600 dark:bg-red-900 dark:text-red-300',
'high': 'bg-orange-100 text-orange-600 dark:bg-orange-900 dark:text-orange-300',
'normal': 'bg-blue-100 text-blue-600 dark:bg-blue-900 dark:text-blue-300',
'low': 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300'
};
return classes[priority] || classes['normal'];
},
/**
* Format date for display
*/
formatDate(dateString) {
if (!dateString) return '-';
const date = new Date(dateString);
const now = new Date();
const diff = Math.floor((now - date) / 1000);
// Show relative time for recent dates
if (diff < 60) return 'Just now';
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
if (diff < 172800) return 'Yesterday';
// Show full date for older dates
return date.toLocaleDateString();
},
// Pagination methods
get totalPages() {
return Math.ceil(this.stats.total / this.limit);
},
prevPage() {
if (this.page > 1) {
this.page--;
this.loadNotifications();
}
},
nextPage() {
if (this.page < this.totalPages) {
this.page++;
this.loadNotifications();
}
},
goToPage(pageNum) {
this.page = pageNum;
this.loadNotifications();
}
};
}