"use client"; import { useState, useEffect } from 'react'; import { format, subDays } from 'date-fns'; import { DateRangePicker } from '@/app/components/ui/DateRangePicker'; import TimeSeriesChart from '@/app/components/charts/TimeSeriesChart'; import GeoAnalytics from '@/app/components/analytics/GeoAnalytics'; import DevicePieCharts from '@/app/components/charts/DevicePieCharts'; import { EventsSummary, TimeSeriesData, GeoData, DeviceAnalytics as DeviceAnalyticsType } from '@/app/api/types'; import { TeamSelector } from '@/app/components/ui/TeamSelector'; import { ProjectSelector } from '@/app/components/ui/ProjectSelector'; import { TagSelector } from '@/app/components/ui/TagSelector'; // 事件类型定义 interface Event { event_id?: string; url_id: string; url: string; event_type: string; visitor_id: string; created_at: string; event_time?: string; referrer?: string; browser?: string; os?: string; device_type?: string; country?: string; city?: string; event_attributes?: string; link_attributes?: string; user_attributes?: string; link_label?: string; link_original_url?: string; team_name?: string; project_name?: string; link_id?: string; link_slug?: string; link_tags?: string; } // 格式化日期函数 const formatDate = (dateString: string | undefined) => { if (!dateString) return ''; try { return format(new Date(dateString), 'yyyy-MM-dd HH:mm:ss'); } catch { return dateString; } }; // 解析JSON字符串 const parseJsonSafely = (jsonString: string) => { if (!jsonString) return null; try { return JSON.parse(jsonString); } catch { return null; } }; // 获取用户可读名称 const getUserDisplayName = (user: Record | null) => { if (!user) return '-'; if (typeof user.full_name === 'string') return user.full_name; if (typeof user.name === 'string') return user.name; if (typeof user.email === 'string') return user.email; return '-'; }; // 提取链接和事件的重要信息 const extractEventInfo = (event: Event) => { // 解析事件属性 const eventAttrs = parseJsonSafely(event.event_attributes || '{}'); // 解析链接属性 const linkAttrs = parseJsonSafely(event.link_attributes || '{}'); // 解析用户属性 const userAttrs = parseJsonSafely(event.user_attributes || '{}'); // 解析标签信息 let tags: string[] = []; try { if (event.link_tags) { const parsedTags = JSON.parse(event.link_tags); if (Array.isArray(parsedTags)) { tags = parsedTags; } } } catch { // 解析失败则保持空数组 } return { eventTime: event.created_at || event.event_time, linkName: event.link_label || linkAttrs?.name || eventAttrs?.link_name || event.link_slug || '-', originalUrl: event.link_original_url || eventAttrs?.origin_url || '-', eventType: event.event_type || '-', visitorId: event.visitor_id?.substring(0, 8) || '-', referrer: eventAttrs?.referrer || '-', location: event.country ? (event.city ? `${event.city}, ${event.country}` : event.country) : '-', device: event.device_type || '-', browser: event.browser || '-', os: event.os || '-', userInfo: getUserDisplayName(userAttrs), teamName: event.team_name || '-', projectName: event.project_name || '-', tags: tags }; }; export default function DashboardPage() { // 默认日期范围为最近7天 const today = new Date(); const [dateRange, setDateRange] = useState({ from: subDays(today, 7), // 7天前 to: today // 今天 }); // 添加团队选择状态 - 使用数组支持多选 const [selectedTeamIds, setSelectedTeamIds] = useState([]); // 添加项目选择状态 - 使用数组支持多选 const [selectedProjectIds, setSelectedProjectIds] = useState([]); // 添加标签选择状态 - 使用数组支持多选 const [selectedTagIds, setSelectedTagIds] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [summary, setSummary] = useState(null); const [timeSeriesData, setTimeSeriesData] = useState([]); const [geoData, setGeoData] = useState([]); const [deviceData, setDeviceData] = useState(null); const [events, setEvents] = useState([]); useEffect(() => { const fetchData = async () => { setLoading(true); setError(null); 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 }); // 添加团队ID参数 - 支持多个团队 if (selectedTeamIds.length > 0) { selectedTeamIds.forEach(teamId => { params.append('teamId', teamId); }); } // 添加项目ID参数 - 支持多个项目 if (selectedProjectIds.length > 0) { selectedProjectIds.forEach(projectId => { params.append('projectId', projectId); }); } // 添加标签ID参数 - 支持多个标签 if (selectedTagIds.length > 0) { selectedTagIds.forEach(tagId => { params.append('tagId', tagId); }); } // 并行获取所有数据 const [summaryRes, timeSeriesRes, geoRes, deviceRes, eventsRes] = await Promise.all([ fetch(`${baseUrl}/summary?${params.toString()}`), fetch(`${baseUrl}/time-series?${params.toString()}`), fetch(`${baseUrl}/geo?${params.toString()}`), fetch(`${baseUrl}/devices?${params.toString()}`), fetch(`${baseUrl}?${params.toString()}`) ]); const [summaryData, timeSeriesData, geoData, deviceData, eventsData] = await Promise.all([ summaryRes.json(), timeSeriesRes.json(), geoRes.json(), deviceRes.json(), eventsRes.json() ]); 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'); if (!eventsRes.ok) throw new Error(eventsData.error || 'Failed to fetch events data'); setSummary(summaryData.data); setTimeSeriesData(timeSeriesData.data); setGeoData(geoData.data); setDeviceData(deviceData.data); setEvents(eventsData.data || []); } catch (err) { setError(err instanceof Error ? err.message : 'An error occurred while fetching data'); } finally { setLoading(false); } }; fetchData(); }, [dateRange, selectedTeamIds, selectedProjectIds, selectedTagIds]); if (loading) { return (
); } if (error) { return (
{error}
); } return (

Analytics Dashboard

setSelectedTeamIds(Array.isArray(value) ? value : [value])} className="w-[250px]" multiple={true} /> setSelectedProjectIds(Array.isArray(value) ? value : [value])} className="w-[250px]" multiple={true} teamId={selectedTeamIds.length === 1 ? selectedTeamIds[0] : undefined} /> setSelectedTagIds(Array.isArray(value) ? value : [value])} className="w-[250px]" multiple={true} teamId={selectedTeamIds.length === 1 ? selectedTeamIds[0] : undefined} />
{/* 显示团队选择信息 */} {selectedTeamIds.length > 0 && (
{selectedTeamIds.length === 1 ? 'Team filter:' : 'Teams filter:'}
{selectedTeamIds.map(teamId => ( {teamId} ))} {selectedTeamIds.length > 0 && ( )}
)} {/* 显示项目选择信息 */} {selectedProjectIds.length > 0 && (
{selectedProjectIds.length === 1 ? 'Project filter:' : 'Projects filter:'}
{selectedProjectIds.map(projectId => ( {projectId} ))} {selectedProjectIds.length > 0 && ( )}
)} {/* 显示标签选择信息 */} {selectedTagIds.length > 0 && (
{selectedTagIds.length === 1 ? 'Tag filter:' : 'Tags filter:'}
{selectedTagIds.map(tagId => ( {tagId} ))} {selectedTagIds.length > 0 && ( )}
)} {/* 事件列表部分 - 现在放在最上面 */}

