auto refresh

This commit is contained in:
2025-04-17 18:28:08 +08:00
parent 6025641ab1
commit 53e1611670
4 changed files with 820 additions and 26 deletions

View File

@@ -306,6 +306,8 @@ function AnalyticsContent() {
const [geoData, setGeoData] = useState<GeoData[]>([]);
const [deviceData, setDeviceData] = useState<DeviceAnalyticsType | null>(null);
const [events, setEvents] = useState<Event[]>([]);
const [isRefreshing, setIsRefreshing] = useState(false); // New state to track auto-refresh
const [lastRefreshed, setLastRefreshed] = useState<Date | null>(null); // Track when data was last refreshed
// 添加 Snackbar 状态
const [isSnackbarOpen, setIsSnackbarOpen] = useState(false);
@@ -449,12 +451,133 @@ function AnalyticsContent() {
setError(err instanceof Error ? err.message : 'An error occurred while fetching data');
} finally {
setLoading(false);
setIsRefreshing(false); // Reset refreshing state
setLastRefreshed(new Date()); // Update last refreshed timestamp
}
};
fetchData();
}, [dateRange, selectedTeamIds, selectedProjectIds, selectedTagNames, selectedSubpath, currentPage, pageSize, selectedShortUrl, shouldFetchData]);
// Add auto-refresh functionality
useEffect(() => {
if (!shouldFetchData) return; // Don't set up refresh until initial data load is triggered
// Function to trigger a refresh of data
const refreshData = () => {
console.log('Auto-refreshing analytics data...');
// Only refresh if not already loading or refreshing
if (!loading && !isRefreshing) {
setIsRefreshing(true);
// Create a new fetch function instead of reusing the effect's fetchData
const fetchRefreshedData = async () => {
try {
const startTime = format(dateRange.from, "yyyy-MM-dd'T'HH:mm:ss'Z'");
const endTime = format(dateRange.to, "yyyy-MM-dd'T'HH:mm:ss'Z'");
// 构建基础URL和查询参数
const baseUrl = '/api/events';
const params = new URLSearchParams({
startTime,
endTime,
page: currentPage.toString(),
pageSize: pageSize.toString()
});
// Duplicate the parameters logic from the main fetch effect
if (selectedShortUrl && selectedShortUrl.externalId) {
params.append('linkId', selectedShortUrl.externalId);
} else {
const savedExternalId = sessionStorage.getItem('current_shorturl_external_id');
if (savedExternalId) {
params.append('linkId', savedExternalId);
}
}
if (selectedTeamIds.length > 0) {
selectedTeamIds.forEach(teamId => {
params.append('teamId', teamId);
});
}
if (selectedProjectIds.length > 0) {
selectedProjectIds.forEach(projectId => {
params.append('projectId', projectId);
});
}
if (selectedTagNames.length > 0) {
selectedTagNames.forEach(tagName => {
params.append('tagName', tagName);
});
}
if (selectedSubpath) {
params.append('subpath', selectedSubpath);
}
// Build all URLs with the same parameters
const summaryUrl = `${baseUrl}/summary?${params.toString()}`;
const timeSeriesUrl = `${baseUrl}/time-series?${params.toString()}`;
const geoUrl = `${baseUrl}/geo?${params.toString()}`;
const devicesUrl = `${baseUrl}/devices?${params.toString()}`;
const eventsUrl = `${baseUrl}?${params.toString()}`;
// Parallel requests for all data
const [summaryRes, timeSeriesRes, geoRes, deviceRes, eventsRes] = await Promise.all([
fetch(summaryUrl),
fetch(timeSeriesUrl),
fetch(geoUrl),
fetch(devicesUrl),
fetch(eventsUrl)
]);
const [summaryData, timeSeriesData, geoData, deviceData, eventsData] = await Promise.all([
summaryRes.json(),
timeSeriesRes.json(),
geoRes.json(),
deviceRes.json(),
eventsRes.json()
]);
// Update state with fresh data
if (summaryRes.ok) setSummary(summaryData.data);
if (timeSeriesRes.ok) setTimeSeriesData(timeSeriesData.data);
if (geoRes.ok) setGeoData(geoData.data);
if (deviceRes.ok) setDeviceData(deviceData.data);
if (eventsRes.ok) {
setEvents(eventsData.data || []);
// Update pagination info
if (eventsData.meta) {
const totalCount = parseInt(String(eventsData.meta.total), 10);
if (!isNaN(totalCount)) {
setTotalEvents(totalCount);
}
}
}
} catch (err) {
console.error('Auto-refresh error:', err);
// Don't show errors during auto-refresh to avoid disrupting the UI
} finally {
setIsRefreshing(false);
setLastRefreshed(new Date()); // Update last refreshed timestamp
}
};
fetchRefreshedData();
}
};
// Set up the interval for auto-refresh every 30 seconds
const intervalId = setInterval(refreshData, 30000);
// Clean up the interval when the component unmounts
return () => {
clearInterval(intervalId);
};
}, [shouldFetchData, loading, isRefreshing, dateRange, selectedTeamIds, selectedProjectIds, selectedTagNames, selectedSubpath, currentPage, pageSize, selectedShortUrl]);
// Function to clear the shorturl filter
const handleClearShortUrlFilter = () => {
// 先清除 store 中的数据
@@ -499,7 +622,7 @@ function AnalyticsContent() {
setCurrentPage(1);
};
if (loading) {
if (loading && !isRefreshing) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500" />
@@ -532,8 +655,24 @@ function AnalyticsContent() {
)}
<div className="flex justify-between items-center mb-8">
<h1 className="text-2xl font-bold text-gray-900">Analytics Dashboard</h1>
<div>
<h1 className="text-2xl font-bold text-gray-900">Analytics Dashboard</h1>
{lastRefreshed && (
<div className="text-xs text-gray-500 mt-1">
Last updated: {format(lastRefreshed, 'MMM d, yyyy HH:mm:ss')}
{isRefreshing ? ' · Refreshing...' : ' · Auto-refreshes every 30 seconds'}
</div>
)}
</div>
<div className="flex flex-col gap-4 md:flex-row md:items-center">
{/* Show refresh indicator */}
{isRefreshing && (
<div className="flex items-center text-blue-600 text-sm">
<div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-blue-500 mr-2" />
<span>Refreshing data...</span>
</div>
)}
{/* 如果有选定的 shorturl可以显示一个提示显示更多详细信息 */}
{selectedShortUrl && (
<div className="bg-blue-100 text-blue-800 px-3 py-2 rounded-md text-sm flex flex-col">