Files
Auditor-Portal-Revamp/src/pages/activity-feeds/GlobalFeed.tsx

284 lines
11 KiB
TypeScript

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>
);
};