add notifiaction handling, error handling
This commit is contained in:
parent
ce708f5209
commit
1af288aa62
21 changed files with 4864 additions and 224 deletions
699
frontend/documentation/stores/errors.md
Normal file
699
frontend/documentation/stores/errors.md
Normal file
|
|
@ -0,0 +1,699 @@
|
|||
# Errors Store
|
||||
|
||||
The errors store provides comprehensive error handling and management for the entire application. It centralizes error tracking, automatic retry logic, and integration with the notification system to provide users with consistent error feedback.
|
||||
|
||||
## Overview
|
||||
|
||||
- **Location**: `src/stores/errors.js`
|
||||
- **Type**: Pinia Store
|
||||
- **Purpose**: Centralized error state management and handling
|
||||
- **Integration**: Works with notification store for user feedback
|
||||
|
||||
## Installation & Setup
|
||||
|
||||
```javascript
|
||||
// Import in your component
|
||||
import { useErrorStore } from "@/stores/errors";
|
||||
|
||||
// Use in component
|
||||
const errorStore = useErrorStore();
|
||||
```
|
||||
|
||||
## State Structure
|
||||
|
||||
### Core State Properties
|
||||
|
||||
```javascript
|
||||
state: {
|
||||
hasError: false, // Global error flag
|
||||
lastError: null, // Most recent global error
|
||||
apiErrors: new Map(), // API-specific errors by key
|
||||
componentErrors: { // Component-specific errors
|
||||
dataTable: null,
|
||||
form: null,
|
||||
clients: null,
|
||||
jobs: null,
|
||||
timesheets: null,
|
||||
warranties: null,
|
||||
routes: null
|
||||
},
|
||||
errorHistory: [], // Historical error log
|
||||
maxHistorySize: 50, // Maximum history entries
|
||||
autoNotifyErrors: true // Auto-show notifications for errors
|
||||
}
|
||||
```
|
||||
|
||||
### Error Object Structure
|
||||
|
||||
```javascript
|
||||
{
|
||||
message: 'Error description', // Human-readable error message
|
||||
type: 'api_error', // Error classification
|
||||
timestamp: '2025-11-12T10:30:00Z', // When error occurred
|
||||
name: 'ValidationError', // Error name (if available)
|
||||
stack: 'Error stack trace...', // Stack trace (if available)
|
||||
status: 404, // HTTP status (for API errors)
|
||||
statusText: 'Not Found', // HTTP status text
|
||||
data: {...} // Additional error data
|
||||
}
|
||||
```
|
||||
|
||||
## Getters
|
||||
|
||||
### `hasAnyError`
|
||||
|
||||
Check if there are any errors in the application.
|
||||
|
||||
```javascript
|
||||
const hasErrors = errorStore.hasAnyError;
|
||||
```
|
||||
|
||||
### `getComponentError(componentName)`
|
||||
|
||||
Get error for a specific component.
|
||||
|
||||
```javascript
|
||||
const formError = errorStore.getComponentError("form");
|
||||
```
|
||||
|
||||
### `getApiError(apiKey)`
|
||||
|
||||
Get error for a specific API operation.
|
||||
|
||||
```javascript
|
||||
const loginError = errorStore.getApiError("user-login");
|
||||
```
|
||||
|
||||
### `getRecentErrors(limit)`
|
||||
|
||||
Get recent errors from history.
|
||||
|
||||
```javascript
|
||||
const recentErrors = errorStore.getRecentErrors(10);
|
||||
```
|
||||
|
||||
## Actions
|
||||
|
||||
### Global Error Management
|
||||
|
||||
#### `setGlobalError(error, showNotification)`
|
||||
|
||||
Set a global application error.
|
||||
|
||||
```javascript
|
||||
errorStore.setGlobalError(
|
||||
new Error("Critical system failure"),
|
||||
true, // Show notification
|
||||
);
|
||||
|
||||
// With custom error object
|
||||
errorStore.setGlobalError({
|
||||
message: "Database connection lost",
|
||||
type: "connection_error",
|
||||
recoverable: true,
|
||||
});
|
||||
```
|
||||
|
||||
#### `clearGlobalError()`
|
||||
|
||||
Clear the global error state.
|
||||
|
||||
```javascript
|
||||
errorStore.clearGlobalError();
|
||||
```
|
||||
|
||||
### Component-Specific Error Management
|
||||
|
||||
#### `setComponentError(componentName, error, showNotification)`
|
||||
|
||||
Set an error for a specific component.
|
||||
|
||||
```javascript
|
||||
// Set error for form component
|
||||
errorStore.setComponentError("form", new Error("Validation failed"), true);
|
||||
|
||||
// Clear error (pass null)
|
||||
errorStore.setComponentError("form", null);
|
||||
```
|
||||
|
||||
#### `clearComponentError(componentName)`
|
||||
|
||||
Clear error for a specific component.
|
||||
|
||||
```javascript
|
||||
errorStore.clearComponentError("form");
|
||||
```
|
||||
|
||||
### API Error Management
|
||||
|
||||
#### `setApiError(apiKey, error, showNotification)`
|
||||
|
||||
Set an error for a specific API operation.
|
||||
|
||||
```javascript
|
||||
// Set API error
|
||||
errorStore.setApiError("user-login", apiError, true);
|
||||
|
||||
// Clear API error (pass null)
|
||||
errorStore.setApiError("user-login", null);
|
||||
```
|
||||
|
||||
#### `clearApiError(apiKey)`
|
||||
|
||||
Clear error for a specific API operation.
|
||||
|
||||
```javascript
|
||||
errorStore.clearApiError("user-login");
|
||||
```
|
||||
|
||||
### Bulk Operations
|
||||
|
||||
#### `clearAllErrors()`
|
||||
|
||||
Clear all errors from the store.
|
||||
|
||||
```javascript
|
||||
errorStore.clearAllErrors();
|
||||
```
|
||||
|
||||
### Advanced Error Handling
|
||||
|
||||
#### `handleApiCall(apiKey, apiFunction, options)`
|
||||
|
||||
Handle an API call with automatic error management and retry logic.
|
||||
|
||||
```javascript
|
||||
const result = await errorStore.handleApiCall(
|
||||
"fetch-users",
|
||||
async () => {
|
||||
return await api.getUsers();
|
||||
},
|
||||
{
|
||||
showNotification: true,
|
||||
retryCount: 2,
|
||||
retryDelay: 1000,
|
||||
onSuccess: (result) => console.log("Success:", result),
|
||||
onError: (error) => console.log("Failed:", error),
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
#### `withErrorHandling(operationKey, asyncOperation, options)`
|
||||
|
||||
Wrap an async operation with comprehensive error handling.
|
||||
|
||||
```javascript
|
||||
const result = await errorStore.withErrorHandling(
|
||||
"save-data",
|
||||
async () => {
|
||||
return await saveUserData();
|
||||
},
|
||||
{
|
||||
componentName: "userForm",
|
||||
showNotification: true,
|
||||
rethrow: false, // Don't re-throw errors
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## Error Types
|
||||
|
||||
The store automatically categorizes errors into different types:
|
||||
|
||||
### `string_error`
|
||||
|
||||
Simple string errors.
|
||||
|
||||
```javascript
|
||||
errorStore.setGlobalError("Something went wrong");
|
||||
```
|
||||
|
||||
### `javascript_error`
|
||||
|
||||
Standard JavaScript Error objects.
|
||||
|
||||
```javascript
|
||||
errorStore.setGlobalError(new Error("Validation failed"));
|
||||
```
|
||||
|
||||
### `api_error`
|
||||
|
||||
HTTP/API response errors with status codes.
|
||||
|
||||
```javascript
|
||||
// Automatically detected from axios-style error objects
|
||||
{
|
||||
message: 'Not Found',
|
||||
status: 404,
|
||||
statusText: 'Not Found',
|
||||
type: 'api_error',
|
||||
data: {...}
|
||||
}
|
||||
```
|
||||
|
||||
### `network_error`
|
||||
|
||||
Network connectivity errors.
|
||||
|
||||
```javascript
|
||||
{
|
||||
message: 'Network error - please check your connection',
|
||||
type: 'network_error'
|
||||
}
|
||||
```
|
||||
|
||||
### `unknown_error`
|
||||
|
||||
Unrecognized error formats.
|
||||
|
||||
```javascript
|
||||
{
|
||||
message: 'An unknown error occurred',
|
||||
type: 'unknown_error',
|
||||
originalError: {...}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Methods
|
||||
|
||||
### `setAutoNotifyErrors(enabled)`
|
||||
|
||||
Enable/disable automatic error notifications.
|
||||
|
||||
```javascript
|
||||
errorStore.setAutoNotifyErrors(false); // Disable auto-notifications
|
||||
```
|
||||
|
||||
### `setMaxHistorySize(size)`
|
||||
|
||||
Set maximum number of errors to keep in history.
|
||||
|
||||
```javascript
|
||||
errorStore.setMaxHistorySize(100);
|
||||
```
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
### Basic Error Handling
|
||||
|
||||
```javascript
|
||||
const errorStore = useErrorStore();
|
||||
|
||||
// Simple error setting
|
||||
try {
|
||||
await riskyOperation();
|
||||
} catch (error) {
|
||||
errorStore.setGlobalError(error);
|
||||
}
|
||||
|
||||
// Component-specific error
|
||||
try {
|
||||
await validateForm();
|
||||
} catch (validationError) {
|
||||
errorStore.setComponentError("form", validationError);
|
||||
}
|
||||
```
|
||||
|
||||
### API Error Handling with Retry
|
||||
|
||||
```javascript
|
||||
// Automatic retry logic
|
||||
const userData = await errorStore.handleApiCall(
|
||||
"fetch-user-data",
|
||||
async () => {
|
||||
const response = await fetch("/api/users");
|
||||
if (!response.ok) throw new Error("Failed to fetch users");
|
||||
return response.json();
|
||||
},
|
||||
{
|
||||
retryCount: 3,
|
||||
retryDelay: 1000,
|
||||
showNotification: true,
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### Comprehensive Operation Wrapping
|
||||
|
||||
```javascript
|
||||
// Wrap complex operations
|
||||
const result = await errorStore.withErrorHandling(
|
||||
"complex-workflow",
|
||||
async () => {
|
||||
// Step 1: Validate data
|
||||
await validateInputData();
|
||||
|
||||
// Step 2: Save to database
|
||||
const saveResult = await saveToDatabase();
|
||||
|
||||
// Step 3: Send notification email
|
||||
await sendNotificationEmail();
|
||||
|
||||
return saveResult;
|
||||
},
|
||||
{
|
||||
componentName: "workflow",
|
||||
showNotification: true,
|
||||
rethrow: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (result) {
|
||||
// Success - result contains the return value
|
||||
console.log("Workflow completed:", result);
|
||||
} else {
|
||||
// Error occurred - check component error for details
|
||||
const workflowError = errorStore.getComponentError("workflow");
|
||||
console.log("Workflow failed:", workflowError?.message);
|
||||
}
|
||||
```
|
||||
|
||||
### Integration with Vue Components
|
||||
|
||||
```javascript
|
||||
// In a Vue component
|
||||
import { useErrorStore } from "@/stores/errors";
|
||||
import { useNotificationStore } from "@/stores/notifications";
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const errorStore = useErrorStore();
|
||||
const notificationStore = useNotificationStore();
|
||||
|
||||
const submitForm = async (formData) => {
|
||||
// Clear any previous errors
|
||||
errorStore.clearComponentError("form");
|
||||
|
||||
try {
|
||||
await errorStore.withErrorHandling(
|
||||
"form-submit",
|
||||
async () => {
|
||||
const result = await api.submitForm(formData);
|
||||
notificationStore.addSuccess("Form submitted successfully!");
|
||||
return result;
|
||||
},
|
||||
{
|
||||
componentName: "form",
|
||||
showNotification: true,
|
||||
},
|
||||
);
|
||||
|
||||
// Reset form on success
|
||||
resetForm();
|
||||
} catch (error) {
|
||||
// Error handling is automatic, but you can add custom logic here
|
||||
console.log("Form submission failed");
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
submitForm,
|
||||
formError: computed(() => errorStore.getComponentError("form")),
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Integration with Enhanced API
|
||||
|
||||
The errors store works seamlessly with the enhanced API wrapper:
|
||||
|
||||
```javascript
|
||||
import { ApiWithErrorHandling } from "@/api-enhanced";
|
||||
|
||||
// The enhanced API automatically uses the error store
|
||||
try {
|
||||
const clients = await ApiWithErrorHandling.getPaginatedClientDetails(
|
||||
pagination,
|
||||
filters,
|
||||
[],
|
||||
{
|
||||
componentName: "clients",
|
||||
retryCount: 2,
|
||||
showErrorNotifications: true,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
// Error is automatically handled by the error store
|
||||
// Check component error for details
|
||||
const clientError = errorStore.getComponentError("clients");
|
||||
}
|
||||
```
|
||||
|
||||
## Error Recovery Patterns
|
||||
|
||||
### Graceful Degradation
|
||||
|
||||
```javascript
|
||||
const loadCriticalData = async () => {
|
||||
let primaryData = null;
|
||||
let fallbackData = null;
|
||||
|
||||
// Try primary data source
|
||||
try {
|
||||
primaryData = await errorStore.withErrorHandling(
|
||||
"primary-data",
|
||||
() => api.getPrimaryData(),
|
||||
{ showNotification: false, rethrow: true },
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Primary data failed, trying fallback...");
|
||||
|
||||
// Try fallback data source
|
||||
try {
|
||||
fallbackData = await errorStore.withErrorHandling(
|
||||
"fallback-data",
|
||||
() => api.getFallbackData(),
|
||||
{ showNotification: false, rethrow: true },
|
||||
);
|
||||
|
||||
notificationStore.addWarning(
|
||||
"Using cached data due to connectivity issues",
|
||||
);
|
||||
} catch (fallbackError) {
|
||||
errorStore.setGlobalError("Unable to load data from any source");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return primaryData || fallbackData;
|
||||
};
|
||||
```
|
||||
|
||||
### User-Driven Error Recovery
|
||||
|
||||
```javascript
|
||||
const handleApiError = async (operation) => {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
// Show error with recovery options
|
||||
notificationStore.addNotification({
|
||||
type: "error",
|
||||
title: "Operation Failed",
|
||||
message: "Would you like to try again or continue with cached data?",
|
||||
persistent: true,
|
||||
actions: [
|
||||
{
|
||||
label: "Retry",
|
||||
variant: "primary",
|
||||
handler: () => handleApiError(operation), // Recursive retry
|
||||
},
|
||||
{
|
||||
label: "Use Cached Data",
|
||||
variant: "secondary",
|
||||
handler: () => loadCachedData(),
|
||||
},
|
||||
{
|
||||
label: "Cancel",
|
||||
variant: "secondary",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
throw error; // Let caller handle as needed
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Debugging & Monitoring
|
||||
|
||||
### Error History Access
|
||||
|
||||
```javascript
|
||||
// Get all error history for debugging
|
||||
const allErrors = errorStore.errorHistory;
|
||||
|
||||
// Get recent errors with details
|
||||
const recent = errorStore.getRecentErrors(5);
|
||||
recent.forEach((error) => {
|
||||
console.log(`[${error.timestamp}] ${error.source}: ${error.message}`);
|
||||
});
|
||||
|
||||
// Filter errors by type
|
||||
const apiErrors = errorStore.errorHistory.filter((e) => e.type === "api_error");
|
||||
```
|
||||
|
||||
### Error Reporting
|
||||
|
||||
```javascript
|
||||
// Send error reports to monitoring service
|
||||
const reportErrors = () => {
|
||||
const recentErrors = errorStore.getRecentErrors(10);
|
||||
|
||||
recentErrors.forEach((error) => {
|
||||
if (error.type === "api_error" && error.status >= 500) {
|
||||
// Report server errors
|
||||
analyticsService.reportError({
|
||||
message: error.message,
|
||||
url: window.location.href,
|
||||
userAgent: navigator.userAgent,
|
||||
timestamp: error.timestamp,
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Testing
|
||||
|
||||
```javascript
|
||||
import { setActivePinia, createPinia } from "pinia";
|
||||
import { useErrorStore } from "@/stores/errors";
|
||||
|
||||
describe("Errors Store", () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
it("sets and clears global errors", () => {
|
||||
const store = useErrorStore();
|
||||
|
||||
store.setGlobalError("Test error");
|
||||
expect(store.hasError).toBe(true);
|
||||
expect(store.lastError.message).toBe("Test error");
|
||||
|
||||
store.clearGlobalError();
|
||||
expect(store.hasError).toBe(false);
|
||||
});
|
||||
|
||||
it("handles component errors", () => {
|
||||
const store = useErrorStore();
|
||||
|
||||
store.setComponentError("form", new Error("Validation failed"));
|
||||
expect(store.getComponentError("form").message).toBe("Validation failed");
|
||||
|
||||
store.clearComponentError("form");
|
||||
expect(store.getComponentError("form")).toBeNull();
|
||||
});
|
||||
|
||||
it("tracks error history", () => {
|
||||
const store = useErrorStore();
|
||||
|
||||
store.setGlobalError("Error 1");
|
||||
store.setGlobalError("Error 2");
|
||||
|
||||
expect(store.errorHistory).toHaveLength(2);
|
||||
expect(store.getRecentErrors(1)[0].message).toBe("Error 2");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Integration Testing
|
||||
|
||||
```javascript
|
||||
// Test error handling with API calls
|
||||
it("handles API errors correctly", async () => {
|
||||
const store = useErrorStore();
|
||||
const mockApi = jest.fn().mockRejectedValue(new Error("API Error"));
|
||||
|
||||
const result = await store.withErrorHandling("test-api", mockApi, {
|
||||
componentName: "test",
|
||||
showNotification: false,
|
||||
rethrow: false,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(store.getComponentError("test").message).toBe("API Error");
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
|
||||
- ✅ Use component-specific errors for UI validation
|
||||
- ✅ Use API-specific errors for network operations
|
||||
- ✅ Enable auto-notifications for user-facing errors
|
||||
- ✅ Use retry logic for transient failures
|
||||
- ✅ Clear errors when operations succeed
|
||||
- ✅ Keep error messages user-friendly and actionable
|
||||
|
||||
### Don'ts
|
||||
|
||||
- ❌ Don't set global errors for minor validation issues
|
||||
- ❌ Don't ignore error context (component/API source)
|
||||
- ❌ Don't let error history grow indefinitely
|
||||
- ❌ Don't show technical stack traces to end users
|
||||
- ❌ Don't retry operations that will consistently fail
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- Error history is automatically trimmed to prevent memory leaks
|
||||
- Use component-specific errors to isolate issues
|
||||
- Clear errors promptly when no longer relevant
|
||||
- Consider disabling auto-notifications for high-frequency operations
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Form Validation Errors
|
||||
|
||||
```javascript
|
||||
const validateAndSubmit = async (formData) => {
|
||||
errorStore.clearComponentError("form");
|
||||
|
||||
try {
|
||||
await errorStore.withErrorHandling(
|
||||
"form-validation",
|
||||
async () => {
|
||||
validateFormData(formData);
|
||||
await submitForm(formData);
|
||||
},
|
||||
{
|
||||
componentName: "form",
|
||||
showNotification: true,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
// Validation errors are now stored in component error
|
||||
// and automatically displayed to user
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Background Task Monitoring
|
||||
|
||||
```javascript
|
||||
const monitorBackgroundTask = async (taskId) => {
|
||||
await errorStore.handleApiCall(
|
||||
`task-${taskId}`,
|
||||
async () => {
|
||||
const status = await api.getTaskStatus(taskId);
|
||||
|
||||
if (status.failed) {
|
||||
throw new Error(`Task failed: ${status.error}`);
|
||||
}
|
||||
|
||||
return status;
|
||||
},
|
||||
{
|
||||
retryCount: 5,
|
||||
retryDelay: 2000,
|
||||
showNotification: status.failed, // Only notify on final failure
|
||||
},
|
||||
);
|
||||
};
|
||||
```
|
||||
609
frontend/documentation/stores/notifications.md
Normal file
609
frontend/documentation/stores/notifications.md
Normal file
|
|
@ -0,0 +1,609 @@
|
|||
# Notifications Store
|
||||
|
||||
The notifications store provides centralized state management for application-wide notifications. It handles the creation, management, and lifecycle of toast-style notifications with support for multiple types, positions, and interactive actions.
|
||||
|
||||
## Overview
|
||||
|
||||
- **Location**: `src/stores/notifications.js`
|
||||
- **Type**: Pinia Store
|
||||
- **Purpose**: Global notification state management
|
||||
- **Integration**: Used by NotificationDisplay component
|
||||
|
||||
## Installation & Setup
|
||||
|
||||
```javascript
|
||||
// Import in your component
|
||||
import { useNotificationStore } from "@/stores/notifications";
|
||||
|
||||
// Use in component
|
||||
const notificationStore = useNotificationStore();
|
||||
```
|
||||
|
||||
## State Structure
|
||||
|
||||
### Core State Properties
|
||||
|
||||
```javascript
|
||||
state: {
|
||||
notifications: [], // Array of active notifications
|
||||
defaultDuration: 4000, // Default auto-dismiss time (ms)
|
||||
maxNotifications: 5, // Maximum concurrent notifications
|
||||
position: 'top-right', // Default position
|
||||
nextId: 1 // Auto-incrementing ID counter
|
||||
}
|
||||
```
|
||||
|
||||
### Notification Object Structure
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: 1, // Unique identifier
|
||||
type: 'success', // 'success' | 'error' | 'warning' | 'info'
|
||||
title: 'Operation Complete', // Notification title
|
||||
message: 'Data saved successfully', // Main message
|
||||
duration: 4000, // Auto-dismiss time (0 = no auto-dismiss)
|
||||
persistent: false, // If true, won't auto-dismiss
|
||||
actions: [], // Array of action buttons
|
||||
data: null, // Additional data for handlers
|
||||
timestamp: '2025-11-12T10:30:00Z', // Creation timestamp
|
||||
dismissed: false, // Whether notification is dismissed
|
||||
seen: false // Whether user has interacted with it
|
||||
}
|
||||
```
|
||||
|
||||
## Getters
|
||||
|
||||
### `getNotificationsByType(type)`
|
||||
|
||||
Get all notifications of a specific type.
|
||||
|
||||
```javascript
|
||||
const errorNotifications = notificationStore.getNotificationsByType("error");
|
||||
```
|
||||
|
||||
### `activeNotifications`
|
||||
|
||||
Get all non-dismissed notifications.
|
||||
|
||||
```javascript
|
||||
const active = notificationStore.activeNotifications;
|
||||
```
|
||||
|
||||
### `activeCount`
|
||||
|
||||
Get count of active notifications.
|
||||
|
||||
```javascript
|
||||
const count = notificationStore.activeCount;
|
||||
```
|
||||
|
||||
### `hasErrorNotifications`
|
||||
|
||||
Check if there are any active error notifications.
|
||||
|
||||
```javascript
|
||||
const hasErrors = notificationStore.hasErrorNotifications;
|
||||
```
|
||||
|
||||
### `hasSuccessNotifications`
|
||||
|
||||
Check if there are any active success notifications.
|
||||
|
||||
```javascript
|
||||
const hasSuccess = notificationStore.hasSuccessNotifications;
|
||||
```
|
||||
|
||||
## Actions
|
||||
|
||||
### Core Notification Methods
|
||||
|
||||
#### `addNotification(notification)`
|
||||
|
||||
Add a new notification with full configuration options.
|
||||
|
||||
```javascript
|
||||
const notificationId = notificationStore.addNotification({
|
||||
type: "warning",
|
||||
title: "Confirm Action",
|
||||
message: "This will permanently delete the item.",
|
||||
persistent: true,
|
||||
actions: [
|
||||
{
|
||||
label: "Delete",
|
||||
variant: "danger",
|
||||
handler: () => performDelete(),
|
||||
},
|
||||
{
|
||||
label: "Cancel",
|
||||
variant: "secondary",
|
||||
},
|
||||
],
|
||||
data: { itemId: 123 },
|
||||
});
|
||||
```
|
||||
|
||||
#### Convenience Methods
|
||||
|
||||
```javascript
|
||||
// Quick success notification
|
||||
notificationStore.addSuccess("Operation completed!");
|
||||
notificationStore.addSuccess("Custom message", "Custom Title", {
|
||||
duration: 6000,
|
||||
});
|
||||
|
||||
// Quick error notification
|
||||
notificationStore.addError("Something went wrong!");
|
||||
notificationStore.addError("Custom error", "Error Title", { persistent: true });
|
||||
|
||||
// Quick warning notification
|
||||
notificationStore.addWarning("Please confirm this action");
|
||||
|
||||
// Quick info notification
|
||||
notificationStore.addInfo("New feature available");
|
||||
```
|
||||
|
||||
### Notification Management
|
||||
|
||||
#### `dismissNotification(id)`
|
||||
|
||||
Mark a notification as dismissed (hides it but keeps in history).
|
||||
|
||||
```javascript
|
||||
notificationStore.dismissNotification(notificationId);
|
||||
```
|
||||
|
||||
#### `removeNotification(id)`
|
||||
|
||||
Completely remove a notification from the store.
|
||||
|
||||
```javascript
|
||||
notificationStore.removeNotification(notificationId);
|
||||
```
|
||||
|
||||
#### `markAsSeen(id)`
|
||||
|
||||
Mark a notification as seen (user has interacted with it).
|
||||
|
||||
```javascript
|
||||
notificationStore.markAsSeen(notificationId);
|
||||
```
|
||||
|
||||
#### `updateNotification(id, updates)`
|
||||
|
||||
Update an existing notification's properties.
|
||||
|
||||
```javascript
|
||||
notificationStore.updateNotification(notificationId, {
|
||||
type: "success",
|
||||
message: "Updated message",
|
||||
persistent: false,
|
||||
});
|
||||
```
|
||||
|
||||
### Bulk Operations
|
||||
|
||||
#### `clearType(type)`
|
||||
|
||||
Remove all notifications of a specific type.
|
||||
|
||||
```javascript
|
||||
notificationStore.clearType("error"); // Remove all error notifications
|
||||
```
|
||||
|
||||
#### `clearAll()`
|
||||
|
||||
Remove all notifications.
|
||||
|
||||
```javascript
|
||||
notificationStore.clearAll();
|
||||
```
|
||||
|
||||
#### `clearDismissed()`
|
||||
|
||||
Remove all dismissed notifications from history.
|
||||
|
||||
```javascript
|
||||
notificationStore.clearDismissed();
|
||||
```
|
||||
|
||||
### Loading Notifications
|
||||
|
||||
#### `showLoadingNotification(message, title)`
|
||||
|
||||
Show a persistent loading notification that can be updated later.
|
||||
|
||||
```javascript
|
||||
const loadingId = notificationStore.showLoadingNotification(
|
||||
"Uploading file...",
|
||||
"Please Wait",
|
||||
);
|
||||
```
|
||||
|
||||
#### `updateToSuccess(id, message, title)`
|
||||
|
||||
Update a loading notification to success and auto-dismiss.
|
||||
|
||||
```javascript
|
||||
notificationStore.updateToSuccess(
|
||||
loadingId,
|
||||
"File uploaded successfully!",
|
||||
"Upload Complete",
|
||||
);
|
||||
```
|
||||
|
||||
#### `updateToError(id, message, title)`
|
||||
|
||||
Update a loading notification to error and auto-dismiss.
|
||||
|
||||
```javascript
|
||||
notificationStore.updateToError(
|
||||
loadingId,
|
||||
"Upload failed. Please try again.",
|
||||
"Upload Failed",
|
||||
);
|
||||
```
|
||||
|
||||
### API Integration Helpers
|
||||
|
||||
#### `showApiSuccess(operation, customMessage)`
|
||||
|
||||
Show standardized success notifications for API operations.
|
||||
|
||||
```javascript
|
||||
// Uses default messages based on operation
|
||||
notificationStore.showApiSuccess("create"); // "Item created successfully"
|
||||
notificationStore.showApiSuccess("update"); // "Item updated successfully"
|
||||
notificationStore.showApiSuccess("delete"); // "Item deleted successfully"
|
||||
notificationStore.showApiSuccess("fetch"); // "Data loaded successfully"
|
||||
|
||||
// With custom message
|
||||
notificationStore.showApiSuccess("create", "New client added successfully!");
|
||||
```
|
||||
|
||||
#### `showApiError(operation, error, customMessage)`
|
||||
|
||||
Show standardized error notifications for API operations.
|
||||
|
||||
```javascript
|
||||
// Automatically extracts error message from different error formats
|
||||
notificationStore.showApiError("create", apiError);
|
||||
notificationStore.showApiError("update", "Network timeout occurred");
|
||||
notificationStore.showApiError("delete", errorObject, "Custom error message");
|
||||
```
|
||||
|
||||
### Configuration Methods
|
||||
|
||||
#### `setPosition(position)`
|
||||
|
||||
Set the global notification position.
|
||||
|
||||
```javascript
|
||||
// Available positions:
|
||||
notificationStore.setPosition("top-right"); // Default
|
||||
notificationStore.setPosition("top-left");
|
||||
notificationStore.setPosition("top-center");
|
||||
notificationStore.setPosition("bottom-right");
|
||||
notificationStore.setPosition("bottom-left");
|
||||
notificationStore.setPosition("bottom-center");
|
||||
```
|
||||
|
||||
#### `setDefaultDuration(duration)`
|
||||
|
||||
Set the default auto-dismiss duration (milliseconds).
|
||||
|
||||
```javascript
|
||||
notificationStore.setDefaultDuration(5000); // 5 seconds
|
||||
```
|
||||
|
||||
#### `setMaxNotifications(max)`
|
||||
|
||||
Set maximum number of concurrent notifications.
|
||||
|
||||
```javascript
|
||||
notificationStore.setMaxNotifications(3);
|
||||
```
|
||||
|
||||
### Advanced Workflow Helper
|
||||
|
||||
#### `withNotifications(operation, asyncFunction, options)`
|
||||
|
||||
Wrap an async operation with automatic loading/success/error notifications.
|
||||
|
||||
```javascript
|
||||
const result = await notificationStore.withNotifications(
|
||||
"save",
|
||||
async () => {
|
||||
return await saveDataToApi();
|
||||
},
|
||||
{
|
||||
loadingMessage: "Saving changes...",
|
||||
successMessage: "Changes saved successfully!",
|
||||
errorMessage: null, // Use default error handling
|
||||
showLoading: true,
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## Action Button Configuration
|
||||
|
||||
### Action Object Structure
|
||||
|
||||
```javascript
|
||||
{
|
||||
label: 'Button Text', // Required: Button label
|
||||
variant: 'primary', // Optional: 'primary' | 'danger' | 'secondary'
|
||||
icon: 'mdi mdi-check', // Optional: Icon class
|
||||
handler: (notification) => {}, // Optional: Click handler function
|
||||
dismissAfter: true // Optional: Auto-dismiss after click (default: true)
|
||||
}
|
||||
```
|
||||
|
||||
### Action Examples
|
||||
|
||||
```javascript
|
||||
notificationStore.addNotification({
|
||||
type: "warning",
|
||||
title: "Unsaved Changes",
|
||||
message: "You have unsaved changes. What would you like to do?",
|
||||
persistent: true,
|
||||
actions: [
|
||||
{
|
||||
label: "Save",
|
||||
variant: "primary",
|
||||
icon: "mdi mdi-content-save",
|
||||
handler: (notification) => {
|
||||
saveChanges();
|
||||
// Access notification data if needed
|
||||
console.log("Saving from notification:", notification.data);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Discard",
|
||||
variant: "danger",
|
||||
icon: "mdi mdi-delete",
|
||||
handler: () => {
|
||||
discardChanges();
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Keep Editing",
|
||||
variant: "secondary",
|
||||
dismissAfter: false, // Don't dismiss, let user continue editing
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
### Basic Notifications
|
||||
|
||||
```javascript
|
||||
const notificationStore = useNotificationStore();
|
||||
|
||||
// Simple success
|
||||
notificationStore.addSuccess("Data saved successfully!");
|
||||
|
||||
// Simple error
|
||||
notificationStore.addError("Failed to connect to server");
|
||||
|
||||
// Custom duration
|
||||
notificationStore.addWarning("Session expires in 5 minutes", "Warning", {
|
||||
duration: 8000,
|
||||
});
|
||||
```
|
||||
|
||||
### API Operation Notifications
|
||||
|
||||
```javascript
|
||||
// Method 1: Manual handling
|
||||
try {
|
||||
await apiCall();
|
||||
notificationStore.addSuccess("Operation completed successfully!");
|
||||
} catch (error) {
|
||||
notificationStore.addError(error.message, "Operation Failed");
|
||||
}
|
||||
|
||||
// Method 2: Using API helpers
|
||||
try {
|
||||
await apiCall();
|
||||
notificationStore.showApiSuccess("create");
|
||||
} catch (error) {
|
||||
notificationStore.showApiError("create", error);
|
||||
}
|
||||
|
||||
// Method 3: Using workflow helper
|
||||
await notificationStore.withNotifications("create", async () => {
|
||||
return await apiCall();
|
||||
});
|
||||
```
|
||||
|
||||
### Loading States
|
||||
|
||||
```javascript
|
||||
// Show loading notification
|
||||
const loadingId = notificationStore.showLoadingNotification("Processing...");
|
||||
|
||||
try {
|
||||
const result = await longRunningOperation();
|
||||
notificationStore.updateToSuccess(loadingId, "Operation completed!");
|
||||
} catch (error) {
|
||||
notificationStore.updateToError(loadingId, "Operation failed");
|
||||
}
|
||||
```
|
||||
|
||||
### Interactive Notifications
|
||||
|
||||
```javascript
|
||||
// Confirmation dialog
|
||||
notificationStore.addNotification({
|
||||
type: "warning",
|
||||
title: "Delete Confirmation",
|
||||
message: `Are you sure you want to delete "${itemName}"?`,
|
||||
persistent: true,
|
||||
actions: [
|
||||
{
|
||||
label: "Yes, Delete",
|
||||
variant: "danger",
|
||||
handler: async () => {
|
||||
const deleteId =
|
||||
notificationStore.showLoadingNotification("Deleting...");
|
||||
try {
|
||||
await deleteItem(itemId);
|
||||
notificationStore.updateToSuccess(
|
||||
deleteId,
|
||||
"Item deleted successfully",
|
||||
);
|
||||
refreshData();
|
||||
} catch (error) {
|
||||
notificationStore.updateToError(deleteId, "Failed to delete item");
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Cancel",
|
||||
variant: "secondary",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Multi-step workflow
|
||||
notificationStore.addNotification({
|
||||
type: "info",
|
||||
title: "Export Complete",
|
||||
message: "Your data has been exported successfully.",
|
||||
actions: [
|
||||
{
|
||||
label: "Download",
|
||||
variant: "primary",
|
||||
icon: "mdi mdi-download",
|
||||
handler: () => downloadFile(),
|
||||
},
|
||||
{
|
||||
label: "Email",
|
||||
variant: "secondary",
|
||||
icon: "mdi mdi-email",
|
||||
handler: () => emailFile(),
|
||||
},
|
||||
{
|
||||
label: "View",
|
||||
variant: "secondary",
|
||||
icon: "mdi mdi-eye",
|
||||
handler: () => viewFile(),
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## Integration with Vue Components
|
||||
|
||||
### In Composition API
|
||||
|
||||
```javascript
|
||||
import { useNotificationStore } from "@/stores/notifications";
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const notificationStore = useNotificationStore();
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await submitForm();
|
||||
notificationStore.addSuccess("Form submitted successfully!");
|
||||
} catch (error) {
|
||||
notificationStore.addError("Failed to submit form", "Submission Error");
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handleSubmit,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### In Options API
|
||||
|
||||
```javascript
|
||||
import { mapActions, mapGetters } from "pinia";
|
||||
import { useNotificationStore } from "@/stores/notifications";
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters(useNotificationStore, [
|
||||
"activeCount",
|
||||
"hasErrorNotifications",
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useNotificationStore, ["addSuccess", "addError", "clearAll"]),
|
||||
|
||||
async saveData() {
|
||||
try {
|
||||
await this.apiCall();
|
||||
this.addSuccess("Data saved!");
|
||||
} catch (error) {
|
||||
this.addError(error.message);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
|
||||
- ✅ Use appropriate notification types for different scenarios
|
||||
- ✅ Keep messages concise and actionable
|
||||
- ✅ Use loading notifications for long-running operations
|
||||
- ✅ Provide clear action buttons for next steps
|
||||
- ✅ Set appropriate durations (longer for errors, shorter for success)
|
||||
- ✅ Use persistent notifications for critical actions requiring user input
|
||||
|
||||
### Don'ts
|
||||
|
||||
- ❌ Don't overwhelm users with too many notifications
|
||||
- ❌ Don't use persistent notifications for simple confirmations
|
||||
- ❌ Don't make notification messages too long
|
||||
- ❌ Don't forget to handle loading state cleanup
|
||||
- ❌ Don't use success notifications for every small action
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- The store automatically limits concurrent notifications
|
||||
- Dismissed notifications are kept in history for debugging
|
||||
- Use `clearDismissed()` periodically to prevent memory leaks
|
||||
- Action handlers should be lightweight to avoid blocking the UI
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Testing
|
||||
|
||||
```javascript
|
||||
import { setActivePinia, createPinia } from "pinia";
|
||||
import { useNotificationStore } from "@/stores/notifications";
|
||||
|
||||
describe("Notifications Store", () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
it("adds notifications correctly", () => {
|
||||
const store = useNotificationStore();
|
||||
const id = store.addSuccess("Test message");
|
||||
|
||||
expect(store.activeNotifications).toHaveLength(1);
|
||||
expect(store.activeNotifications[0].message).toBe("Test message");
|
||||
expect(store.activeNotifications[0].type).toBe("success");
|
||||
});
|
||||
|
||||
it("dismisses notifications", () => {
|
||||
const store = useNotificationStore();
|
||||
const id = store.addInfo("Test");
|
||||
|
||||
store.dismissNotification(id);
|
||||
expect(store.activeNotifications).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue