Fix: Prevent console message loop on maintenance updates
The console was generating excessive messages after schedule maintenance updates.
This commit is contained in:
@@ -35,4 +35,52 @@ const api = {
|
||||
}
|
||||
};
|
||||
|
||||
// Mock fetch override for development
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = async (url, options = {}) => {
|
||||
// Check if this is an API request to our mock endpoints
|
||||
if (typeof url === 'string' && url.startsWith('/api/')) {
|
||||
console.log('Intercepting API request:', url, options);
|
||||
|
||||
try {
|
||||
let body = {};
|
||||
|
||||
// Properly handle different body types
|
||||
if (options.body) {
|
||||
if (typeof options.body === 'string') {
|
||||
body = JSON.parse(options.body);
|
||||
} else {
|
||||
// Handle ReadableStream or other BodyInit types
|
||||
const bodyText = await new Response(options.body).text();
|
||||
body = bodyText ? JSON.parse(bodyText) : {};
|
||||
}
|
||||
}
|
||||
|
||||
const result = await api.handleRequest(url, options.method || 'GET', body);
|
||||
|
||||
// Create a proper Response object
|
||||
return new Response(JSON.stringify(result.json), {
|
||||
status: result.status,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in API handler:', error);
|
||||
return new Response(JSON.stringify({
|
||||
success: false,
|
||||
message: 'Internal server error'
|
||||
}), {
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// For all other requests, use the original fetch
|
||||
return originalFetch(url, options);
|
||||
};
|
||||
|
||||
export default api;
|
||||
@@ -58,7 +58,6 @@ export const useMaintenanceData = ({ refreshTrigger = 0 }: UseMaintenanceDataPro
|
||||
|
||||
// Prevent duplicate requests
|
||||
if (currentRequestRef.current) {
|
||||
console.log("Request already in progress, waiting...");
|
||||
await currentRequestRef.current;
|
||||
return;
|
||||
}
|
||||
@@ -72,14 +71,11 @@ export const useMaintenanceData = ({ refreshTrigger = 0 }: UseMaintenanceDataPro
|
||||
|
||||
const requestPromise = (async () => {
|
||||
try {
|
||||
console.log("Fetching maintenance data...", force ? "(forced)" : "");
|
||||
const data = await maintenanceService.getMaintenanceRecords();
|
||||
|
||||
// Check if component is still mounted before updating state
|
||||
if (!mountedRef.current) return;
|
||||
|
||||
console.log(`Fetched ${data.length} maintenance records`);
|
||||
|
||||
// Update state with fetched data
|
||||
setAllMaintenanceData(data);
|
||||
setInitialized(true);
|
||||
@@ -119,7 +115,6 @@ export const useMaintenanceData = ({ refreshTrigger = 0 }: UseMaintenanceDataPro
|
||||
|
||||
// Initial fetch on mount - NO AUTOMATIC POLLING
|
||||
useEffect(() => {
|
||||
console.log("useMaintenanceData hook mounted, fetching initial data");
|
||||
mountedRef.current = true;
|
||||
|
||||
// Only fetch initial data, no polling
|
||||
@@ -134,7 +129,6 @@ export const useMaintenanceData = ({ refreshTrigger = 0 }: UseMaintenanceDataPro
|
||||
// Handle refresh trigger changes - ONLY when explicitly triggered
|
||||
useEffect(() => {
|
||||
if (refreshTrigger > 0) {
|
||||
console.log("Manual refresh triggered, forcing data fetch");
|
||||
fetchMaintenanceData(true); // Force refresh to bypass cache
|
||||
}
|
||||
}, [refreshTrigger, fetchMaintenanceData]);
|
||||
|
||||
+125
-42
@@ -19,74 +19,157 @@ export const MaintenanceStatusChecker = ({
|
||||
const { toast } = useToast();
|
||||
const checkedItemsRef = useRef<Set<string>>(new Set());
|
||||
const notificationSentRef = useRef<Set<string>>(new Set());
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!maintenanceData || maintenanceData.length === 0) return;
|
||||
|
||||
// Check for maintenance items that need status update
|
||||
const now = new Date();
|
||||
const checkAndUpdateStatus = async () => {
|
||||
const currentTime = new Date();
|
||||
let hasUpdates = false;
|
||||
|
||||
console.log('MaintenanceStatusChecker: Checking status updates at', currentTime.toISOString());
|
||||
console.log('MaintenanceStatusChecker: Checking', maintenanceData.length, 'maintenance items');
|
||||
|
||||
for (const item of maintenanceData) {
|
||||
if (item.status.toLowerCase() !== 'scheduled') continue;
|
||||
|
||||
// Skip if we've already checked this item in this session
|
||||
if (checkedItemsRef.current.has(item.id)) continue;
|
||||
|
||||
try {
|
||||
const startTime = new Date(item.start_time);
|
||||
const endTime = new Date(item.end_time);
|
||||
const status = item.status.toLowerCase();
|
||||
|
||||
// If current time is past start time but before end time, update to in_progress
|
||||
if (startTime <= now && now <= endTime) {
|
||||
console.log(`Auto-updating maintenance ${item.title} to in_progress status`);
|
||||
console.log(`MaintenanceStatusChecker: Item ${item.id} - Status: ${status}, Start: ${startTime.toISOString()}, End: ${endTime.toISOString()}, Current: ${currentTime.toISOString()}`);
|
||||
|
||||
// Check if scheduled maintenance should start (become in_progress)
|
||||
if (status === 'scheduled' && currentTime >= startTime && currentTime <= endTime) {
|
||||
const checkKey = `${item.id}-started`;
|
||||
const notificationKey = `${item.id}-start-notification`;
|
||||
|
||||
// Update status to in_progress
|
||||
await maintenanceService.updateMaintenanceStatus(item.id, 'in_progress');
|
||||
|
||||
// Only send notification if not sent before
|
||||
if (!notificationSentRef.current.has(item.id)) {
|
||||
console.log(`Sending start notification for maintenance ${item.title}`);
|
||||
if (!checkedItemsRef.current.has(checkKey)) {
|
||||
console.log(`MaintenanceStatusChecker: Starting maintenance ${item.id} at ${currentTime.toISOString()}`);
|
||||
|
||||
// Send notification for maintenance start
|
||||
await maintenanceNotificationService.sendMaintenanceNotification({
|
||||
maintenance: item,
|
||||
notificationType: 'start'
|
||||
// Update status to in_progress first
|
||||
await maintenanceService.updateMaintenanceStatus(item.id, 'in_progress');
|
||||
|
||||
// Send start notification only once
|
||||
if (!notificationSentRef.current.has(notificationKey)) {
|
||||
try {
|
||||
await maintenanceNotificationService.sendMaintenanceNotification({
|
||||
maintenance: item,
|
||||
notificationType: 'start'
|
||||
});
|
||||
notificationSentRef.current.add(notificationKey);
|
||||
console.log(`MaintenanceStatusChecker: Start notification sent for ${item.id}`);
|
||||
} catch (notificationError) {
|
||||
console.log('MaintenanceStatusChecker: Start notification failed', notificationError);
|
||||
}
|
||||
}
|
||||
|
||||
toast({
|
||||
title: t('maintenanceInProgress'),
|
||||
description: `${item.title} ${t('isNowInProgress')}`,
|
||||
});
|
||||
|
||||
// Mark as notified
|
||||
notificationSentRef.current.add(item.id);
|
||||
checkedItemsRef.current.add(checkKey);
|
||||
hasUpdates = true;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: t('maintenanceInProgress'),
|
||||
description: `${item.title} ${t('isNowInProgress')}`,
|
||||
});
|
||||
|
||||
onStatusUpdated();
|
||||
}
|
||||
|
||||
// Mark as checked regardless of outcome
|
||||
checkedItemsRef.current.add(item.id);
|
||||
// Check if in_progress maintenance should be completed
|
||||
if (status === 'in_progress' && currentTime >= endTime) {
|
||||
const checkKey = `${item.id}-completed`;
|
||||
const notificationKey = `${item.id}-end-notification`;
|
||||
|
||||
if (!checkedItemsRef.current.has(checkKey)) {
|
||||
console.log(`MaintenanceStatusChecker: Completing maintenance ${item.id} at ${currentTime.toISOString()}`);
|
||||
|
||||
// Update status to completed first
|
||||
await maintenanceService.updateMaintenanceStatus(item.id, 'completed');
|
||||
|
||||
// Send completion notification only once
|
||||
if (!notificationSentRef.current.has(notificationKey)) {
|
||||
try {
|
||||
await maintenanceNotificationService.sendMaintenanceNotification({
|
||||
maintenance: item,
|
||||
notificationType: 'end'
|
||||
});
|
||||
notificationSentRef.current.add(notificationKey);
|
||||
console.log(`MaintenanceStatusChecker: Completion notification sent for ${item.id}`);
|
||||
} catch (notificationError) {
|
||||
console.log('MaintenanceStatusChecker: Completion notification failed', notificationError);
|
||||
}
|
||||
}
|
||||
|
||||
toast({
|
||||
title: t('maintenanceCompleted'),
|
||||
description: `${item.title} ${t('hasBeenCompleted')}`,
|
||||
});
|
||||
|
||||
checkedItemsRef.current.add(checkKey);
|
||||
hasUpdates = true;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error auto-updating maintenance status for ${item.id}:`, error);
|
||||
|
||||
// Add a small delay before trying again to prevent rapid retry cycles
|
||||
console.error('MaintenanceStatusChecker: Error updating status for item', item.id, error);
|
||||
// Clear the check flags after 2 minutes to allow retry
|
||||
setTimeout(() => {
|
||||
checkedItemsRef.current.delete(item.id); // Allow retry on next check
|
||||
}, 300000); // 5 minutes before retry
|
||||
checkedItemsRef.current.delete(`${item.id}-started`);
|
||||
checkedItemsRef.current.delete(`${item.id}-completed`);
|
||||
notificationSentRef.current.delete(`${item.id}-start-notification`);
|
||||
notificationSentRef.current.delete(`${item.id}-end-notification`);
|
||||
}, 120000);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUpdates) {
|
||||
console.log('MaintenanceStatusChecker: Status updates detected, triggering refresh');
|
||||
// Force immediate refresh to update the UI
|
||||
onStatusUpdated();
|
||||
}
|
||||
};
|
||||
|
||||
// Run the check immediately
|
||||
// Clear the interval if it exists
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
|
||||
// Initial check immediately
|
||||
checkAndUpdateStatus();
|
||||
|
||||
// Set up interval to check every minute
|
||||
const intervalId = setInterval(checkAndUpdateStatus, 60000);
|
||||
// Check every 5 seconds for immediate status updates
|
||||
intervalRef.current = window.setInterval(checkAndUpdateStatus, 5000);
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
return () => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [maintenanceData, onStatusUpdated, t, toast]);
|
||||
|
||||
// This is a utility component with no UI render
|
||||
// Clear check flags when maintenance data changes significantly
|
||||
useEffect(() => {
|
||||
const currentIds = new Set(maintenanceData.map(item => item.id));
|
||||
|
||||
// Clean up check flags for items that no longer exist
|
||||
const keysToDelete = Array.from(checkedItemsRef.current).filter(key => {
|
||||
const itemId = key.split('-')[0];
|
||||
return !currentIds.has(itemId);
|
||||
});
|
||||
|
||||
keysToDelete.forEach(key => {
|
||||
checkedItemsRef.current.delete(key);
|
||||
});
|
||||
|
||||
// Clean up notification flags for items that no longer exist
|
||||
const notificationKeysToDelete = Array.from(notificationSentRef.current).filter(key => {
|
||||
const itemId = key.split('-')[0];
|
||||
return !currentIds.has(itemId);
|
||||
});
|
||||
|
||||
notificationKeysToDelete.forEach(key => {
|
||||
notificationSentRef.current.delete(key);
|
||||
});
|
||||
}, [maintenanceData]);
|
||||
|
||||
return null;
|
||||
};
|
||||
};
|
||||
+1
-27
@@ -25,7 +25,6 @@ export const MaintenanceDetailContent = ({
|
||||
queryKey: ['users'],
|
||||
queryFn: async () => {
|
||||
const usersList = await userService.getUsers();
|
||||
console.log("Fetched users for maintenance detail:", usersList);
|
||||
return usersList || [];
|
||||
},
|
||||
enabled: true
|
||||
@@ -36,7 +35,6 @@ export const MaintenanceDetailContent = ({
|
||||
queryKey: ['notificationChannels'],
|
||||
queryFn: async () => {
|
||||
const channels = await alertConfigService.getAlertConfigurations();
|
||||
console.log("Fetched notification channels:", channels);
|
||||
return channels || [];
|
||||
},
|
||||
enabled: true
|
||||
@@ -45,9 +43,6 @@ export const MaintenanceDetailContent = ({
|
||||
// Process user information when users data is available
|
||||
useEffect(() => {
|
||||
if (users.length > 0 && maintenance) {
|
||||
console.log("Processing maintenance data with users:", maintenance);
|
||||
console.log("Maintenance assigned_users:", maintenance.assigned_users);
|
||||
|
||||
// Create a copy of the maintenance object for modifications
|
||||
const enhancedMaintenance = { ...maintenance };
|
||||
|
||||
@@ -58,7 +53,6 @@ export const MaintenanceDetailContent = ({
|
||||
// Step 1: Extract user IDs from various formats
|
||||
if (Array.isArray(maintenance.assigned_users)) {
|
||||
userIds = maintenance.assigned_users;
|
||||
console.log("Assigned users is an array:", userIds);
|
||||
} else if (typeof maintenance.assigned_users === 'string') {
|
||||
// Try parsing the string to extract user IDs
|
||||
try {
|
||||
@@ -67,34 +61,28 @@ export const MaintenanceDetailContent = ({
|
||||
if (Array.isArray(parsedData)) {
|
||||
// Direct array format
|
||||
userIds = parsedData;
|
||||
console.log("Parsed assigned_users from JSON array:", userIds);
|
||||
} else if (typeof parsedData === 'string') {
|
||||
// Nested JSON string
|
||||
try {
|
||||
const nestedData = JSON.parse(parsedData);
|
||||
if (Array.isArray(nestedData)) {
|
||||
userIds = nestedData;
|
||||
console.log("Parsed assigned_users from nested JSON:", userIds);
|
||||
}
|
||||
} catch (e) {
|
||||
// If nested parsing fails, treat as single ID
|
||||
userIds = [parsedData];
|
||||
console.log("Using parsed string as single ID:", userIds);
|
||||
}
|
||||
} else {
|
||||
// Unknown format, use as is
|
||||
userIds = [String(parsedData)];
|
||||
console.log("Using parsed data as single ID:", userIds);
|
||||
}
|
||||
} catch (e) {
|
||||
// If JSON parsing fails, try comma splitting
|
||||
if (maintenance.assigned_users.includes(',')) {
|
||||
userIds = maintenance.assigned_users.split(',').map(id => id.trim()).filter(Boolean);
|
||||
console.log("Split assigned_users by comma:", userIds);
|
||||
} else {
|
||||
// Single ID
|
||||
userIds = [maintenance.assigned_users];
|
||||
console.log("Using string as single ID:", userIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,20 +95,15 @@ export const MaintenanceDetailContent = ({
|
||||
cleanId = cleanId.replace(/[\[\]"'\\]/g, '');
|
||||
return cleanId;
|
||||
}).filter(Boolean);
|
||||
|
||||
console.log("Cleaned user IDs:", userIds);
|
||||
|
||||
// Step 3: Find matching users from the users array
|
||||
if (userIds.length > 0) {
|
||||
const matchedUsers = users.filter(user => userIds.includes(user.id));
|
||||
console.log("Matched assigned users:", matchedUsers);
|
||||
setAssignedUsers(matchedUsers);
|
||||
} else {
|
||||
console.log("No user IDs found in assigned_users");
|
||||
setAssignedUsers([]);
|
||||
}
|
||||
} else {
|
||||
console.log("No assigned users found in maintenance data");
|
||||
setAssignedUsers([]);
|
||||
}
|
||||
|
||||
@@ -138,24 +121,15 @@ export const MaintenanceDetailContent = ({
|
||||
const channelId = maintenance.notification_channel_id || maintenance.notification_id;
|
||||
|
||||
if (channelId && notificationChannels.length > 0) {
|
||||
console.log("Looking for notification channel with ID:", channelId);
|
||||
console.log("Available notification channels:", notificationChannels.map(c => ({ id: c.id, name: c.notify_name })));
|
||||
|
||||
const channel = notificationChannels.find(ch => ch.id === channelId);
|
||||
if (channel) {
|
||||
console.log("Found notification channel:", channel);
|
||||
enhancedMaintenance.notification_channel_name = `${channel.notify_name} (${channel.notification_type})`;
|
||||
} else {
|
||||
console.log("No matching notification channel found for ID:", channelId);
|
||||
enhancedMaintenance.notification_channel_name = `Channel ID: ${channelId}`;
|
||||
}
|
||||
} else {
|
||||
console.log("No channel ID available or no notification channels loaded");
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Enhanced maintenance with details:", enhancedMaintenance);
|
||||
console.log("Assigned users for display:", assignedUsers);
|
||||
setMaintenanceWithDetails(enhancedMaintenance);
|
||||
}
|
||||
}, [maintenance, users, notificationChannels]);
|
||||
@@ -173,4 +147,4 @@ export const MaintenanceDetailContent = ({
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -13,14 +13,7 @@ const CACHE_EXPIRY_MS = 600000; // 10 minutes cache expiry (increased significan
|
||||
*/
|
||||
export const isCacheValid = (): boolean => {
|
||||
const now = Date.now();
|
||||
const isValid = !!(cachedMaintenanceRecords && now - lastFetchTimestamp < CACHE_EXPIRY_MS);
|
||||
|
||||
// Minimal logging to reduce console spam
|
||||
if (isValid) {
|
||||
console.log("Using cached maintenance data");
|
||||
}
|
||||
|
||||
return isValid;
|
||||
return !!(cachedMaintenanceRecords && now - lastFetchTimestamp < CACHE_EXPIRY_MS);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -28,7 +21,6 @@ export const isCacheValid = (): boolean => {
|
||||
*/
|
||||
export const getCachedRecords = () => {
|
||||
if (cachedMaintenanceRecords) {
|
||||
console.log("Returning cached maintenance records:", cachedMaintenanceRecords.length);
|
||||
return [...cachedMaintenanceRecords]; // Return a copy to prevent mutation
|
||||
}
|
||||
return null;
|
||||
@@ -40,7 +32,6 @@ export const getCachedRecords = () => {
|
||||
export const updateCache = (data: MaintenanceItem[], timestamp?: number): void => {
|
||||
cachedMaintenanceRecords = data;
|
||||
lastFetchTimestamp = timestamp || Date.now();
|
||||
console.log("Maintenance cache updated with", data.length, "records");
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -49,7 +40,6 @@ export const updateCache = (data: MaintenanceItem[], timestamp?: number): void =
|
||||
export const clearCache = (): void => {
|
||||
cachedMaintenanceRecords = null;
|
||||
lastFetchTimestamp = 0;
|
||||
console.log("Maintenance cache cleared");
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,6 @@ export const fetchAllMaintenanceRecords = async (forceRefresh = false): Promise<
|
||||
// If forced refresh, clear cache first
|
||||
if (forceRefresh) {
|
||||
clearCache();
|
||||
console.log('Cache cleared for forced refresh');
|
||||
}
|
||||
|
||||
// Return cached data if available and not forced refresh
|
||||
@@ -29,14 +28,12 @@ export const fetchAllMaintenanceRecords = async (forceRefresh = false): Promise<
|
||||
|
||||
// Request deduplication: if a request is already in progress, wait for it
|
||||
if (currentRequest) {
|
||||
console.log('Request already in progress, waiting for completion');
|
||||
return await currentRequest;
|
||||
}
|
||||
|
||||
// Strict rate limiting - prevent requests too close together (unless forced)
|
||||
const timeSinceLastRequest = now - lastRequestTime;
|
||||
if (timeSinceLastRequest < MIN_REQUEST_INTERVAL && !forceRefresh) {
|
||||
console.log(`Rate limiting: ${Math.round((MIN_REQUEST_INTERVAL - timeSinceLastRequest) / 1000)}s remaining`);
|
||||
return getCachedRecords() || [];
|
||||
}
|
||||
|
||||
@@ -66,8 +63,6 @@ export const fetchAllMaintenanceRecords = async (forceRefresh = false): Promise<
|
||||
* Perform the actual API request
|
||||
*/
|
||||
const performRequest = async (timestamp: number): Promise<MaintenanceItem[]> => {
|
||||
console.log('Making API request for maintenance records...');
|
||||
|
||||
// Create abort controller for request timeout
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
|
||||
@@ -95,7 +90,6 @@ const performRequest = async (timestamp: number): Promise<MaintenanceItem[]> =>
|
||||
// Update cache
|
||||
updateCache(normalizedData, timestamp);
|
||||
|
||||
console.log(`Successfully fetched ${normalizedData.length} maintenance records`);
|
||||
return normalizedData;
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -61,14 +61,12 @@ const getCompletedMaintenance = async (): Promise<MaintenanceItem[]> => {
|
||||
const createMaintenance = async (data: CreateMaintenanceInput): Promise<void> => {
|
||||
await createMaintenanceRecord(data);
|
||||
clearMaintenanceCache(); // Force cache refresh after creation
|
||||
console.log('Maintenance created and cache cleared');
|
||||
};
|
||||
|
||||
// Update an existing maintenance record
|
||||
const updateMaintenanceRecord = async (id: string, data: Partial<MaintenanceItem>): Promise<void> => {
|
||||
await updateMaintenance(id, data);
|
||||
clearMaintenanceCache(); // Force cache refresh after update
|
||||
console.log('Maintenance updated and cache cleared');
|
||||
};
|
||||
|
||||
// Refresh cache manually
|
||||
|
||||
Reference in New Issue
Block a user