dashboard data
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { addDays, format } from 'date-fns';
|
||||
import { format } from 'date-fns';
|
||||
import { DateRangePicker } from '@/app/components/ui/DateRangePicker';
|
||||
import TimeSeriesChart from '@/app/components/charts/TimeSeriesChart';
|
||||
import GeoAnalytics from '@/app/components/analytics/GeoAnalytics';
|
||||
@@ -21,57 +21,117 @@ export default function DashboardPage() {
|
||||
const [geoData, setGeoData] = useState<GeoData[]>([]);
|
||||
const [deviceData, setDeviceData] = useState<DeviceAnalyticsType | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// 获取统计数据
|
||||
const fetchSummary = 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'");
|
||||
|
||||
// 并行获取所有数据
|
||||
const [summaryRes, timeSeriesRes, geoRes, deviceRes] = await Promise.all([
|
||||
fetch(`/api/events/summary?startTime=${startTime}&endTime=${endTime}`),
|
||||
fetch(`/api/events/time-series?startTime=${startTime}&endTime=${endTime}`),
|
||||
fetch(`/api/events/geo?startTime=${startTime}&endTime=${endTime}`),
|
||||
fetch(`/api/events/devices?startTime=${startTime}&endTime=${endTime}`)
|
||||
]);
|
||||
const response = await fetch(
|
||||
`/api/events/summary?startTime=${startTime}&endTime=${endTime}`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
const [summaryData, timeSeriesData, geoData, deviceData] = await Promise.all([
|
||||
summaryRes.json(),
|
||||
timeSeriesRes.json(),
|
||||
geoRes.json(),
|
||||
deviceRes.json()
|
||||
]);
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to fetch summary');
|
||||
}
|
||||
|
||||
if (!summaryRes.ok) throw new Error(summaryData.error || 'Failed to fetch summary data');
|
||||
if (!timeSeriesRes.ok) throw new Error(timeSeriesData.error || 'Failed to fetch time series data');
|
||||
if (!geoRes.ok) throw new Error(geoData.error || 'Failed to fetch geo data');
|
||||
if (!deviceRes.ok) throw new Error(deviceData.error || 'Failed to fetch device data');
|
||||
|
||||
setSummary(summaryData);
|
||||
setTimeSeriesData(timeSeriesData.data);
|
||||
setGeoData(geoData.data);
|
||||
setDeviceData(deviceData.data);
|
||||
if (data.success && data.data) {
|
||||
console.log('Summary data:', data.data); // 添加日志
|
||||
setSummary(data.data);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'An error occurred while fetching data');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
console.error('Error fetching summary:', err);
|
||||
setError(err instanceof Error ? err.message : 'Failed to load summary');
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, [dateRange]);
|
||||
// 获取时间序列数据
|
||||
const fetchTimeSeriesData = 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'");
|
||||
|
||||
if (loading) {
|
||||
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" />
|
||||
</div>
|
||||
const response = await fetch(
|
||||
`/api/events/time-series?startTime=${startTime}&endTime=${endTime}&granularity=day`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to fetch time series data');
|
||||
}
|
||||
|
||||
if (data.success && Array.isArray(data.data)) {
|
||||
setTimeSeriesData(data.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching time series:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取地理位置数据
|
||||
const fetchGeoData = 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'");
|
||||
|
||||
const response = await fetch(
|
||||
`/api/events/geo?startTime=${startTime}&endTime=${endTime}`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to fetch geo data');
|
||||
}
|
||||
|
||||
if (data.success && Array.isArray(data.data)) {
|
||||
setGeoData(data.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching geo data:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取设备数据
|
||||
const fetchDeviceData = 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'");
|
||||
|
||||
const response = await fetch(
|
||||
`/api/events/devices?startTime=${startTime}&endTime=${endTime}`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to fetch device data');
|
||||
}
|
||||
|
||||
if (data.success && data.data) {
|
||||
setDeviceData(data.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching device data:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载所有数据
|
||||
const loadAllData = async () => {
|
||||
setLoading(true);
|
||||
await Promise.all([
|
||||
fetchSummary(),
|
||||
fetchTimeSeriesData(),
|
||||
fetchGeoData(),
|
||||
fetchDeviceData()
|
||||
]);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
// 当日期范围改变时重新加载数据
|
||||
useEffect(() => {
|
||||
loadAllData();
|
||||
}, [dateRange]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
@@ -90,50 +150,82 @@ export default function DashboardPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{summary && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">Total Events</h3>
|
||||
<p className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
{typeof summary.totalEvents === 'number' ? summary.totalEvents.toLocaleString() : summary.totalEvents}
|
||||
{loading ? (
|
||||
<span className="text-gray-400">Loading...</span>
|
||||
) : (
|
||||
summary?.totalEvents?.toLocaleString() || '0'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">Unique Visitors</h3>
|
||||
<p className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
{typeof summary.uniqueVisitors === 'number' ? summary.uniqueVisitors.toLocaleString() : summary.uniqueVisitors}
|
||||
{loading ? (
|
||||
<span className="text-gray-400">Loading...</span>
|
||||
) : (
|
||||
summary?.uniqueVisitors?.toLocaleString() || '0'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">Total Conversions</h3>
|
||||
<p className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
{typeof summary.totalConversions === 'number' ? summary.totalConversions.toLocaleString() : summary.totalConversions}
|
||||
{loading ? (
|
||||
<span className="text-gray-400">Loading...</span>
|
||||
) : (
|
||||
summary?.totalConversions?.toLocaleString() || '0'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">Avg. Time Spent</h3>
|
||||
<p className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||
{summary.averageTimeSpent?.toFixed(1) || '0'}s
|
||||
{loading ? (
|
||||
<span className="text-gray-400">Loading...</span>
|
||||
) : (
|
||||
`${summary?.averageTimeSpent?.toFixed(1) || '0'}s`
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-8">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Event Trends</h2>
|
||||
<div className="h-96">
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<span className="text-gray-400">Loading...</span>
|
||||
</div>
|
||||
) : (
|
||||
<TimeSeriesChart data={timeSeriesData} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-8">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Device Analytics</h2>
|
||||
{deviceData && <DeviceAnalytics data={deviceData} />}
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-48">
|
||||
<span className="text-gray-400">Loading...</span>
|
||||
</div>
|
||||
) : (
|
||||
deviceData && <DeviceAnalytics data={deviceData} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Geographic Distribution</h2>
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-48">
|
||||
<span className="text-gray-400">Loading...</span>
|
||||
</div>
|
||||
) : (
|
||||
<GeoAnalytics data={geoData} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user