Recent Events

{events.map((event, index) => { const info = extractEventInfo(event); return ( ); })}
Time Link Name Original URL Event Type Tags User Team/Project Device Info
{formatDate(info.eventTime)} {info.linkName}
ID: {event.link_id?.substring(0, 8) || '-'}
{info.originalUrl} {info.eventType}
{info.tags && info.tags.length > 0 ? ( info.tags.map((tag, idx) => ( {tag} )) ) : ( - )}
{info.userInfo}
{info.visitorId}...
{info.teamName}
{info.projectName}
Device: {info.device} Browser: {info.browser} OS: {info.os}
{/* 表格为空状态 */} {!loading && events.length === 0 && (
No events found
)}
{/* 仪表板内容 - 现在放在事件列表之后 */} <> {summary && (

Total Events

{typeof summary.totalEvents === 'number' ? summary.totalEvents.toLocaleString() : summary.totalEvents}

Unique Visitors

{typeof summary.uniqueVisitors === 'number' ? summary.uniqueVisitors.toLocaleString() : summary.uniqueVisitors}

Total Conversions

{typeof summary.totalConversions === 'number' ? summary.totalConversions.toLocaleString() : summary.totalConversions}

Avg. Time Spent

{summary.averageTimeSpent?.toFixed(1) || '0'}s

)}

Event Trends

Device Analytics

{deviceData && }

Geographic Distribution

); }