diff --git a/application/src/api/index.ts b/application/src/api/index.ts index 40c730b..83bbdef 100644 --- a/application/src/api/index.ts +++ b/application/src/api/index.ts @@ -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; \ No newline at end of file diff --git a/application/src/components/schedule-incident/hooks/useMaintenanceData.ts b/application/src/components/schedule-incident/hooks/useMaintenanceData.ts index 8da2bd9..a53ead8 100644 --- a/application/src/components/schedule-incident/hooks/useMaintenanceData.ts +++ b/application/src/components/schedule-incident/hooks/useMaintenanceData.ts @@ -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]); diff --git a/application/src/components/schedule-incident/maintenance/MaintenanceStatusChecker.tsx b/application/src/components/schedule-incident/maintenance/MaintenanceStatusChecker.tsx index 01e20f6..ace590b 100644 --- a/application/src/components/schedule-incident/maintenance/MaintenanceStatusChecker.tsx +++ b/application/src/components/schedule-incident/maintenance/MaintenanceStatusChecker.tsx @@ -19,74 +19,157 @@ export const MaintenanceStatusChecker = ({ const { toast } = useToast(); const checkedItemsRef = useRef>(new Set()); const notificationSentRef = useRef>(new Set()); + const intervalRef = useRef(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; -}; +}; \ No newline at end of file diff --git a/application/src/components/schedule-incident/maintenance/detail-dialog/MaintenanceDetailContent.tsx b/application/src/components/schedule-incident/maintenance/detail-dialog/MaintenanceDetailContent.tsx index 778c8f5..1eecbfe 100644 --- a/application/src/components/schedule-incident/maintenance/detail-dialog/MaintenanceDetailContent.tsx +++ b/application/src/components/schedule-incident/maintenance/detail-dialog/MaintenanceDetailContent.tsx @@ -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 = ({ /> ); -}; +}; \ No newline at end of file diff --git a/application/src/services/maintenance/api/maintenanceCache.ts b/application/src/services/maintenance/api/maintenanceCache.ts index a2a3003..964cd3b 100644 --- a/application/src/services/maintenance/api/maintenanceCache.ts +++ b/application/src/services/maintenance/api/maintenanceCache.ts @@ -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"); }; /** diff --git a/application/src/services/maintenance/api/maintenanceFetch.ts b/application/src/services/maintenance/api/maintenanceFetch.ts index b79116c..6056863 100644 --- a/application/src/services/maintenance/api/maintenanceFetch.ts +++ b/application/src/services/maintenance/api/maintenanceFetch.ts @@ -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 => { - 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 => // Update cache updateCache(normalizedData, timestamp); - console.log(`Successfully fetched ${normalizedData.length} maintenance records`); return normalizedData; } catch (error) { diff --git a/application/src/services/maintenance/maintenanceService.ts b/application/src/services/maintenance/maintenanceService.ts index b4e9d28..faf3fda 100644 --- a/application/src/services/maintenance/maintenanceService.ts +++ b/application/src/services/maintenance/maintenanceService.ts @@ -61,14 +61,12 @@ const getCompletedMaintenance = async (): Promise => { const createMaintenance = async (data: CreateMaintenanceInput): Promise => { 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): Promise => { await updateMaintenance(id, data); clearMaintenanceCache(); // Force cache refresh after update - console.log('Maintenance updated and cache cleared'); }; // Refresh cache manually