diff --git a/src/pages/clients/ClientsOverview.tsx b/src/pages/clients/ClientsOverview.tsx index a02017a..1302402 100644 --- a/src/pages/clients/ClientsOverview.tsx +++ b/src/pages/clients/ClientsOverview.tsx @@ -9,17 +9,29 @@ import { auditSummaryService } from '../../api/auditSummaryService'; interface ClientBarData { client: string; + org_id: string | number; + db_name: string; pending: number; deleted: number; audited: number; total: number; } +interface SiteOption { + site_id: string; + site_name: string; +} + const todayStr = () => new Date().toISOString().split('T')[0]; +const pastWeekStr = () => { + const d = new Date(); + d.setDate(d.getDate() - 7); + return d.toISOString().split('T')[0]; +}; // Mirrors the site-parsing logic in MainLayout.tsx — /Master/site's response shape // varies (responseData[0].records, nested sub_sites, or a plain array). -const parseSiteIds = (sitesRes: any): string => { +const parseSites = (sitesRes: any): SiteOption[] => { let parsedSites: any[] = []; if (sitesRes?.responseData?.[0]?.records) { parsedSites = sitesRes.responseData[0].records; @@ -35,41 +47,44 @@ const parseSiteIds = (sitesRes: any): string => { } return parsedSites - .map((site) => site.site_id || (site.sub_sites || []).map((s: any) => s.site_id).filter(Boolean).join(',')) - .filter(Boolean) - .join(','); + .map((site) => { + const site_id = site.site_id || (site.sub_sites || []).map((s: any) => s.site_id).filter(Boolean).join(','); + const site_name = site.site_name || site.name || site.siteName || site_id; + return { site_id, site_name }; + }) + .filter((s) => s.site_id); }; export const ClientsOverview: React.FC = () => { - const [fromDate, setFromDate] = useState(todayStr()); + const [fromDate, setFromDate] = useState(pastWeekStr()); const [toDate, setToDate] = useState(todayStr()); const [isRectificationEnabled, setIsRectificationEnabled] = useState(false); const [chartData, setChartData] = useState([]); + const [sitesByOrg, setSitesByOrg] = useState>({}); const [selectedClient, setSelectedClient] = useState('All Clients'); + const [selectedSite, setSelectedSite] = useState('All Sites'); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [siteSummary, setSiteSummary] = useState(null); + const [siteLoading, setSiteLoading] = useState(false); + const [siteError, setSiteError] = useState(null); - const displayedData = useMemo( - () => (selectedClient === 'All Clients' ? chartData : chartData.filter((c) => c.client === selectedClient)), + const currentOrg = useMemo( + () => chartData.find((c) => c.client === selectedClient) || null, [chartData, selectedClient] ); - // Totals reflect the dropdown: sum of every client when "All Clients" is selected, - // or just the one selected client's counts otherwise. - const totals = useMemo( - () => - displayedData.reduce( - (acc, c) => ({ - pending: acc.pending + c.pending, - deleted: acc.deleted + c.deleted, - audited: acc.audited + c.audited, - total: acc.total + c.total, - }), - { pending: 0, deleted: 0, audited: 0, total: 0 } - ), - [displayedData] + const availableSites = useMemo( + () => (currentOrg ? sitesByOrg[currentOrg.org_id] || [] : []), + [currentOrg, sitesByOrg] ); + // Reset the site filter whenever the client changes — a site from the + // previous org shouldn't stay selected against a different org's list. + useEffect(() => { + setSelectedSite('All Sites'); + }, [selectedClient]); + const fetchAllClients = useCallback(async () => { setLoading(true); setError(null); @@ -79,9 +94,12 @@ export const ClientsOverview: React.FC = () => { const results = await Promise.allSettled( (orgs || []).map(async (org) => { const sitesRes = await activityFeedsService.getOrganizationSites(org.org_id); - const siteIds = parseSiteIds(sitesRes); + const sites = parseSites(sitesRes); + setSitesByOrg((prev) => ({ ...prev, [org.org_id]: sites })); + + const siteIds = sites.map((s) => s.site_id).join(','); if (!siteIds) { - return { client: org.org_name, pending: 0, deleted: 0, audited: 0, total: 0 }; + return { client: org.org_name, org_id: org.org_id, db_name: org.db_name, pending: 0, deleted: 0, audited: 0, total: 0 }; } const summary = await auditSummaryService.getSummary({ dbName: org.db_name, @@ -92,6 +110,8 @@ export const ClientsOverview: React.FC = () => { }); return { client: org.org_name, + org_id: org.org_id, + db_name: org.db_name, pending: summary.counts.pending, deleted: summary.counts.deleted, audited: summary.counts.audited, @@ -103,7 +123,7 @@ export const ClientsOverview: React.FC = () => { const data: ClientBarData[] = results.map((r, i) => r.status === 'fulfilled' ? r.value - : { client: orgs[i]?.org_name || `Client ${i}`, pending: 0, deleted: 0, audited: 0, total: 0 } + : { client: orgs[i]?.org_name || `Client ${i}`, org_id: orgs[i]?.org_id, db_name: orgs[i]?.db_name, pending: 0, deleted: 0, audited: 0, total: 0 } ); setChartData(data); } catch (err: any) { @@ -118,9 +138,80 @@ export const ClientsOverview: React.FC = () => { fetchAllClients(); }, [fetchAllClients]); + // Drill down to a single site within the selected client — a fresh, + // site-scoped call, since the org-level fetch above only has aggregates. + useEffect(() => { + if (selectedClient === 'All Clients' || selectedSite === 'All Sites' || !currentOrg) { + setSiteSummary(null); + return; + } + let cancelled = false; + const fetchSite = async () => { + setSiteLoading(true); + setSiteError(null); + try { + const summary = await auditSummaryService.getSummary({ + dbName: currentOrg.db_name, + site_id: selectedSite, + isRectificationEnabled, + fromDate, + toDate, + }); + if (cancelled) return; + const siteName = availableSites.find((s) => s.site_id === selectedSite)?.site_name || selectedSite; + setSiteSummary({ + client: siteName, + org_id: currentOrg.org_id, + db_name: currentOrg.db_name, + pending: summary.counts.pending, + deleted: summary.counts.deleted, + audited: summary.counts.audited, + total: summary.counts.total, + }); + } catch (err) { + if (cancelled) return; + console.error('Failed to load site summary', err); + setSiteError('Failed to load site data. Please try again.'); + } finally { + if (!cancelled) setSiteLoading(false); + } + }; + fetchSite(); + return () => { cancelled = true; }; + }, [selectedClient, selectedSite, currentOrg, availableSites, isRectificationEnabled, fromDate, toDate]); + + const displayedData = useMemo(() => { + if (selectedClient === 'All Clients') return chartData; + if (selectedSite === 'All Sites') return chartData.filter((c) => c.client === selectedClient); + return siteSummary ? [siteSummary] : []; + }, [chartData, selectedClient, selectedSite, siteSummary]); + + const effectiveLoading = loading || (selectedSite !== 'All Sites' && siteLoading); + const effectiveError = error || (selectedSite !== 'All Sites' ? siteError : null); + + // Totals reflect the dropdowns: sum of every client, just the selected + // client, or just the selected site's counts. + const totals = useMemo( + () => + displayedData.reduce( + (acc, c) => ({ + pending: acc.pending + c.pending, + deleted: acc.deleted + c.deleted, + audited: acc.audited + c.audited, + total: acc.total + c.total, + }), + { pending: 0, deleted: 0, audited: 0, total: 0 } + ), + [displayedData] + ); + + const totalsLabel = selectedSite !== 'All Sites' + ? (availableSites.find((s) => s.site_id === selectedSite)?.site_name || selectedSite) + : selectedClient; + return ( - + { ))} + + {selectedClient !== 'All Clients' && ( + + + + )} - Total Pending ({selectedClient}) + Total Pending ({totalsLabel}) - {loading ? '...' : totals.pending.toLocaleString()} + {effectiveLoading ? '...' : totals.pending.toLocaleString()} - Total Deleted ({selectedClient}) + Total Deleted ({totalsLabel}) - {loading ? '...' : totals.deleted.toLocaleString()} + {effectiveLoading ? '...' : totals.deleted.toLocaleString()} - Total Audited ({selectedClient}) + Total Audited ({totalsLabel}) - {loading ? '...' : totals.audited.toLocaleString()} + {effectiveLoading ? '...' : totals.audited.toLocaleString()} - Grand Total ({selectedClient}) + Grand Total ({totalsLabel}) - {loading ? '...' : totals.total.toLocaleString()} + {effectiveLoading ? '...' : totals.total.toLocaleString()} @@ -212,16 +318,17 @@ export const ClientsOverview: React.FC = () => { {isRectificationEnabled ? 'Rectification' : 'Audit'} Counts by Client {selectedClient !== 'All Clients' ? ` — ${selectedClient}` : ''} + {selectedSite !== 'All Sites' ? ` — ${totalsLabel}` : ''} - {loading ? ( + {effectiveLoading ? ( - ) : error ? ( - {error} + ) : effectiveError ? ( + {effectiveError} ) : displayedData.length === 0 ? ( - No clients found + No data found ) : (