events date range 7days

This commit is contained in:
2025-03-31 21:21:17 +08:00
parent 52969426a2
commit 727f0a9336
2 changed files with 73 additions and 89 deletions

View File

@@ -7,8 +7,8 @@ import { Event, EventType } from '@/lib/types';
export default function EventsPage() { export default function EventsPage() {
const [dateRange, setDateRange] = useState({ const [dateRange, setDateRange] = useState({
from: new Date('2024-02-01'), from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7天前
to: new Date('2025-03-05') to: new Date() // 今天
}); });
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -227,6 +227,24 @@ export default function EventsPage() {
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" 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"
/> />
</div> </div>
<div>
<select
value={filters.tags.length > 0 ? filters.tags[0] : ''}
onChange={(e) => {
const selectedTag = e.target.value;
setFilters(prev => ({
...prev,
tags: selectedTag ? [selectedTag] : []
}));
}}
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"
>
<option value=""></option>
{tags.map(tag => (
<option key={tag} value={tag}>{tag}</option>
))}
</select>
</div>
<div> <div>
<select <select
value={filters.sortBy} value={filters.sortBy}
@@ -250,33 +268,6 @@ export default function EventsPage() {
</div> </div>
</div> </div>
{/* 标签选择 */}
<div className="mt-4">
<h3 className="text-sm font-medium mb-2"></h3>
<div className="flex flex-wrap gap-2">
{tags.map(tag => (
<button
key={tag}
onClick={() => {
setFilters(prev => ({
...prev,
tags: prev.tags.includes(tag)
? prev.tags.filter(t => t !== tag)
: [...prev.tags, tag]
}));
}}
className={`px-2 py-1 rounded-full text-sm font-medium ${
filters.tags.includes(tag)
? 'bg-blue-500 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
{tag}
</button>
))}
</div>
</div>
<div className="mt-4 flex justify-end"> <div className="mt-4 flex justify-end">
<button <button
onClick={resetFilters} onClick={resetFilters}

View File

@@ -186,66 +186,6 @@ export default function LinkDetailsPage({
} }
}, [dateRange, timeGranularity]); }, [dateRange, timeGranularity]);
// 获取并设置linkId
useEffect(() => {
const loadParams = async () => {
const resolvedParams = await params;
setLinkId(resolvedParams.id);
};
loadParams();
}, [params]);
// 获取链接详情数据
useEffect(() => {
if (!linkId) return;
const fetchLinkDetails = async () => {
setLoading(true);
try {
// 获取链接详情
const response = await fetch(`/api/links/${linkId}/details`);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || "Failed to fetch link details");
}
const details = await response.json();
console.log("Link details:", details); // 添加日志以确认 API 响应
// 将 API 返回的数据映射到组件需要的格式
setLinkDetails({
id: details.link_id || linkId,
name: details.title || "Untitled Link",
shortUrl: details.short_url || window.location.hostname + "/" + details.link_id,
originalUrl: details.original_url || "",
creator: details.created_by || "Unknown",
createdAt: details.created_at || new Date().toISOString(),
visits: details.visits || 0,
visitChange: details.visit_change || 0,
uniqueVisitors: details.unique_visitors || 0,
uniqueVisitorsChange: details.unique_visitors_change || 0,
avgTime: formatTime(details.avg_time_spent || 0),
avgTimeChange: details.avg_time_change || 0,
conversionRate: details.conversion_rate || 0,
conversionChange: details.conversion_change || 0,
status: details.is_active ? "active" : "inactive",
tags: details.tags || [],
});
setLoading(false);
// 获取分析数据
fetchAnalyticsData(linkId);
} catch (error) {
console.error("Failed to fetch link details:", error);
setLoading(false);
}
};
fetchLinkDetails();
}, [linkId, fetchAnalyticsData]);
// 格式化时间(秒转为分钟和秒) // 格式化时间(秒转为分钟和秒)
const formatTime = (seconds: number) => { const formatTime = (seconds: number) => {
const minutes = Math.floor(seconds / 60); const minutes = Math.floor(seconds / 60);
@@ -253,6 +193,59 @@ export default function LinkDetailsPage({
return `${minutes}m ${remainingSeconds}s`; return `${minutes}m ${remainingSeconds}s`;
}; };
// 将 fetchLinkDetails 移到 useEffect 外面
const fetchLinkDetails = useCallback(async (id: string) => {
setLoading(true);
try {
// 获取链接详情
const response = await fetch(`/api/links/${id}/details`);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || "Failed to fetch link details");
}
const details = await response.json();
console.log("Link details:", details); // 添加日志以确认 API 响应
// 将 API 返回的数据映射到组件需要的格式
setLinkDetails({
id: details.link_id || id,
name: details.title || "Untitled Link",
shortUrl: details.short_url || window.location.hostname + "/" + details.link_id,
originalUrl: details.original_url || "",
creator: details.created_by || "Unknown",
createdAt: details.created_at || new Date().toISOString(),
visits: details.visits || 0,
visitChange: details.visit_change || 0,
uniqueVisitors: details.unique_visitors || 0,
uniqueVisitorsChange: details.unique_visitors_change || 0,
avgTime: formatTime(details.avg_time_spent || 0),
avgTimeChange: details.avg_time_change || 0,
conversionRate: details.conversion_rate || 0,
conversionChange: details.conversion_change || 0,
status: details.is_active ? "active" : "inactive",
tags: details.tags || [],
});
setLoading(false);
} catch (error) {
console.error("Failed to fetch link details:", error);
setLoading(false);
}
}, []);
// 获取并设置linkId - 移除异步获取参数的逻辑因为params已经可用
useEffect(() => {
// 直接使用params.id
if (params.id) {
setLinkId(params.id);
fetchLinkDetails(params.id);
fetchAnalyticsData(params.id);
}
}, [params.id, fetchLinkDetails, fetchAnalyticsData]);
// 获取链接详情数据 - 这个 useEffect 可以删除,已合并到上面的 useEffect
// 更新时间粒度并重新获取趋势数据 // 更新时间粒度并重新获取趋势数据
const updateTimeGranularity = (granularity: TimeGranularity) => { const updateTimeGranularity = (granularity: TimeGranularity) => {
setTimeGranularity(granularity); setTimeGranularity(granularity);