Initial commit of react-revamp for Auditor Portal

This commit is contained in:
2026-06-10 18:33:17 +05:30
commit a4c0892741
45 changed files with 7095 additions and 0 deletions

View File

@@ -0,0 +1,205 @@
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Box, Typography, Button, Paper, LinearProgress, Container } from '@mui/material';
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
import { activityFeedsService } from '../../api/activityFeedsService';
import { TopFilterBar } from '../../components/common/TopFilterBar';
import { useFeedStore } from '../../store/feedStore';
export const Dashboard: React.FC = () => {
const {
selectedSite, auditStatus, filterFromDate, filterTillDate,
filterMinChainage, filterMaxChainage,
selectedAssetIds, dashboardCache, setDashboardCache
} = useFeedStore();
const navigate = useNavigate();
const selectedSiteId = selectedSite?.site_id ?? '';
const assetIdsKey = selectedAssetIds.join(',');
// Build a key that uniquely identifies the current filter state
const currentFilterKey = `${selectedSiteId}|${auditStatus}|${filterFromDate}|${filterTillDate}|${assetIdsKey}|${filterMinChainage}|${filterMaxChainage}`;
// Initialise stats from cache immediately (no flicker on tab switch)
const [stats, setStats] = useState(() =>
dashboardCache.stats ?? { total: 0, audited: 0, pending: 0, anomaliesFound: 0 }
);
// Only show loading spinner if there's no cached result for these filters
const [loading, setLoading] = useState(dashboardCache.stats === null);
useEffect(() => {
// CACHE HIT: filters unchanged since last fetch — skip API call entirely
if (dashboardCache.stats !== null && currentFilterKey === `${dashboardCache.siteId}|${dashboardCache.auditStatus}|${dashboardCache.fromDate}|${dashboardCache.tillDate}|${dashboardCache.assetIds}|${dashboardCache.minChainage}|${dashboardCache.maxChainage}`) {
setStats(dashboardCache.stats);
setLoading(false);
return;
}
const fetchStats = async () => {
try {
setLoading(true);
const res: any = await activityFeedsService.getHistory({
date: '',
pageNo: 0,
pageSize: 200,
sortByDateAsc: false,
isRectificationEnabled: false,
isTrueFalse: auditStatus === 'False Audits' ? 'true' : 'both',
auto_audit: false,
siteIds: (!selectedSiteId || selectedSiteId === 'All Sites') ? '' : selectedSiteId,
assetsIds: assetIdsKey,
fromDate: filterFromDate,
tillDate: filterTillDate,
fromChainage: filterMinChainage,
toChainage: filterMaxChainage,
});
if (res) {
const anomalies = res.anomalies || [];
const newStats = {
total: res.count || anomalies.length,
audited: anomalies.filter((a: any) => a.IsAudited === 1).length,
pending: anomalies.filter((a: any) => a.IsAudited === 0).length,
anomaliesFound: anomalies.filter((a: any) => a.Audit_status === 1).length,
};
setStats(newStats);
// Persist to store cache so re-mounting the Dashboard skips this fetch
setDashboardCache({
siteId: selectedSiteId,
auditStatus,
fromDate: filterFromDate,
tillDate: filterTillDate,
assetIds: assetIdsKey,
minChainage: filterMinChainage,
maxChainage: filterMaxChainage,
stats: newStats,
});
}
} catch (err) {
console.error('Failed to load dashboard stats', err);
} finally {
setLoading(false);
}
};
fetchStats();
}, [currentFilterKey]); // eslint-disable-line react-hooks/exhaustive-deps
const progressPercent = stats.total > 0 ? Math.round((stats.audited / stats.total) * 100) : 0;
return (
<Box sx={{ width: '100%', maxWidth: '100vw', display: 'flex', flexDirection: 'column' }}>
<TopFilterBar />
<Box sx={{ px: 2, pt: 2, pb: 4, width: '100%' }}>
<Box sx={{ width: '100%' }}>
{/* Stats Row */}
<Box sx={{ display: 'flex', gap: 2, mb: 3 }}>
{/* Total Items */}
<Paper sx={{
flex: 1, p: 3, bgcolor: '#0f172a', border: '1px solid #1e293b', borderRadius: 2
}}>
<Typography variant="body2" sx={{ color: '#94a3b8', mb: 1 }}>Total Items</Typography>
<Typography variant="h4" sx={{ color: '#3b82f6', fontWeight: 'bold' }}>
{loading ? '...' : stats.total.toLocaleString()}
</Typography>
</Paper>
{/* Audited */}
<Paper sx={{
flex: 1, p: 3, bgcolor: '#052e16', border: '1px solid #14532d', borderRadius: 2
}}>
<Typography variant="body2" sx={{ color: '#94a3b8', mb: 1 }}>Audited</Typography>
<Typography variant="h4" sx={{ color: '#22c55e', fontWeight: 'bold' }}>
{loading ? '...' : stats.audited.toLocaleString()}
</Typography>
</Paper>
{/* Pending */}
<Paper sx={{
flex: 1, p: 3, bgcolor: '#422006', border: '1px solid #713f12', borderRadius: 2
}}>
<Typography variant="body2" sx={{ color: '#94a3b8', mb: 1 }}>Pending</Typography>
<Typography variant="h4" sx={{ color: '#eab308', fontWeight: 'bold' }}>
{loading ? '...' : stats.pending.toLocaleString()}
</Typography>
</Paper>
{/* Anomalies Found */}
<Paper sx={{
flex: 1, p: 3, bgcolor: '#450a0a', border: '1px solid #7f1d1d', borderRadius: 2
}}>
<Typography variant="body2" sx={{ color: '#94a3b8', mb: 1 }}>Anomalies Found</Typography>
<Typography variant="h4" sx={{ color: '#ef4444', fontWeight: 'bold' }}>
{loading ? '...' : stats.anomaliesFound.toLocaleString()}
</Typography>
</Paper>
</Box>
{/* Overall Progress */}
<Paper sx={{ p: 3, bgcolor: '#0f172a', border: '1px solid #1e293b', borderRadius: 2, mb: 3 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 'bold', color: '#f8fafc' }}>Overall Progress</Typography>
<Typography variant="body2" sx={{ color: '#94a3b8' }}>{stats.audited} / {stats.total} completed</Typography>
</Box>
<LinearProgress
variant="determinate"
value={progressPercent}
sx={{
height: 10,
borderRadius: 5,
bgcolor: '#1e293b',
'& .MuiLinearProgress-bar': { bgcolor: '#3b82f6', borderRadius: 5 }
}}
/>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 1 }}>
<Typography variant="caption" sx={{ color: '#64748b' }}>{progressPercent}% complete</Typography>
<Typography variant="caption" sx={{ color: '#64748b' }}>~{Math.ceil(stats.pending / 50)} sessions remaining at 50/session</Typography>
</Box>
</Paper>
{/* Next Session Info */}
<Paper sx={{ p: 2, px: 3, bgcolor: '#0f172a', border: '1px solid #1e293b', borderRadius: 2, mb: 2 }}>
<Typography variant="body1" sx={{ color: '#f8fafc' }}>
<Typography component="span" sx={{ color: '#3b82f6', fontWeight: 'bold' }}>Next session:</Typography> 50 items will be queued ({stats.pending} pending total)
</Typography>
</Paper>
{/* Warning Box */}
<Paper sx={{ p: 2, px: 3, bgcolor: '#450a0a', border: '1px solid #7f1d1d', borderRadius: 2, mb: 4, display: 'flex', alignItems: 'center', gap: 1 }}>
<WarningAmberIcon sx={{ color: '#ef4444', fontSize: 20 }} />
<Typography variant="body1" sx={{ color: '#f8fafc' }}>
<Typography component="span" sx={{ color: '#ef4444', fontWeight: 'bold' }}>181 high-priority items</Typography> will appear first in the queue
</Typography>
</Paper>
{/* Start Button */}
<Button
variant="contained"
fullWidth
onClick={() => navigate('/audit-session')}
sx={{
py: 2,
bgcolor: '#3b82f6',
'&:hover': { bgcolor: '#2563eb' },
textTransform: 'none',
fontSize: '1.1rem',
fontWeight: 'bold',
borderRadius: 2
}}
>
Start Session Audit Next 50 Items
</Button>
{/* Footer links */}
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 2 }}>
<Typography variant="caption" sx={{ color: '#475569' }}>Keyboard shortcuts available inside the audit view</Typography>
<Typography variant="caption" sx={{ color: '#64748b', textDecoration: 'underline', cursor: 'pointer', '&:hover': { color: '#f8fafc' } }}>Reset demo data</Typography>
</Box>
</Box>
</Box>
</Box>
);
};