Initial commit of react-revamp for Auditor Portal
This commit is contained in:
283
src/pages/activity-feeds/GlobalFeed.tsx
Normal file
283
src/pages/activity-feeds/GlobalFeed.tsx
Normal file
@@ -0,0 +1,283 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Box, Typography, TablePagination, Button, FormControl, Select, MenuItem } from '@mui/material';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import { TopFilterBar } from '../../components/common/TopFilterBar';
|
||||
import { FeedTabs } from './FeedTabs';
|
||||
import { AnomalyTable } from './AnomalyTable';
|
||||
import { PreviewPanel } from './PreviewPanel';
|
||||
import { useFeedStore } from '../../store/feedStore';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import { activityFeedsService } from '../../api/activityFeedsService';
|
||||
export const GlobalFeed: React.FC = () => {
|
||||
const {
|
||||
setAssets, setAssetTypes, addTab, tabs, selectedTabIndex, updateTab,
|
||||
sites, setSites, selectedSite, setSelectedSite, auditStatus,
|
||||
isInitialized, setIsInitialized,
|
||||
lastFetchedFilterKey, setLastFetchedFilterKey
|
||||
} = useFeedStore();
|
||||
const { user } = useAuthStore();
|
||||
// Ref to skip the first run of the site/auditStatus effect
|
||||
const isFirstRender = React.useRef(true);
|
||||
useEffect(() => {
|
||||
// Initial Load
|
||||
const loadInitialData = async () => {
|
||||
try {
|
||||
const assetRes: any = await activityFeedsService.getAssets();
|
||||
if (assetRes && assetRes.asset_types) {
|
||||
// Map assets mimicking the Angular logic
|
||||
let flattenedAssets: any[] = [];
|
||||
assetRes.asset_types.forEach((type: any) => {
|
||||
const mapped = type.assets.map((a: any) => ({
|
||||
id: a.asset_id,
|
||||
name: a.asset_name,
|
||||
asset_type_id: type.type_id
|
||||
}));
|
||||
flattenedAssets = [...flattenedAssets, ...mapped];
|
||||
});
|
||||
setAssets(flattenedAssets);
|
||||
setAssetTypes(assetRes.asset_types); // store raw grouped data for filters dialog
|
||||
}
|
||||
|
||||
// Fetch Sites
|
||||
let defaultSite = null;
|
||||
if (user?.org_id) {
|
||||
try {
|
||||
const sitesRes: any = await activityFeedsService.getOrganizationSites(user.org_id);
|
||||
let parsedSites: any[] = [];
|
||||
if (sitesRes?.responseData?.[0]?.records) {
|
||||
parsedSites = sitesRes.responseData[0].records;
|
||||
// Handle nested array response [ [ {site1}, {site2} ] ]
|
||||
if (parsedSites.length === 1 && Array.isArray(parsedSites[0])) {
|
||||
parsedSites = parsedSites[0];
|
||||
} else if (parsedSites.length > 0 && Array.isArray(parsedSites[0])) {
|
||||
parsedSites = parsedSites.flat();
|
||||
}
|
||||
} else if (Array.isArray(sitesRes)) {
|
||||
parsedSites = sitesRes;
|
||||
} else if (sitesRes?.data && Array.isArray(sitesRes.data)) {
|
||||
parsedSites = sitesRes.data;
|
||||
}
|
||||
|
||||
if (parsedSites.length > 0) {
|
||||
// Filter to ensure we only show sites for the currently selected org
|
||||
parsedSites = parsedSites.filter(site => {
|
||||
let siteOrgId = site.org_id;
|
||||
if (siteOrgId === undefined && site.sub_sites && site.sub_sites.length > 0) {
|
||||
siteOrgId = site.sub_sites[0].org_id;
|
||||
}
|
||||
|
||||
if (siteOrgId !== undefined && siteOrgId !== null) {
|
||||
return String(siteOrgId) === String(user.org_id);
|
||||
}
|
||||
return true;
|
||||
}).map(site => {
|
||||
// Normalize site_id: if missing, aggregate from sub_sites
|
||||
if (!site.site_id && site.sub_sites && site.sub_sites.length > 0) {
|
||||
site.site_id = site.sub_sites.map((sub: any) => sub.site_id).filter(Boolean).join(',');
|
||||
}
|
||||
return site;
|
||||
});
|
||||
|
||||
setSites(parsedSites);
|
||||
defaultSite = { site_id: 'All Sites', site_name: 'All Sites' };
|
||||
setSelectedSite(defaultSite);
|
||||
}
|
||||
} catch (siteErr) {
|
||||
console.error("Failed to fetch sites", siteErr);
|
||||
}
|
||||
}
|
||||
|
||||
// Add default Unaudited tab if empty and fetch data
|
||||
if (useFeedStore.getState().tabs.length === 0) {
|
||||
addTab({
|
||||
date: '', // Unaudited
|
||||
isRectificationTab: false,
|
||||
totalAnomalyCount: 0,
|
||||
anomalies: [],
|
||||
anomaliesCopy: [],
|
||||
selectedAnomalyIndex: 0,
|
||||
filters: {},
|
||||
page: 0,
|
||||
rowsPerPage: 20
|
||||
});
|
||||
|
||||
try {
|
||||
const historyRes: any = await activityFeedsService.getHistory({
|
||||
date: '',
|
||||
pageNo: 0,
|
||||
pageSize: 20,
|
||||
siteIds: defaultSite?.site_id === 'All Sites' ? '' : (defaultSite?.site_id || ''),
|
||||
isTrueFalse: auditStatus === 'False Audits' ? 'true' : 'both',
|
||||
auto_audit: false,
|
||||
isRectificationEnabled: false
|
||||
});
|
||||
if (historyRes && historyRes.anomalies) {
|
||||
updateTab(0, {
|
||||
anomalies: historyRes.anomalies,
|
||||
anomaliesCopy: historyRes.anomalies,
|
||||
totalAnomalyCount: historyRes.count || historyRes.anomalies.length
|
||||
});
|
||||
}
|
||||
} catch (historyErr) {
|
||||
console.error("[GlobalFeed] Failed to load history", historyErr);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load initial feed data", error);
|
||||
} finally {
|
||||
setIsInitialized(true); // Mark as initialized regardless, to prevent retry loops
|
||||
}
|
||||
};
|
||||
|
||||
// GUARD: Only run initial load once across tab switches
|
||||
if (useFeedStore.getState().isInitialized) {
|
||||
return;
|
||||
}
|
||||
loadInitialData();
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Fetch data when selectedSite or auditStatus changes
|
||||
useEffect(() => {
|
||||
// GUARD: Skip if tabs are not initialized (initial load hasn't finished)
|
||||
if (useFeedStore.getState().tabs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build a key for the current filter state
|
||||
const filterKey = `${selectedSite?.site_id ?? ''}|${auditStatus}`;
|
||||
|
||||
// CACHE HIT: This exact filter combo was already fetched — skip re-fetch on remount
|
||||
if (filterKey === lastFetchedFilterKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchFilteredHistory = async () => {
|
||||
const state = useFeedStore.getState();
|
||||
const currentTabIndex = state.selectedTabIndex > -1 ? state.selectedTabIndex : 0;
|
||||
const activeTab = state.tabs[currentTabIndex];
|
||||
const siteIdParam = (!selectedSite || selectedSite?.site_id === 'All Sites')
|
||||
? '' : String(selectedSite.site_id || '');
|
||||
|
||||
try {
|
||||
const historyRes: any = await activityFeedsService.getHistory({
|
||||
date: activeTab?.date || '',
|
||||
pageNo: 0,
|
||||
pageSize: 20,
|
||||
siteIds: siteIdParam,
|
||||
isTrueFalse: auditStatus === 'False Audits' ? 'true' : 'both',
|
||||
auto_audit: false,
|
||||
isRectificationEnabled: false,
|
||||
...(activeTab?.filters || {})
|
||||
});
|
||||
if (historyRes && historyRes.anomalies) {
|
||||
useFeedStore.getState().updateTab(currentTabIndex, {
|
||||
anomalies: historyRes.anomalies,
|
||||
anomaliesCopy: historyRes.anomalies,
|
||||
totalAnomalyCount: historyRes.count || historyRes.anomalies.length
|
||||
});
|
||||
setLastFetchedFilterKey(filterKey); // mark this combo as fetched
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[GlobalFeed] Failed to load history for site/audit status change', err);
|
||||
}
|
||||
};
|
||||
|
||||
fetchFilteredHistory();
|
||||
// Use primitive/stable values to avoid spurious re-runs.
|
||||
// selectedSite?.site_id and auditStatus are both strings/primitives.
|
||||
}, [selectedSite?.site_id, auditStatus]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const handlePageChange = async (event: unknown, newPage: number) => {
|
||||
if (selectedTabIndex < 0 || tabs.length === 0) return;
|
||||
const activeTab = tabs[selectedTabIndex];
|
||||
const allSiteIds = sites.map(s => s.site_id).filter(Boolean).join(',');
|
||||
const rowsPerPage = activeTab.rowsPerPage || 20;
|
||||
|
||||
try {
|
||||
const historyRes: any = await activityFeedsService.getHistory({
|
||||
date: activeTab?.date || '',
|
||||
pageNo: newPage * rowsPerPage,
|
||||
pageSize: rowsPerPage,
|
||||
siteIds: selectedSite?.site_id === 'All Sites' ? '' : (selectedSite?.site_id || ''),
|
||||
isTrueFalse: auditStatus === 'False Audits' ? 'true' : 'both',
|
||||
auto_audit: false,
|
||||
isRectificationEnabled: false,
|
||||
...(activeTab?.filters || {})
|
||||
});
|
||||
if (historyRes && historyRes.anomalies) {
|
||||
updateTab(selectedTabIndex, {
|
||||
anomalies: historyRes.anomalies,
|
||||
anomaliesCopy: historyRes.anomalies,
|
||||
totalAnomalyCount: historyRes.count || historyRes.anomalies.length,
|
||||
page: newPage,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to load history for page change", err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRowsPerPageChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (selectedTabIndex < 0 || tabs.length === 0) return;
|
||||
const activeTab = tabs[selectedTabIndex];
|
||||
const allSiteIds = sites.map(s => s.site_id).filter(Boolean).join(',');
|
||||
const newRowsPerPage = parseInt(event.target.value, 10);
|
||||
const newPage = 0;
|
||||
|
||||
try {
|
||||
const historyRes: any = await activityFeedsService.getHistory({
|
||||
date: activeTab?.date || '',
|
||||
pageNo: newPage * newRowsPerPage,
|
||||
pageSize: newRowsPerPage,
|
||||
siteIds: selectedSite?.site_id === 'All Sites' ? '' : (selectedSite?.site_id || ''),
|
||||
isTrueFalse: auditStatus === 'False Audits' ? 'true' : 'both',
|
||||
auto_audit: false,
|
||||
isRectificationEnabled: false,
|
||||
...(activeTab?.filters || {})
|
||||
});
|
||||
if (historyRes && historyRes.anomalies) {
|
||||
updateTab(selectedTabIndex, {
|
||||
anomalies: historyRes.anomalies,
|
||||
anomaliesCopy: historyRes.anomalies,
|
||||
totalAnomalyCount: historyRes.count || historyRes.anomalies.length,
|
||||
page: newPage,
|
||||
rowsPerPage: newRowsPerPage,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to load history for rowsPerPage change", err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ height: 'calc(100vh - 64px)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
||||
|
||||
<TopFilterBar />
|
||||
<FeedTabs />
|
||||
|
||||
<Box sx={{ display: 'flex', flex: 1, overflow: 'hidden', bgcolor: 'background.default' }}>
|
||||
{/* Left side Table */}
|
||||
<Box sx={{ flex: 1, overflow: 'auto', px: 2, display: 'flex', flexDirection: 'column' }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center', py: 0 }}>
|
||||
<TablePagination
|
||||
component="div"
|
||||
count={tabs[selectedTabIndex]?.totalAnomalyCount || 0}
|
||||
page={tabs[selectedTabIndex]?.page || 0}
|
||||
onPageChange={handlePageChange}
|
||||
rowsPerPage={tabs[selectedTabIndex]?.rowsPerPage || 20}
|
||||
onRowsPerPageChange={handleRowsPerPageChange}
|
||||
rowsPerPageOptions={[10, 20, 50, 100]}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ flex: 1, overflow: 'auto' }}>
|
||||
<AnomalyTable />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Right side Preview Panel */}
|
||||
<PreviewPanel />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user