events team
This commit is contained in:
@@ -17,16 +17,26 @@ interface Event {
|
|||||||
event_type: string;
|
event_type: string;
|
||||||
visitor_id: string;
|
visitor_id: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
|
event_time?: string;
|
||||||
referrer?: string;
|
referrer?: string;
|
||||||
browser?: string;
|
browser?: string;
|
||||||
os?: string;
|
os?: string;
|
||||||
device_type?: string;
|
device_type?: string;
|
||||||
country?: string;
|
country?: string;
|
||||||
city?: 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化日期函数
|
// 格式化日期函数
|
||||||
const formatDate = (dateString: string) => {
|
const formatDate = (dateString: string | undefined) => {
|
||||||
if (!dateString) return '';
|
if (!dateString) return '';
|
||||||
try {
|
try {
|
||||||
return format(new Date(dateString), 'yyyy-MM-dd HH:mm:ss');
|
return format(new Date(dateString), 'yyyy-MM-dd HH:mm:ss');
|
||||||
@@ -35,6 +45,53 @@ const formatDate = (dateString: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 解析JSON字符串
|
||||||
|
const parseJsonSafely = (jsonString: string) => {
|
||||||
|
if (!jsonString) return null;
|
||||||
|
try {
|
||||||
|
return JSON.parse(jsonString);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取用户可读名称
|
||||||
|
const getUserDisplayName = (user: Record<string, unknown> | 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 || '{}');
|
||||||
|
|
||||||
|
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 || '-'
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export default function DashboardPage() {
|
||||||
// 默认日期范围为最近7天
|
// 默认日期范围为最近7天
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
@@ -255,59 +312,80 @@ export default function DashboardPage() {
|
|||||||
Time
|
Time
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
URL ID
|
Link Name
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
URL
|
Original URL
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Event Type
|
Event Type
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Visitor ID
|
User
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Referrer
|
Team/Project
|
||||||
</th>
|
</th>
|
||||||
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Location
|
Device Info
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{events.map((event, index) => (
|
{events.map((event, index) => {
|
||||||
<tr key={event.event_id || index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
const info = extractEventInfo(event);
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
return (
|
||||||
{formatDate(event.created_at)}
|
<tr key={event.event_id || index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
|
||||||
</td>
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
{formatDate(info.eventTime)}
|
||||||
{event.url_id}
|
</td>
|
||||||
</td>
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
<span className="font-medium">{info.linkName}</span>
|
||||||
<a href={event.url} className="text-blue-600 hover:underline" target="_blank" rel="noopener noreferrer">
|
<div className="text-xs text-gray-500 mt-1 truncate max-w-xs">
|
||||||
{event.url}
|
ID: {event.link_id?.substring(0, 8) || '-'}
|
||||||
</a>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm">
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-blue-600">
|
||||||
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${
|
<a href={info.originalUrl} className="hover:underline truncate max-w-xs block" target="_blank" rel="noopener noreferrer">
|
||||||
event.event_type === 'click'
|
{info.originalUrl}
|
||||||
? 'bg-green-100 text-green-800'
|
</a>
|
||||||
: 'bg-blue-100 text-blue-800'
|
</td>
|
||||||
}`}>
|
<td className="px-6 py-4 whitespace-nowrap text-sm">
|
||||||
{event.event_type}
|
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${
|
||||||
</span>
|
info.eventType === 'click'
|
||||||
</td>
|
? 'bg-green-100 text-green-800'
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
: 'bg-blue-100 text-blue-800'
|
||||||
{event.visitor_id.substring(0, 8)}...
|
}`}>
|
||||||
</td>
|
{info.eventType}
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
</span>
|
||||||
{event.referrer || '-'}
|
</td>
|
||||||
</td>
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
<div className="font-medium">{info.userInfo}</div>
|
||||||
{event.country && event.city ? `${event.city}, ${event.country}` : (event.country || '-')}
|
<div className="text-xs text-gray-400 mt-1">{info.visitorId}...</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
))}
|
<div className="font-medium">{info.teamName}</div>
|
||||||
|
<div className="text-xs text-gray-400 mt-1">{info.projectName}</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-xs inline-flex items-center mb-1">
|
||||||
|
<span className="font-medium">Device:</span>
|
||||||
|
<span className="ml-1">{info.device}</span>
|
||||||
|
</span>
|
||||||
|
<span className="text-xs inline-flex items-center mb-1">
|
||||||
|
<span className="font-medium">Browser:</span>
|
||||||
|
<span className="ml-1">{info.browser}</span>
|
||||||
|
</span>
|
||||||
|
<span className="text-xs inline-flex items-center">
|
||||||
|
<span className="font-medium">OS:</span>
|
||||||
|
<span className="ml-1">{info.os}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user