Persist selections across pages, add Select All for Granite Loader

- Selections persist when paging through the device table
- Select All button fetches all hostnames for the active sheet
- Granite Loader button fetches full device data for all selected
  (not just current page) before opening the modal
- Clear selection button when items are selected across pages
- Page checkbox adds to existing selection instead of replacing
This commit is contained in:
Jordan Ramos
2026-08-05 11:51:00 -06:00
parent c7a4e411ce
commit 68431f2d3f

View File

@@ -68,6 +68,7 @@ export default function GraniteHygieneSection({ activeTeam, teamScope: _teamScop
setActiveSheet(sheetName);
setDevicesPage(1);
setDevicesSearch('');
setSelectedDevices(new Set());
fetchDevices(sheetName, 1, '');
}
};
@@ -83,6 +84,45 @@ export default function GraniteHygieneSection({ activeTeam, teamScope: _teamScop
fetchDevices(activeSheet, 1, val);
};
// Select all devices for the active sheet (fetches all hostnames)
const selectAll = async () => {
if (!activeSheet) return;
try {
const params = new URLSearchParams({ sheet: activeSheet, page: '1', page_size: '10000' });
if (activeTeam) params.set('team', activeTeam);
const res = await fetch(`${API_BASE}/compliance/supplemental/items?${params}`, { credentials: 'include' });
if (res.ok) {
const data = await res.json();
setSelectedDevices(new Set((data.devices || []).map(d => d.hostname)));
}
} catch (_err) { /* silent */ }
};
// Open Loader with all selected devices (fetches full data for all selected)
const openLoaderWithSelected = async () => {
if (selectedDevices.size === 0) return;
// If all selected are on current page, use local data
const allOnPage = [...selectedDevices].every(h => devices.some(d => d.hostname === h));
if (allOnPage) {
setShowLoaderModal(true);
return;
}
// Fetch all selected devices from API
try {
const params = new URLSearchParams({ sheet: activeSheet, page: '1', page_size: '10000' });
if (activeTeam) params.set('team', activeTeam);
const res = await fetch(`${API_BASE}/compliance/supplemental/items?${params}`, { credentials: 'include' });
if (res.ok) {
const data = await res.json();
const allDevices = (data.devices || []).filter(d => selectedDevices.has(d.hostname));
setAllSelectedDeviceData(allDevices);
}
} catch (_err) { /* silent */ }
setShowLoaderModal(true);
};
const [allSelectedDeviceData, setAllSelectedDeviceData] = useState(null);
// Don't render if no data
if (!summary || (summary.sheets && summary.sheets.length === 0)) {
if (loading) return null;
@@ -170,7 +210,7 @@ export default function GraniteHygieneSection({ activeTeam, teamScope: _teamScop
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
{selectedDevices.size > 0 && (
<button
onClick={() => setShowLoaderModal(true)}
onClick={openLoaderWithSelected}
style={styles.loaderBtn}
>
<FileSpreadsheet style={{ width: '12px', height: '12px' }} />
@@ -208,10 +248,15 @@ export default function GraniteHygieneSection({ activeTeam, teamScope: _teamScop
if (devices.every(d => selectedDevices.has(d.hostname))) {
setSelectedDevices(new Set());
} else {
setSelectedDevices(new Set(devices.map(d => d.hostname)));
setSelectedDevices(prev => {
const next = new Set(prev);
devices.forEach(d => next.add(d.hostname));
return next;
});
}
}}
style={{ accentColor: '#14B8A6', cursor: 'pointer' }}
title={`Select page (${devices.length})`}
/>
</th>
<th style={styles.th}>Hostname</th>
@@ -281,6 +326,20 @@ export default function GraniteHygieneSection({ activeTeam, teamScope: _teamScop
>
Next
</button>
<button
onClick={selectAll}
style={{ ...styles.pageBtn, color: '#14B8A6', borderColor: 'rgba(20,184,166,0.4)' }}
>
Select All ({devicesTotal})
</button>
{selectedDevices.size > 0 && selectedDevices.size !== devices.length && (
<button
onClick={() => setSelectedDevices(new Set())}
style={{ ...styles.pageBtn, color: '#64748B' }}
>
Clear ({selectedDevices.size})
</button>
)}
</div>
)}
</>
@@ -302,13 +361,16 @@ export default function GraniteHygieneSection({ activeTeam, teamScope: _teamScop
{/* Granite Loader Modal */}
<LoaderModal
isOpen={showLoaderModal}
onClose={() => { setShowLoaderModal(false); setSelectedDevices(new Set()); }}
initialDevices={showLoaderModal ? devices.filter(d => selectedDevices.has(d.hostname)).map(d => ({
ip_address: d.ip_address || '',
hostname: d.hostname || '',
host_id: null,
equip_inst_id: d.equip_inst_id || '',
})) : null}
onClose={() => { setShowLoaderModal(false); setAllSelectedDeviceData(null); }}
initialDevices={showLoaderModal ? (() => {
const source = allSelectedDeviceData || devices.filter(d => selectedDevices.has(d.hostname));
return source.map(d => ({
ip_address: d.ip_address || '',
hostname: d.hostname || '',
host_id: null,
equip_inst_id: d.equip_inst_id || '',
}));
})() : null}
/>
</div>
);