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 filters
let url = `/api/files?clusterId=${encodeURIComponent(clusterId)}&page=${currentPage}&pageSize=100`;
// Add night filters
switch (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 selected
if (speciesFilter) {
url += `&speciesId=${encodeURIComponent(speciesFilter)}`;
}
try {
const accessToken = await getAccessToken();
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
// Build URL with filters
let url = `/api/files?clusterId=${encodeURIComponent(clusterId)}&page=${currentPage}&pageSize=100`;
// Add night filters
switch (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 selected
if (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-renders
const 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 file
const 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 metadata
const 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 information
const 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 render
setHasMetadata(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 error
setFiles([]);
setPagination(null);
} finally {
setLoading(false);
// Effect to handle clusterId changes and initial load
useEffect(() => {
// Reset state when cluster changes
setFiles([]);
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 content
const timeoutId = setTimeout(() => {
if (isAuthenticated && !authLoading) {
fetchFiles();
}
}, 50);
return () => clearTimeout(timeoutId);
}, [isAuthenticated, authLoading, clusterId, fetchFiles]);
// Effect to handle pagination and filter changes
useEffect(() => {
// 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 indicators
fetchFiles(true);
}
}, [currentPage, nightFilter, speciesFilter, fetchFiles, loading]);