auto refresh
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user