Files
PowderCoatingLogix/src/PowderCoating.Web/wwwroot/js/toast-notifications.js
T
spouliot 5631d1d57a Add WarningPermanent toast type and upgrade invoice failure notifications
Email delivery failures and PDF generation errors now show a permanent
warning/error toast that requires manual dismissal, so users cannot
accidentally miss critical action-blocking feedback.

- ToastHelper: WarningPermanent TempData key + Warning/WarningPermanent
  extension methods on both ITempDataDictionary and Controller
- SetNotificationResultToast: NotificationStatus.Failed now uses
  ToastWarningPermanent (previously auto-dismissed in 5 s)
- InvoicesController.Send: TempData["Warning"] → TempData["WarningPermanent"]
  when PDF generation or email dispatch fails
- InvoicesController.DownloadPdf: TempData["Error"] → TempData["ErrorPermanent"]
  with the actual exception message so root cause is visible
- _Layout.cshtml: WarningPermanent hidden div
- toast-notifications.js: WarningPermanent handler (timeOut: 0)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 16:02:16 -04:00

181 lines
5.3 KiB
JavaScript

/**
* Toast Notification System
* Provides consistent, user-friendly notifications across the application
* Uses Toastr library with Bootstrap 5 theming
*/
// Configure Toastr global options
toastr.options = {
"closeButton": true,
"debug": false,
"newestOnTop": true,
"progressBar": true,
"positionClass": "toast-top-right",
"preventDuplicates": true,
"onclick": null,
"showDuration": "300",
"hideDuration": "1000",
"timeOut": "5000",
"extendedTimeOut": "1000",
"showEasing": "swing",
"hideEasing": "linear",
"showMethod": "fadeIn",
"hideMethod": "fadeOut"
};
/**
* Show success toast notification
* @param {string} message - The success message to display
* @param {string} title - Optional title for the toast
*/
function showSuccess(message, title = 'Success') {
toastr.success(message, title);
}
/**
* Show error toast notification
* @param {string} message - The error message to display
* @param {string} title - Optional title for the toast
*/
function showError(message, title = 'Error') {
toastr.error(message, title);
}
/**
* Show warning toast notification
* @param {string} message - The warning message to display
* @param {string} title - Optional title for the toast
*/
function showWarning(message, title = 'Warning') {
toastr.warning(message, title);
}
/**
* Show info toast notification
* @param {string} message - The info message to display
* @param {string} title - Optional title for the toast
*/
function showInfo(message, title = 'Info') {
toastr.info(message, title);
}
/**
* Show validation errors from ModelState
* @param {Array<string>} errors - Array of validation error messages
*/
function showValidationErrors(errors) {
if (!errors || errors.length === 0) return;
// If single error, show it directly
if (errors.length === 1) {
showError(errors[0], 'Validation Error');
return;
}
// Multiple errors - show as a list
const errorList = '<ul class="mb-0 ps-3">' +
errors.map(err => `<li>${escapeHtml(err)}</li>`).join('') +
'</ul>';
toastr.error(errorList, `${errors.length} Validation Errors`, {
timeOut: 8000,
extendedTimeOut: 2000
});
}
/**
* Display TempData messages automatically on page load
* Expects TempData keys: Success, Error, Warning, Info
*/
function displayTempDataMessages() {
// Success message
const successMsg = document.getElementById('tempdata-success-message');
if (successMsg && successMsg.textContent.trim()) {
showSuccess(successMsg.textContent.trim());
}
// Error message
const errorMsg = document.getElementById('tempdata-error-message');
if (errorMsg && errorMsg.textContent.trim()) {
showError(errorMsg.textContent.trim());
}
// Permanent success — no auto-dismiss
const successPerm = document.getElementById('tempdata-success-permanent-message');
if (successPerm && successPerm.textContent.trim()) {
toastr.success(successPerm.textContent.trim(), 'Success', { timeOut: 0, extendedTimeOut: 0 });
}
// Permanent error — no auto-dismiss
const errorPerm = document.getElementById('tempdata-error-permanent-message');
if (errorPerm && errorPerm.textContent.trim()) {
toastr.error(errorPerm.textContent.trim(), 'Error', { timeOut: 0, extendedTimeOut: 0 });
}
// Warning message
const warningMsg = document.getElementById('tempdata-warning-message');
if (warningMsg && warningMsg.textContent.trim()) {
showWarning(warningMsg.textContent.trim());
}
// Permanent warning — no auto-dismiss
const warningPerm = document.getElementById('tempdata-warning-permanent-message');
if (warningPerm && warningPerm.textContent.trim()) {
toastr.warning(warningPerm.textContent.trim(), 'Warning', { timeOut: 0, extendedTimeOut: 0 });
}
// Info message
const infoMsg = document.getElementById('tempdata-info-message');
if (infoMsg && infoMsg.textContent.trim()) {
showInfo(infoMsg.textContent.trim());
}
}
/**
* Display ModelState validation errors on page load
* Expects a hidden div with id 'modelstate-errors' containing JSON array of errors
*/
function displayModelStateErrors() {
const errorContainer = document.getElementById('modelstate-errors');
if (errorContainer && errorContainer.textContent.trim()) {
try {
const errors = JSON.parse(errorContainer.textContent);
showValidationErrors(errors);
} catch (e) {
console.error('Failed to parse ModelState errors:', e);
}
}
}
/**
* Escape HTML to prevent XSS
* @param {string} text - Text to escape
* @returns {string} - Escaped text
*/
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* Clear all toasts
*/
function clearAllToasts() {
toastr.clear();
}
// Auto-initialize on page load
document.addEventListener('DOMContentLoaded', function() {
displayTempDataMessages();
displayModelStateErrors();
});
// Make functions globally available
window.showSuccess = showSuccess;
window.showError = showError;
window.showWarning = showWarning;
window.showInfo = showInfo;
window.showValidationErrors = showValidationErrors;
window.clearAllToasts = clearAllToasts;