UQZD6FFK2L7ZZQGBQZRV4UDTFZ7QHNRFESMUZRH4XK3HBBSGA6LAC LYPSC7BOH6T45FCPRHSCXILAJSJ74D5WSQTUIKPWD5ECXOYGUY5AC M3JUJ2WWZGCVMBITKRM5FUJMHFYL2QRMXJUVRUE4AC2RF74AOL5AC HBM7XFBGVMKW3P3VZFNQTJMINZ4DC3D4TZMT4TPTJRXC62HKZMMQC ROQGXQWL2V363K3W7TVVYKIAX4N4IWRERN5BJ7NYJRRVB6OMIJ4QC J2RLNDEXTGAV4BB6ANIIR7XJLJBHSB4NFQWSBWHNAFB6DMLGS5RAC 4RBE543WLHA7PIYT4W7YEJPF6XKZ2UGKPJBQ3CTLJ44AOMGHCEYQC const fetchFiles = async () => {if (!isAuthenticated || !clusterId) {if (!authLoading) {setLoading(false);}return;
const fetchFiles = React.useCallback(async (skipLoadingState = false) => {if (!isAuthenticated || !clusterId) {if (!authLoading) {setLoading(false);
// Build URL with filterslet url = `/api/files?clusterId=${encodeURIComponent(clusterId)}&page=${currentPage}&pageSize=100`;// Add night filtersswitch (nightFilter) {case 'solarNight':url += '&solarNight=true';break;case 'solarDay':url += '&solarNight=false';break;case 'civilNight':url += '&civilNight=true';break;case 'civilDay':url += '&civilNight=false';break;// 'none' doesn't add any filter parameters}// Add species filter if selectedif (speciesFilter) {url += `&speciesId=${encodeURIComponent(speciesFilter)}`;}
try {const accessToken = await getAccessToken();
const response = await fetch(url, {headers: {Authorization: `Bearer ${accessToken}`,},});
// Build URL with filterslet url = `/api/files?clusterId=${encodeURIComponent(clusterId)}&page=${currentPage}&pageSize=100`;// Add night filtersswitch (nightFilter) {case 'solarNight':url += '&solarNight=true';break;case 'solarDay':url += '&solarNight=false';break;case 'civilNight':url += '&civilNight=true';break;case 'civilDay':url += '&civilNight=false';break;// 'none' doesn't add any filter parameters}// Add species filter if selectedif (speciesFilter) {url += `&speciesId=${encodeURIComponent(speciesFilter)}`;}
if (!data.data || !Array.isArray(data.data) || !data.pagination) {throw new Error("Invalid response format");
if (!data.data || !Array.isArray(data.data) || !data.pagination) {throw new Error("Invalid response format");}// Process all metadata checks and state updates in a batch// to reduce multiple re-rendersconst validMetadata = data.data.some(file => {if (!file.metadata) return false;try {const meta = typeof file.metadata === 'string'? JSON.parse(file.metadata): file.metadata;return meta && typeof meta === 'object' && Object.keys(meta).length > 0;} catch {return false;
// Check if valid metadata is present in any fileconst validMetadata = data.data.some(file => {if (!file.metadata) return false;try {const meta = typeof file.metadata === 'string'? JSON.parse(file.metadata): file.metadata;return meta && typeof meta === 'object' && Object.keys(meta).length > 0;} catch {return false;}});// Check if any files have moth metadataconst validMothMetadata = data.data.some(file =>file.mothMetadata &&(file.mothMetadata.gain !== null ||file.mothMetadata.batteryV !== null ||file.mothMetadata.tempC !== null));// Check if any files have species informationconst validSpeciesData = data.data.some(file =>file.species && file.species.length > 0);setHasMetadata(validMetadata);setHasMothMetadata(validMothMetadata);setHasSpecies(validSpeciesData || speciesOptions.length > 0);setFiles(data.data);setPagination(data.pagination);} catch (err) {const errorMessage = err instanceof Error ? err.message : "Failed to fetch files";setError(errorMessage);} finally {setLoading(false);}};if (isAuthenticated && !authLoading) {fetchFiles();
});const validMothMetadata = data.data.some(file =>file.mothMetadata &&(file.mothMetadata.gain !== null ||file.mothMetadata.batteryV !== null ||file.mothMetadata.tempC !== null));const validSpeciesData = data.data.some(file =>file.species && file.species.length > 0);// Use React 18's batch updates to minimize re-renders// All of these state updates will be batched into a single rendersetHasMetadata(validMetadata);setHasMothMetadata(validMothMetadata);setHasSpecies(validSpeciesData || speciesOptions.length > 0);setFiles(data.data);setPagination(data.pagination);setError(null);} catch (err) {const errorMessage = err instanceof Error ? err.message : "Failed to fetch files";setError(errorMessage);// Clear data when there's an errorsetFiles([]);setPagination(null);} finally {setLoading(false);
// Effect to handle clusterId changes and initial loaduseEffect(() => {// Reset state when cluster changessetFiles([]);setPagination(null);setLoading(true);setError(null);// Use a small delay before fetching to allow the UI to show loading state// This prevents the brief flash of empty contentconst timeoutId = setTimeout(() => {if (isAuthenticated && !authLoading) {fetchFiles();}}, 50);return () => clearTimeout(timeoutId);}, [isAuthenticated, authLoading, clusterId, fetchFiles]);
// Effect to handle pagination and filter changesuseEffect(() => {// Skip the initial render caused by the dependency array initialization// Only fetch if we're not already in the initial loading state (first load)if (!loading && (currentPage > 1 || nightFilter !== 'none' || speciesFilter !== null)) {// Use skipLoadingState=true to avoid flashing when changing filters or pagination// This allows data to be replaced smoothly without showing loading indicatorsfetchFiles(true);}}, [currentPage, nightFilter, speciesFilter, fetchFiles, loading]);