Files
checkcle/application/src/components/schedule-incident/hooks/useIncidentData.ts
T
2025-07-10 22:14:17 +07:00

201 lines
6.3 KiB
TypeScript

import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { incidentService, IncidentItem } from '@/services/incident';
interface UseIncidentDataProps {
refreshTrigger?: number;
}
export const useIncidentData = ({ refreshTrigger = 0 }: UseIncidentDataProps) => {
const [incidents, setIncidents] = useState<IncidentItem[]>([]);
const [filter, setFilter] = useState("unresolved");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [initialized, setInitialized] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
// Use a ref to prevent multiple simultaneous fetch requests
const isFetchingRef = useRef(false);
// Use a ref to track the refresh trigger
const lastRefreshTriggerRef = useRef(refreshTrigger);
// Simplified fetch function with improved controls to prevent duplicate calls
const fetchIncidentData = useCallback(async (force = false) => {
// Skip if already fetching
if (isFetchingRef.current) {
// console.log('Already fetching data, skipping additional request');
return;
}
// Skip if not forced and already initialized
if (initialized && !force) {
// console.log('Data already initialized and no force refresh, skipping fetch');
return;
}
// Set fetching flags
isFetchingRef.current = true;
if (!initialized || force) {
setLoading(true);
}
if (force) {
setIsRefreshing(true);
}
setError(null);
try {
// console.log(`Fetching incident data (force=${force})`);
const allIncidents = await incidentService.getAllIncidents(force);
if (Array.isArray(allIncidents)) {
setIncidents(allIncidents);
// console.log(`Successfully set ${allIncidents.length} incidents to state`);
} else {
setIncidents([]);
// console.warn('No incidents returned from service');
}
setInitialized(true);
setLoading(false);
setIsRefreshing(false);
} catch (error) {
// console.error('Error fetching incident data:', error);
setError('Failed to load incident data. Please try again later.');
setIncidents([]);
setInitialized(true);
setLoading(false);
setIsRefreshing(false);
} finally {
// Reset fetching flag after a slight delay to prevent rapid consecutive calls
setTimeout(() => {
isFetchingRef.current = false;
}, 500);
}
}, [initialized]);
// Only fetch when component mounts or refreshTrigger changes
useEffect(() => {
// Skip if the refresh trigger hasn't changed (prevents duplicate effect calls)
if (refreshTrigger === lastRefreshTriggerRef.current && initialized) {
// console.log('Refresh trigger unchanged, skipping fetch');
return;
}
// Update last refresh trigger ref
lastRefreshTriggerRef.current = refreshTrigger;
// Create an abort controller for cleanup
const abortController = new AbortController();
let isMounted = true;
// console.log(`useIncidentData effect running, refreshTrigger: ${refreshTrigger}`);
// Use a longer delay to ensure we don't trigger too many API calls
const fetchTimer = setTimeout(() => {
if (isMounted) {
fetchIncidentData(refreshTrigger > 0);
}
}, 500); // Use a longer delay
// Cleanup function to abort any in-flight requests and clear timers
return () => {
// console.log('Cleaning up incident data fetch effect');
isMounted = false;
clearTimeout(fetchTimer);
abortController.abort();
};
}, [fetchIncidentData, refreshTrigger, initialized]);
// Filter the data based on the current filter
const incidentData = useMemo(() => {
if (!initialized || incidents.length === 0) return [];
// console.log(`Filtering incidents by: ${filter}`);
if (filter === "unresolved") {
return incidents.filter(item => {
const status = (item.status || item.impact_status || '').toLowerCase();
return status !== 'resolved';
});
} else if (filter === "resolved") {
return incidents.filter(item => {
const status = (item.status || item.impact_status || '').toLowerCase();
return status === 'resolved';
});
}
return [];
}, [filter, incidents, initialized]);
// Calculate stats for overview cards
const overviewStats = useMemo(() => {
if (!initialized || incidents.length === 0) {
return {
unresolved: 0,
resolved: 0,
critical: 0,
highPriority: 0,
avgResolutionTime: "0h"
};
}
const unresolvedIncidents = incidents.filter(item => {
const status = (item.status || item.impact_status || '').toLowerCase();
return status !== 'resolved';
});
const resolvedIncidents = incidents.filter(item => {
const status = (item.status || item.impact_status || '').toLowerCase();
return status === 'resolved';
});
const criticalCount = unresolvedIncidents.filter(item =>
(item.priority?.toLowerCase() === 'critical') ||
(item.impact?.toLowerCase() === 'critical')
).length;
const highPriorityCount = unresolvedIncidents.filter(item =>
(item.priority?.toLowerCase() === 'high')
).length;
let avgResolutionTime = "0h";
if (resolvedIncidents.length > 0) {
const totalHours = resolvedIncidents.reduce((total, item) => {
if (!item.created || !item.resolution_time) return total;
const createdAt = new Date(item.created).getTime();
const resolvedAt = new Date(item.resolution_time).getTime();
const durationHours = (resolvedAt - createdAt) / (1000 * 60 * 60);
return isNaN(durationHours) ? total : total + durationHours;
}, 0);
avgResolutionTime = `${(totalHours / resolvedIncidents.length).toFixed(1)}h`;
}
return {
unresolved: unresolvedIncidents.length,
resolved: resolvedIncidents.length,
critical: criticalCount,
highPriority: highPriorityCount,
avgResolutionTime
};
}, [incidents, initialized]);
const isEmpty = !incidentData.length && initialized && !loading;
return {
filter,
setFilter,
incidentData,
overviewStats,
fetchIncidentData,
isEmpty,
error,
loading,
initialized,
isRefreshing
};
};