custom_ui/frontend/src/api-toast.js

242 lines
6.1 KiB
JavaScript

/**
* Enhanced API wrapper with PrimeVue Toast integration
*
* This provides a cleaner, simpler API error handling system using PrimeVue Toast
* instead of a complex custom notification system.
*/
import { useErrorStore } from "@/stores/errors";
import { useLoadingStore } from "@/stores/loading";
import Api from "./api";
export class ApiWithToast {
static async makeApiCall(apiFunction, options = {}) {
const {
// Error handling options
componentName = null,
showErrorToast = true,
showSuccessToast = false,
// Loading options
showLoading = true,
loadingMessage = "Loading...",
// Success options
successMessage = null,
// Error options
customErrorMessage = null,
// Retry options
retryCount = 0,
retryDelay = 1000,
// Operation identifier for tracking
operationKey = null,
rethrow = false,
} = options;
const errorStore = useErrorStore();
const loadingStore = useLoadingStore();
// Generate operation key if not provided
const operation = operationKey || `api-${Date.now()}`;
try {
// Clear any existing errors
if (componentName) {
errorStore.clearComponentError(componentName);
}
// Show loading state
if (showLoading) {
if (componentName) {
loadingStore.setComponentLoading(componentName, true, loadingMessage);
} else {
loadingStore.startOperation(operation, loadingMessage);
}
}
// Make the API call with retry logic
let attempt = 0;
while (attempt <= retryCount) {
try {
const result = await apiFunction();
// Show success toast if requested
if (showSuccessToast && successMessage) {
errorStore.setSuccess(successMessage);
}
return result;
} catch (error) {
attempt++;
if (attempt <= retryCount) {
// Wait before retry
await new Promise((resolve) => setTimeout(resolve, retryDelay));
continue;
}
// Final attempt failed - handle error
if (componentName) {
errorStore.setComponentError(componentName, error, false);
}
// Show error toast
if (showErrorToast) {
const errorMessage = customErrorMessage || this.extractErrorMessage(error);
errorStore.setGlobalError(new Error(errorMessage));
}
// Rethrow error if requested (default is to rethrow)
if (rethrow) {
throw error;
}
// If not rethrowing, return null to indicate failure
return null;
}
}
} finally {
// Always clear loading state
if (showLoading) {
if (componentName) {
loadingStore.setComponentLoading(componentName, false);
} else {
loadingStore.stopOperation(operation);
}
}
}
}
// Helper to extract meaningful error messages
static extractErrorMessage(error) {
if (typeof error === "string") {
return error;
}
if (error?.response?.data?.message) {
return error.response.data.message;
}
if (error?.message) {
return error.message;
}
if (error?.request) {
return "Network error - please check your connection";
}
return "An unexpected error occurred";
}
// Convenience methods for common API operations
static async getClientStatusCounts(options = {}) {
return this.makeApiCall(() => Api.getClientStatusCounts(), {
operationKey: "client-status-counts",
componentName: "clients",
loadingMessage: "Loading client status data...",
showSuccessToast: false, // Don't show success for data fetches
...options,
});
}
static async getPaginatedClientDetails(paginationParams, filters, sorting, options = {}) {
return this.makeApiCall(
() => Api.getPaginatedClientDetails(paginationParams, filters, sorting),
{
operationKey: "client-table-data",
componentName: "dataTable",
loadingMessage: "Loading client data...",
showSuccessToast: false,
...options,
},
);
}
static async createClient(clientData, options = {}) {
return this.makeApiCall(() => Api.createClient(clientData), {
operationKey: "create-client",
componentName: "form",
loadingMessage: "Creating client...",
showSuccessToast: true,
successMessage: "Client created successfully!",
...options,
});
}
static async getPaginatedJobDetails(paginationParams, filters, sorting, options = {}) {
return this.makeApiCall(
() => Api.getPaginatedJobDetails(paginationParams, filters, sorting),
{
operationKey: "job-table-data",
componentName: "jobs",
loadingMessage: "Loading job data...",
showSuccessToast: false,
...options,
},
);
}
static async getPaginatedWarrantyData(paginationParams, filters, sorting, options = {}) {
return this.makeApiCall(
() => Api.getPaginatedWarrantyData(paginationParams, filters, sorting),
{
operationKey: "warranty-table-data",
componentName: "warranties",
loadingMessage: "Loading warranty data...",
showSuccessToast: false,
...options,
},
);
}
static async getCityStateByZip(zipcode, options = {}) {
return this.makeApiCall(() => Api.getCityStateByZip(zipcode), {
operationKey: "zip-lookup",
componentName: "form",
loadingMessage: "Looking up location...",
showSuccessToast: false,
customErrorMessage: "Unable to find location for the provided zip code",
retryCount: 2,
retryDelay: 1000,
...options,
});
}
}
/**
* Simple usage examples:
*
* // Basic API call with automatic toast notifications
* try {
* const result = await ApiWithToast.getPaginatedClientDetails(pagination, filters, []);
* // Success - data loaded, no toast shown for data fetches
* } catch (error) {
* // Error toast automatically shown, component error set
* }
*
* // Create operation with success toast
* try {
* await ApiWithToast.createClient(formData);
* // Success toast shown automatically: "Client created successfully!"
* } catch (error) {
* // Error toast shown automatically with appropriate message
* }
*
* // Custom options
* await ApiWithToast.makeApiCall(
* () => someApiCall(),
* {
* componentName: 'myComponent',
* showSuccessToast: true,
* successMessage: 'Operation completed!',
* retryCount: 3,
* customErrorMessage: 'Custom error message'
* }
* );
*/
export default ApiWithToast;