"use client"; import { useState, useEffect } from 'react'; import { format } from 'date-fns'; import { DateRangePicker } from '@/app/components/ui/DateRangePicker'; import { Event, EventType } from '@/lib/types'; export default function EventsPage() { const [dateRange, setDateRange] = useState({ from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7天前 to: new Date() // 今天 }); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [events, setEvents] = useState([]); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [totalEvents, setTotalEvents] = useState(0); const [tags, setTags] = useState([]); // 过滤条件状态 const [filters, setFilters] = useState({ eventType: '', linkId: '', linkSlug: '', userId: '', teamId: '', projectId: '', tags: [] as string[], searchSlug: '', sortBy: 'event_time', sortOrder: 'desc' as 'asc' | 'desc' }); // 加载标签列表 const fetchTags = async () => { try { const response = await fetch('/api/events/tags'); const data = await response.json(); if (data.success && Array.isArray(data.data)) { setTags(data.data.map((tag: { tag_name: string }) => tag.tag_name)); } } catch (err) { console.error('Error fetching tags:', err); } }; // 获取事件列表 const fetchEvents = async (pageNum: number) => { 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 params = new URLSearchParams({ page: pageNum.toString(), pageSize: '20' }); // 添加时间范围参数(如果有) if (startTime) params.append('startTime', startTime); if (endTime) params.append('endTime', endTime); // 添加其他过滤参数 if (filters.eventType) params.append('eventType', filters.eventType); if (filters.linkId) params.append('linkId', filters.linkId); if (filters.linkSlug) params.append('linkSlug', filters.linkSlug); if (filters.userId) params.append('userId', filters.userId); if (filters.teamId) params.append('teamId', filters.teamId); if (filters.projectId) params.append('projectId', filters.projectId); if (filters.tags.length > 0) params.append('tags', filters.tags.join(',')); if (filters.searchSlug) params.append('searchSlug', filters.searchSlug); if (filters.sortBy) params.append('sortBy', filters.sortBy); if (filters.sortOrder) params.append('sortOrder', filters.sortOrder); const response = await fetch(`/api/events?${params.toString()}`); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Failed to fetch events'); } if (pageNum === 1) { setEvents(data.data || []); } else { setEvents(prev => [...prev, ...(data.data || [])]); } setTotalEvents(data.meta?.total || 0); setHasMore(data.data && data.data.length === 20); } catch (err) { console.error("Error fetching events:", err); setError(err instanceof Error ? err.message : 'An error occurred while fetching events'); setEvents([]); } finally { setLoading(false); } }; // 初始化加载 useEffect(() => { fetchTags(); }, []); // 当过滤条件改变时重新加载数据 useEffect(() => { setPage(1); setEvents([]); setLoading(true); fetchEvents(1); }, [dateRange, filters]); // 加载更多数据 const loadMore = () => { if (!loading && hasMore) { const nextPage = page + 1; setPage(nextPage); fetchEvents(nextPage); } }; // 重置过滤条件 const resetFilters = () => { setFilters({ eventType: '', linkId: '', linkSlug: '', userId: '', teamId: '', projectId: '', tags: [], searchSlug: '', sortBy: 'event_time', sortOrder: 'desc' }); }; // 格式化日期 const formatDate = (dateString: string) => { const date = new Date(dateString); return date.toLocaleString(); }; if (error) { return (
{error}
); } return (
{/* 过滤器部分 */}

过滤器

setFilters(prev => ({ ...prev, linkId: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500" />
setFilters(prev => ({ ...prev, linkSlug: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500" />
setFilters(prev => ({ ...prev, userId: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500" />
setFilters(prev => ({ ...prev, teamId: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500" />
setFilters(prev => ({ ...prev, projectId: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500" />
setFilters(prev => ({ ...prev, searchSlug: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500" />
{/* 事件列表 */}

事件列表 ({totalEvents})

{events.map((event) => (
{event.event_type} {formatDate(event.event_time)}
{event.device_type}

链接: {event.link_slug}

用户: {event.user_name || event.user_id}

IP: {event.ip_address}

位置: {event.country} {event.city}

{event.link_tags && (
{(typeof event.link_tags === 'string' ? (() => { try { return JSON.parse(event.link_tags); } catch (e) { console.error('Error parsing link_tags:', e); return []; } })() : event.link_tags ).map((tag: string) => ( {tag} ))}
)}
))}
{hasMore && (
)} {!loading && events.length === 0 && (
没有找到符合条件的事件
)}
); }