diff --git a/.env.production b/.env.production index 89d5901..47bcdfa 100644 --- a/.env.production +++ b/.env.production @@ -1,2 +1,4 @@ -VITE_DASHBOARD_API_URL=https://python.seekright.com -VITE_AUDIT_API_URL=https://node-audit.seekright.com +# VITE_DASHBOARD_API_URL=https://python.seekright.com +# VITE_AUDIT_API_URL=https://node-audit.seekright.com +VITE_DASHBOARD_API_URL=https://sr-backend-api.takeleap.in +VITE_AUDIT_API_URL=https://sr-audit.takeleap.in diff --git a/src/App.tsx b/src/App.tsx index 0e51362..8eb4109 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -43,11 +43,13 @@ import { Login } from './pages/account/Login'; import { Dashboard } from './pages/dashboard/Dashboard'; import { GlobalFeed } from './pages/activity-feeds/GlobalFeed'; import { AuditSession } from './pages/audit-session/AuditSession'; +import { ToastProvider } from './components/common/ToastProvider'; export default function App() { return ( + {/* Public Routes */} diff --git a/src/api/activityFeedsService.ts b/src/api/activityFeedsService.ts index 4fdb2d1..a97dd47 100644 --- a/src/api/activityFeedsService.ts +++ b/src/api/activityFeedsService.ts @@ -62,8 +62,15 @@ export const activityFeedsService = { return response; }, + // Called after normal (non-rectification) true anomaly audit updateAnomalyInDashboard: async (payload: { site_id: string, anomaly_id: number[] }) => { - const response = await axiosClient.post(`/api/audit/auditor/anomaly/update`, payload); + const response = await axiosClient.post('/api/audit/auditor/anomaly/update', payload); + return response; + }, + + // Called after rectification true anomaly audit + markAnomalyCompleted: async (payload: { anomaly_id: number[], site_id: number }) => { + const response = await axiosClient.post('/api/audit/auditor/anomaly/completed', payload); return response; }, diff --git a/src/api/axiosClient.ts b/src/api/axiosClient.ts index bbd65d1..30c6435 100644 --- a/src/api/axiosClient.ts +++ b/src/api/axiosClient.ts @@ -1,5 +1,6 @@ import axios from 'axios'; import { useAuthStore } from '../store/authStore'; +import { useToastStore } from '../store/toastStore'; // You can move this to an environment variable later // Base URL is empty because we use specific proxy prefixes in the service calls @@ -15,6 +16,18 @@ export const axiosClient = axios.create({ // Request Interceptor: Attach token if available axiosClient.interceptors.request.use( (config) => { + // Rocket.Chat webhook: skip auth headers, skip dbName, let Vite proxy handle in dev + if (config.url?.startsWith('/api/rocketchat')) { + if (import.meta.env.DEV) { + // Leave URL as-is — Vite proxy rewrites /api/rocketchat → https://text.seekright.com + return config; + } + // Production: rewrite URL directly (no Vite proxy) + const rcUrl = import.meta.env.VITE_ROCKETCHAT_URL || 'https://text.seekright.com'; + config.url = config.url.replace('/api/rocketchat', rcUrl); + return config; + } + const user = useAuthStore.getState().user; if (user?.access_token) { config.headers['x-access-token'] = user.access_token; @@ -23,8 +36,6 @@ axiosClient.interceptors.request.use( config.params = { ...config.params, dbName: user.db_name }; } - // Automatically rewrite local proxy paths to the real URLs from environment variables - // This allows us to remove the Vite/Nginx proxy completely! if (config.url?.startsWith('/api/dashboard')) { const dashboardUrl = import.meta.env.VITE_DASHBOARD_API_URL || 'https://sr-backend-api.takeleap.in'; config.url = config.url.replace('/api/dashboard', dashboardUrl); @@ -43,22 +54,21 @@ axiosClient.interceptors.response.use( (response) => response.data, (error) => { if (!error.response) { - // Network offline or CORS error (status 0 in Angular) - alert('Seems one of the sub-application is offline. Please restart the application again.'); + useToastStore.getState().showToast('Network error — one of the services appears to be offline.', 'error'); return Promise.reject(error); } const { status, data } = error.response; if (status === 400) { - const message = data?.responseStatus?.responseMessage || data?.errorMessage || 'OOPS! Something wrong has happened. Please refresh the browser and try again.'; - alert(message); + const message = data?.responseStatus?.responseMessage || data?.errorMessage || 'Something went wrong. Please try again.'; + useToastStore.getState().showToast(message, 'error'); } else if (status === 401) { console.error('Login issue'); useAuthStore.getState().logout(); - window.location.href = '/login'; // Redirect to login + window.location.href = '/login'; } else { - alert('OOPS! Something wrong has happened. Please refresh the browser and try again.'); + useToastStore.getState().showToast('Something went wrong. Please refresh and try again.', 'error'); } return Promise.reject(error); diff --git a/src/components/common/ToastProvider.tsx b/src/components/common/ToastProvider.tsx new file mode 100644 index 0000000..0691a0a --- /dev/null +++ b/src/components/common/ToastProvider.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { Alert, Stack } from '@mui/material'; +import { useToastStore } from '../../store/toastStore'; + +export const ToastProvider: React.FC = () => { + const { toasts, removeToast } = useToastStore(); + + if (toasts.length === 0) return null; + + return ( + + {toasts.map((toast) => ( + removeToast(toast.id)} + sx={{ + minWidth: 280, + maxWidth: 420, + boxShadow: '0 4px 16px rgba(0,0,0,0.5)', + pointerEvents: 'all', + }} + > + {toast.message} + + ))} + + ); +}; diff --git a/src/components/common/TopFilterBar.tsx b/src/components/common/TopFilterBar.tsx index 7f4dd0c..bbe9114 100644 --- a/src/components/common/TopFilterBar.tsx +++ b/src/components/common/TopFilterBar.tsx @@ -155,6 +155,7 @@ export const TopFilterBar: React.FC = () => { } }; + // Used by RECTIFICATION HISTORY dialog — isRectificationTab: true → fetches with isRectificationEnabled=true const handleSelectDate = (date: string) => { const existingIndex = tabs.findIndex(t => t.date === date && t.isRectificationTab); if (existingIndex !== -1) { @@ -174,6 +175,26 @@ export const TopFilterBar: React.FC = () => { } }; + // Used by History dropdown — isRectificationTab: false → fetches with isRectificationEnabled=false + const handleHistoryDateSelect = (date: string) => { + const existingIndex = tabs.findIndex(t => t.date === date && !t.isRectificationTab); + if (existingIndex !== -1) { + setSelectedTabIndex(existingIndex); + } else { + addTab({ + date: date, + isRectificationTab: false, + totalAnomalyCount: 0, + anomalies: [], + anomaliesCopy: [], + selectedAnomalyIndex: 0, + filters: {}, + page: 0, + rowsPerPage: 20 + }); + } + }; + const handleViewHistory = () => { if (!selectedHistoryDate) return; handleSelectDate(selectedHistoryDate); @@ -241,7 +262,7 @@ export const TopFilterBar: React.FC = () => { onChange={(e) => { const date = e.target.value as string; if (date) { - handleSelectDate(date); + handleHistoryDateSelect(date); setHistoryDropdownValue(''); } }} diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index 175454f..134e34f 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -102,7 +102,7 @@ export const MainLayout: React.FC = () => { {/* Child routes get injected here */} - + ); diff --git a/src/pages/activity-feeds/AnomalyTable.tsx b/src/pages/activity-feeds/AnomalyTable.tsx index dd98b17..1d67c4e 100644 --- a/src/pages/activity-feeds/AnomalyTable.tsx +++ b/src/pages/activity-feeds/AnomalyTable.tsx @@ -1,9 +1,11 @@ import React from 'react'; -import { Box, Typography, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Slider, Checkbox } from '@mui/material'; +import { Box, Typography, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Slider, Checkbox, Skeleton } from '@mui/material'; import { useFeedStore } from '../../store/feedStore'; +const SKELETON_COLS = 13; + export const AnomalyTable: React.FC = () => { - const { tabs, selectedTabIndex, updateTab, filterPlaza, isRectificationEnabled } = useFeedStore(); + const { tabs, selectedTabIndex, updateTab, filterPlaza, isRectificationEnabled, isFeedLoading } = useFeedStore(); if (tabs.length === 0 || selectedTabIndex < 0) { return Loading feeds...; @@ -64,7 +66,16 @@ export const AnomalyTable: React.FC = () => { - {anomalies.map((anomaly: any, _filteredIndex: number) => { + {isFeedLoading && Array.from({ length: 8 }).map((_, i) => ( + + {Array.from({ length: SKELETON_COLS }).map((__, j) => ( + + + + ))} + + ))} + {!isFeedLoading && anomalies.map((anomaly: any, _filteredIndex: number) => { const realIndex = rawAnomalies.indexOf(anomaly); const isSelected = activeTab.selectedAnomalyIndex === realIndex; const assetName = anomaly?.Asset?.replace(/_/gi, ' ') || 'Unknown Asset'; diff --git a/src/pages/activity-feeds/GlobalFeed.tsx b/src/pages/activity-feeds/GlobalFeed.tsx index c74ea24..4c149b4 100644 --- a/src/pages/activity-feeds/GlobalFeed.tsx +++ b/src/pages/activity-feeds/GlobalFeed.tsx @@ -15,7 +15,8 @@ export const GlobalFeed: React.FC = () => { isRectificationEnabled, filterFromDate, filterTillDate, filterMinChainage, filterMaxChainage, - selectedAssetIds + selectedAssetIds, + setIsFeedLoading } = useFeedStore(); useEffect(() => { @@ -70,6 +71,7 @@ export const GlobalFeed: React.FC = () => { const siteIdParam = (!selectedSite || selectedSite?.site_id === 'All Sites') ? '' : String(selectedSite.site_id || ''); + setIsFeedLoading(true); try { const historyRes: any = await activityFeedsService.getHistory({ date: activeTab.date || '', @@ -106,6 +108,8 @@ export const GlobalFeed: React.FC = () => { } } catch (err) { console.error('[GlobalFeed] Failed to load history for active tab/filters change', err); + } finally { + setIsFeedLoading(false); } }; diff --git a/src/pages/audit-session/AuditSession.tsx b/src/pages/audit-session/AuditSession.tsx index f9217b6..6f65cf0 100644 --- a/src/pages/audit-session/AuditSession.tsx +++ b/src/pages/audit-session/AuditSession.tsx @@ -1,10 +1,12 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; -import { Box, Typography, Button, Chip, Switch, Dialog, DialogTitle, DialogContent, DialogActions, IconButton } from '@mui/material'; +import { Box, Typography, Button, Chip, Switch, Dialog, DialogTitle, DialogContent, DialogActions, IconButton, Select, MenuItem, TextField, CircularProgress } from '@mui/material'; import { useNavigate, useLocation } from 'react-router-dom'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import ZoomInIcon from '@mui/icons-material/ZoomIn'; import ZoomOutIcon from '@mui/icons-material/ZoomOut'; import { activityFeedsService } from '../../api/activityFeedsService'; +import { axiosClient } from '../../api/axiosClient'; +import { useToastStore } from '../../store/toastStore'; import { useAuthStore } from '../../store/authStore'; import { useFeedStore } from '../../store/feedStore'; @@ -59,6 +61,68 @@ export const AuditSession: React.FC = () => { const [isNotesFocused, setIsNotesFocused] = useState(false); const [showCompletionModal, setShowCompletionModal] = useState(false); const [isShortcutsDialogOpen, setIsShortcutsDialogOpen] = useState(false); + const [isReportDialogOpen, setIsReportDialogOpen] = useState(false); + const [reportDev, setReportDev] = useState(''); + const [reportMessage, setReportMessage] = useState(''); + const [reportSending, setReportSending] = useState(false); + + const DEVS = ['aromal', 'divya', 'Poonam', 'ravi', 'Kaushik', 'Saleth', 'Jagan', 'Yash', 'faraz_siddiqui', 'brinda.n', 'SasiVardhan', 'Shubham']; + const ROCKETCHAT_WEBHOOK_PATH = '/api/rocketchat/hooks/6a22ad73a88367bc6e1095a2/xcHkbDSce5rCwofdBZF3ri2rb6ntxT2mahqT6Gg7iKdectYk'; + + const handleSendReport = async () => { + if (!reportDev || !reportMessage.trim()) return; + setReportSending(true); + try { + const a = currentAnomaly; + const masterPrefix = isRectActive + ? 'https://sr-img.seekright.com/' + : 'https://auditor-master-images.seekright.com/SeekRight/'; + const testPrefix = 'https://dashboard-images.seekright.com/SeekRight/'; + + const masterImageUrl = a?.Frame_Master ? `${masterPrefix}${a.Frame_Master}` : null; + const anomalyImageUrl = a?.Frame_Test ? `${testPrefix}${a.Frame_Test}` : null; + const videoUrl = a?.video_url ? `${testPrefix}${a.video_url}` : null; + const extraImages: string[] = a?.Extra_Image + ? a.Extra_Image.split(',').map((src: string) => `${testPrefix}${src.trim()}`) + : []; + + const context = [ + `*Anomaly Report*`, + `• *ID:* #${a?.id}`, + `• *Asset:* ${a?.Asset?.replace(/_/gi, ' ') || 'N/A'}`, + `• *Plaza:* ${a?.Plaza || 'N/A'}`, + `• *Chainage:* ${a?.Chainage || 'N/A'}`, + `• *Side:* ${a?.Side || 'N/A'}`, + `• *Position:* ${a?.Pos || 'N/A'}`, + `• *Road:* ${a?.road || 'N/A'}`, + `• *Algorithm:* ${a?.Algorithm || 'N/A'}`, + `• *Created:* ${a?.Created_on ? new Date(a.Created_on).toLocaleString() : 'N/A'}`, + ``, + `*Media:*`, + masterImageUrl ? `• *Master Image:* ${masterImageUrl}` : `• *Master Image:* Not available`, + anomalyImageUrl ? `• *Anomaly Image:* ${anomalyImageUrl}` : `• *Anomaly Image:* Not available`, + videoUrl ? `• *Video:* ${videoUrl}` : `• *Video:* Not available`, + ...(extraImages.length > 1 + ? [`• *Extra Images:*`, ...extraImages.map((url, i) => ` ${i + 1}. ${url}`)] + : [] + ), + ``, + `*Message from ${user?.username || user?.email}:*`, + reportMessage.trim(), + ].join('\n'); + + await axiosClient.post(ROCKETCHAT_WEBHOOK_PATH, { channel: `@${reportDev}`, text: context }); + setIsReportDialogOpen(false); + setReportDev(''); + setReportMessage(''); + useToastStore.getState().showToast(`Message sent to @${reportDev}`, 'success'); + } catch (err) { + console.error('Failed to send report:', err); + useToastStore.getState().showToast('Failed to send message. Please try again.', 'error'); + } finally { + setReportSending(false); + } + }; const location = useLocation(); const { @@ -80,7 +144,7 @@ export const AuditSession: React.FC = () => { }; const [sessionPageNo, setSessionPageNo] = useState(() => location.state?.startPage || 0); const hasInitializedSession = useRef(false); - + const [masterZoom, setMasterZoom] = useState(1); const [anomalyZoom, setAnomalyZoom] = useState(1); @@ -124,7 +188,7 @@ export const AuditSession: React.FC = () => { setNotAnAnomalySubCategory(null); setItemAuditStates(res.anomalies.map((a: any) => a.IsAudited === 1 ? 'audited' : 'pending')); } else { - alert("No more anomalies left in the feed."); + useToastStore.getState().showToast('No more anomalies left in the feed.', 'info'); setShowCompletionModal(false); navigate('/activity-feeds'); } @@ -163,8 +227,8 @@ export const AuditSession: React.FC = () => { }); if (res && res.anomalies && res.anomalies.length > 0) { - updateTab(selectedTabIndex, { - anomalies: res.anomalies, + updateTab(selectedTabIndex, { + anomalies: res.anomalies, anomaliesCopy: res.anomalies, page: nextPage }); @@ -180,7 +244,7 @@ export const AuditSession: React.FC = () => { setAnomalyZoom(1); setItemAuditStates(res.anomalies.map((a: any) => a.IsAudited === 1 ? 'audited' : 'pending')); } else { - alert("No more anomalies left in the feed."); + useToastStore.getState().showToast('No more anomalies left in the feed.', 'info'); navigate('/activity-feeds'); } } catch (err) { @@ -204,8 +268,8 @@ export const AuditSession: React.FC = () => { setAnomalies(filteredAnomalies); // Find correct index in filtered list const origSelectedAnomaly = activeTab.anomalies[navState.startIndex || 0]; - const newIdx = origSelectedAnomaly - ? filteredAnomalies.findIndex((a: any) => (a.id === origSelectedAnomaly.id || a._id === origSelectedAnomaly._id)) + const newIdx = origSelectedAnomaly + ? filteredAnomalies.findIndex((a: any) => (a.id === origSelectedAnomaly.id || a._id === origSelectedAnomaly._id)) : 0; setCurrentIndex(newIdx !== -1 ? newIdx : 0); setItemAuditStates(filteredAnomalies.map((a: any) => a.IsAudited === 1 ? 'audited' : 'pending')); @@ -236,7 +300,7 @@ export const AuditSession: React.FC = () => { } }, [location.state, tabs, selectedTabIndex, loadSessionData, filterPlaza]); - const IMAGES_AWS_PREFIX = isRectActive + const IMAGES_AWS_PREFIX = isRectActive ? "https://sr-img.seekright.com/" : "https://auditor-master-images.seekright.com/SeekRight/"; const IMAGES_TEST_AUDIT_PREFIX = "https://dashboard-images.seekright.com/SeekRight/"; @@ -257,7 +321,7 @@ export const AuditSession: React.FC = () => { let masterImageSrc = ''; let latStr = 'N/A'; let longStr = 'E/A'; - + if (currentAnomaly) { if (currentAnomaly.Frame_Master) { masterImageSrc = IMAGES_AWS_PREFIX + currentAnomaly.Frame_Master; @@ -287,10 +351,10 @@ export const AuditSession: React.FC = () => { } } - const mediaItems: { type: 'image'|'video', url: string }[] = React.useMemo(() => { + const mediaItems: { type: 'image' | 'video', url: string }[] = React.useMemo(() => { if (!currentAnomaly) return []; - const items: { type: 'image'|'video', url: string }[] = []; - + const items: { type: 'image' | 'video', url: string }[] = []; + if (currentAnomaly.images && currentAnomaly.images.length > 0) { currentAnomaly.images.forEach((img: any) => { items.push({ type: 'image', url: IMAGES_TEST_AUDIT_PREFIX + img.src }); @@ -302,7 +366,7 @@ export const AuditSession: React.FC = () => { if (currentAnomaly.video_url) { items.push({ type: 'video', url: IMAGES_TEST_AUDIT_PREFIX + currentAnomaly.video_url }); } - + return items; }, [currentAnomaly]); @@ -341,20 +405,19 @@ export const AuditSession: React.FC = () => { return next; }); handleNext(); - }; const saveAuditState = useCallback(async ( - anomalyVal: boolean | null, - catVal: number | null, - notesVal: string, + }; const saveAuditState = useCallback(async ( + anomalyVal: boolean | null, + catVal: number | null, + notesVal: string, plantSub?: string | null, notAnAnomalySub?: string | null ) => { if (!currentAnomaly || !user) return; try { const isAnomalyBool = anomalyVal ?? true; - const auditStatusVal = isAnomalyBool ? 1 : 0; const categories = isAnomalyBool ? ANOMALY_CATEGORIES : SAFE_CATEGORIES; const matchedCategory = categories.find(c => c.id === catVal); - + let auditValueVal = matchedCategory ? matchedCategory.value : ''; if (isAnomalyBool && catVal === 1 && plantSub) { auditValueVal = plantSub; @@ -362,49 +425,55 @@ export const AuditSession: React.FC = () => { auditValueVal = notAnAnomalySub; } - // Construct properties matching Angular properties list: - // ["dbName", "isRectificationEnabled", "Audit_value", "Comments", "Audit_status", "Frame_Test", "Frame_Master", "Chainage", "Pos", "id", "road", "user_id", "Algorithm"] + // Body matches exactly what the API expects (dbName + isRectificationEnabled go in URL params via axiosClient) const auditPayload = { - dbName: user.db_name || '', - isRectificationEnabled: isRectActive, Audit_value: auditValueVal, Comments: notesVal || '', - Audit_status: auditStatusVal, + Audit_status: isAnomalyBool, // boolean true/false, not 1/0 Frame_Test: currentAnomaly.Frame_Test || '', Frame_Master: currentAnomaly.Frame_Master || '', Chainage: currentAnomaly.Chainage || '', Pos: currentAnomaly.Pos || '', id: currentAnomaly.id, road: currentAnomaly.road || 'mcw', + Algorithm: currentAnomaly.Algorithm || '', user_id: user.user_id || 0, - Algorithm: currentAnomaly.Algorithm || '' }; - // Call API 1: Update anomaly + // Call API 1: Save audit result await activityFeedsService.updateAnomaly([auditPayload], isRectActive); - // Call API 2: If true anomaly, update anomaly status in dashboard to notify client portal - if (auditStatusVal === 1) { - await activityFeedsService.updateAnomalyInDashboard({ - site_id: String(currentAnomaly.site_id), - anomaly_id: [currentAnomaly.id] - }); + // Call API 2: Notify dashboard — only for true anomaly, endpoint differs by mode + if (isAnomalyBool) { + if (isRectActive) { + // Rectification mode: /auditor/anomaly/completed, site_id as number + await activityFeedsService.markAnomalyCompleted({ + anomaly_id: [currentAnomaly.id], + site_id: currentAnomaly.site_id + }); + } else { + // Normal audit mode: /auditor/anomaly/update, site_id as string + await activityFeedsService.updateAnomalyInDashboard({ + site_id: String(currentAnomaly.site_id), + anomaly_id: [currentAnomaly.id] + }); + } } } catch (err) { console.error('Failed to submit audit state:', err); } - }, [currentAnomaly, user]); + }, [currentAnomaly, user, isRectActive]); const handleSaveAndNext = useCallback(async () => { if (!currentAnomaly) return; if (selectedCategory === 1) { if ((isAnomaly === null || isAnomaly === true) && !plantSubCategory) { - alert("Please select feature before submit."); + useToastStore.getState().showToast('Please select a feature before submitting.', 'warning'); return; } if (isAnomaly === false && !notAnAnomalySubCategory) { - alert("Please select feature before submit."); + useToastStore.getState().showToast('Please select a feature before submitting.', 'warning'); return; } } @@ -414,17 +483,17 @@ export const AuditSession: React.FC = () => { next[currentIndex] = 'audited'; return next; }); - + // Call the API await saveAuditState(isAnomaly, selectedCategory, notes, plantSubCategory, notAnAnomalySubCategory); - + handleNext(); }, [currentIndex, currentAnomaly, isAnomaly, selectedCategory, notes, plantSubCategory, notAnAnomalySubCategory, saveAuditState, handleNext]); // Quick Auto Advance save trigger const handleQuickSaveAndNext = useCallback(async ( - anomalyVal: boolean, - catVal: number, + anomalyVal: boolean, + catVal: number, plantSub?: string | null, notAnAnomalySub?: string | null ) => { @@ -437,7 +506,7 @@ export const AuditSession: React.FC = () => { // Call the API await saveAuditState(anomalyVal, catVal, notes, plantSub, notAnAnomalySub); - + if (currentIndex < anomalies.length - 1) { setCurrentIndex(prev => prev + 1); setSelectedCategory(null); @@ -451,10 +520,10 @@ export const AuditSession: React.FC = () => { }, [currentIndex, anomalies.length, currentAnomaly, notes, saveAuditState]); // Keyboard shortcuts const handleKeyDown = useCallback((e: KeyboardEvent) => { - if (isNotesFocused) return; + if (isNotesFocused || isReportDialogOpen || isShortcutsDialogOpen) return; const key = e.key.toLowerCase(); - + // Register active flash pulse setActiveKeyFlash(key); setTimeout(() => setActiveKeyFlash(null), 150); @@ -558,7 +627,7 @@ export const AuditSession: React.FC = () => { handleSkip(); break; } - }, [currentIndex, anomalies, isAnomaly, selectedCategory, notes, plantSubCategory, isQuickAudit, isNotesFocused, handleQuickSaveAndNext, handleSaveAndNext, handleSkip]); + }, [currentIndex, anomalies, isAnomaly, selectedCategory, notes, plantSubCategory, isQuickAudit, isNotesFocused, isReportDialogOpen, isShortcutsDialogOpen, handleQuickSaveAndNext, handleSaveAndNext, handleSkip]); useEffect(() => { window.addEventListener('keydown', handleKeyDown); @@ -596,25 +665,25 @@ export const AuditSession: React.FC = () => { }; return ( - - + {/* Header */} - + {/* Right Side: Sidebar */} - + {/* Quick-Audit Auto-Advance Toggle */} Quick Audit Auto-advances on keypress - handleToggleQuickAudit(e.target.checked)} @@ -821,15 +900,15 @@ export const AuditSession: React.FC = () => { A / S - - - - + + + + {/* Keyboard Shortcuts Dialog */} { {(() => { const navState = location.state as any; - return navState?.useExistingFeed + return navState?.useExistingFeed ? `You have successfully completed this feed session.` : `You have successfully audited this session of 50 items.`; })()} @@ -1204,9 +1354,9 @@ export const AuditSession: React.FC = () => { const feedPageSize = activeTab?.rowsPerPage || 20; return ( <> - -