events filter

This commit is contained in:
2025-04-02 08:55:46 +08:00
parent 4b7fb7a887
commit 9cb9f62686
7 changed files with 185 additions and 101 deletions

View File

@@ -112,9 +112,9 @@ export default function AnalyticsPage() {
Tag Analytics ({selectedTagIds.length} selected)
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{selectedTagIds.map((tagId) => (
<div key={tagId} className="p-4 border rounded-md">
<h3 className="font-medium text-gray-800">Tag ID: {tagId}</h3>
{selectedTagIds.map((tagName) => (
<div key={tagName} className="p-4 border rounded-md">
<h3 className="font-medium text-gray-800">Tag: {tagName}</h3>
<p className="text-gray-500 mt-2">Tag analytics will appear here</p>
</div>
))}

View File

@@ -1,50 +1,70 @@
import { NextRequest, NextResponse } from 'next/server';
import type { ApiResponse, EventsQueryParams, EventType } from '@/lib/types';
import {
getEvents,
getEventsSummary,
getTimeSeriesData,
getGeoAnalytics,
getDeviceAnalytics
} from '@/lib/analytics';
import { getEvents, EventsQueryParams } from '@/lib/analytics';
import { ApiResponse } from '@/lib/types';
// 获取事件列表
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
const { searchParams } = new URL(request.url);
// 获取查询参数
const page = parseInt(searchParams.get('page') || '1');
const pageSize = parseInt(searchParams.get('pageSize') || '20');
const eventType = searchParams.get('eventType') || undefined;
const linkId = searchParams.get('linkId') || undefined;
const linkSlug = searchParams.get('linkSlug') || undefined;
const userId = searchParams.get('userId') || undefined;
// 获取可能存在的多个团队、项目和标签ID
const teamIds = searchParams.getAll('teamId');
const projectIds = searchParams.getAll('projectId');
const tagIds = searchParams.getAll('tagId');
const startTime = searchParams.get('startTime') || undefined;
const endTime = searchParams.get('endTime') || undefined;
const sortBy = searchParams.get('sortBy') || undefined;
const sortOrder = (searchParams.get('sortOrder') as 'asc' | 'desc') || undefined;
console.log("API接收到的tagIds:", tagIds); // 添加日志便于调试
// 获取事件列表
const params: EventsQueryParams = {
startTime: searchParams.get('startTime') || undefined,
endTime: searchParams.get('endTime') || undefined,
eventType: searchParams.get('eventType') as EventType || undefined,
linkId: searchParams.get('linkId') || undefined,
linkSlug: searchParams.get('linkSlug') || undefined,
userId: searchParams.get('userId') || undefined,
teamId: searchParams.get('teamId') || undefined,
projectId: searchParams.get('projectId') || undefined,
page: searchParams.has('page') ? parseInt(searchParams.get('page')!, 10) : 1,
pageSize: searchParams.has('pageSize') ? parseInt(searchParams.get('pageSize')!, 10) : 20,
sortBy: searchParams.get('sortBy') || undefined,
sortOrder: (searchParams.get('sortOrder') as 'asc' | 'desc') || undefined
page,
pageSize,
eventType,
linkId,
linkSlug,
userId,
teamId: teamIds.length > 0 ? teamIds[0] : undefined,
teamIds: teamIds.length > 1 ? teamIds : undefined,
projectId: projectIds.length > 0 ? projectIds[0] : undefined,
projectIds: projectIds.length > 1 ? projectIds : undefined,
tagIds: tagIds.length > 0 ? tagIds : undefined,
startTime,
endTime,
sortBy,
sortOrder
};
const { events, total } = await getEvents(params);
const response: ApiResponse<typeof events> = {
const result = await getEvents(params);
const response: ApiResponse<typeof result.events> = {
success: true,
data: events,
data: result.events,
meta: {
total,
page: params.page,
pageSize: params.pageSize
total: result.total,
page,
pageSize
}
};
return NextResponse.json(response);
} catch (error) {
console.error('获取事件列表失败:', error);
const response: ApiResponse<null> = {
success: false,
error: error instanceof Error ? error.message : 'Unknown error occurred'
data: null,
error: error instanceof Error ? error.message : '获取事件列表失败'
};
return NextResponse.json(response, { status: 500 });
}

View File

@@ -46,18 +46,47 @@ export function TagSelector({
const [isOpen, setIsOpen] = useState(false);
const selectorRef = useRef<HTMLDivElement>(null);
// Initialize selected tags based on value prop
useEffect(() => {
if (value) {
if (Array.isArray(value)) {
setSelectedIds(value);
} else {
setSelectedIds(value ? [value] : []);
}
} else {
setSelectedIds([]);
// 标签名称与ID的映射函数
const getTagIdByName = (name: string): string | undefined => {
const tag = tags.find(t => t.name === name);
return tag?.id;
};
const getTagNameById = (id: string): string | undefined => {
const tag = tags.find(t => t.id === id);
return tag?.name;
};
// 从标签名称转换为标签ID
const nameToId = (nameOrNames: string | string[] | undefined): string[] => {
if (!nameOrNames) return [];
if (Array.isArray(nameOrNames)) {
return nameOrNames
.map(name => getTagIdByName(name))
.filter((id): id is string => !!id);
}
}, [value]);
const id = getTagIdByName(nameOrNames);
return id ? [id] : [];
};
// 从标签ID转换为标签名称
const idToName = (idOrIds: string | string[] | undefined): string[] => {
if (!idOrIds) return [];
if (Array.isArray(idOrIds)) {
return idOrIds
.map(id => getTagNameById(id))
.filter((name): name is string => !!name);
}
const name = getTagNameById(idOrIds);
return name ? [name] : [];
};
// 初始化已选择的标签 - 从传入的名称转换为ID
useEffect(() => {
if (tags.length > 0 && value) {
setSelectedIds(nameToId(value));
}
}, [value, tags]);
// Add click outside listener to close dropdown
useEffect(() => {
@@ -167,8 +196,10 @@ export function TagSelector({
setSelectedIds(newSelected);
// 传递标签名称而不是ID
if (onChange) {
onChange(multiple ? newSelected : newSelected[0] || '');
const tagNames = idToName(newSelected);
onChange(multiple ? tagNames : tagNames[0] || '');
}
};
@@ -176,8 +207,11 @@ export function TagSelector({
e.stopPropagation();
const newSelected = selectedIds.filter(id => id !== tagId);
setSelectedIds(newSelected);
// 传递标签名称而不是ID
if (onChange) {
onChange(multiple ? newSelected : newSelected[0] || '');
const tagNames = idToName(newSelected);
onChange(multiple ? tagNames : tagNames[0] || '');
}
};
@@ -214,6 +248,7 @@ export function TagSelector({
);
}
// 根据已选择的ID筛选出已选择的标签
const selectedTags = tags.filter(tag => selectedIds.includes(tag.id));
return (

View File

@@ -322,11 +322,11 @@ export default function HomePage() {
{selectedTagIds.length === 1 ? 'Tag filter:' : 'Tags filter:'}
</span>
<div className="flex flex-wrap gap-2">
{selectedTagIds.map(tagId => (
<span key={tagId} className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{tagId}
{selectedTagIds.map(tagName => (
<span key={tagName} className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{tagName}
<button
onClick={() => setSelectedTagIds(selectedTagIds.filter(id => id !== tagId))}
onClick={() => setSelectedTagIds(selectedTagIds.filter(name => name !== tagName))}
className="ml-1 text-blue-600 hover:text-blue-800"
>
×
@@ -350,7 +350,7 @@ export default function HomePage() {
<div className="p-6 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900 mb-4">Recent Events</h2>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
@@ -473,19 +473,19 @@ export default function HomePage() {
<p className="text-2xl font-semibold text-gray-900">
{typeof summary.totalEvents === 'number' ? summary.totalEvents.toLocaleString() : summary.totalEvents}
</p>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-sm font-medium text-gray-500">Unique Visitors</h3>
<p className="text-2xl font-semibold text-gray-900">
{typeof summary.uniqueVisitors === 'number' ? summary.uniqueVisitors.toLocaleString() : summary.uniqueVisitors}
</p>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-sm font-medium text-gray-500">Total Conversions</h3>
<p className="text-2xl font-semibold text-gray-900">
{typeof summary.totalConversions === 'number' ? summary.totalConversions.toLocaleString() : summary.totalConversions}
</p>
</div>
</div>
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-sm font-medium text-gray-500">Avg. Time Spent</h3>
<p className="text-2xl font-semibold text-gray-900">
@@ -510,8 +510,8 @@ export default function HomePage() {
<div className="bg-white rounded-lg shadow p-6 mb-8">
<h2 className="text-lg font-semibold text-gray-900 mb-4">Geographic Distribution</h2>
<GeoAnalytics data={geoData} />
</div>
</div>
</>
</div>
);
}
}