take shorturl data
This commit is contained in:
@@ -11,6 +11,8 @@ import { EventsSummary, TimeSeriesData, GeoData, DeviceAnalytics as DeviceAnalyt
|
|||||||
import { TeamSelector } from '@/app/components/ui/TeamSelector';
|
import { TeamSelector } from '@/app/components/ui/TeamSelector';
|
||||||
import { ProjectSelector } from '@/app/components/ui/ProjectSelector';
|
import { ProjectSelector } from '@/app/components/ui/ProjectSelector';
|
||||||
import { TagSelector } from '@/app/components/ui/TagSelector';
|
import { TagSelector } from '@/app/components/ui/TagSelector';
|
||||||
|
import { useSearchParams } from 'next/navigation';
|
||||||
|
import { useShortUrlStore } from '@/app/utils/store';
|
||||||
|
|
||||||
// 事件类型定义
|
// 事件类型定义
|
||||||
interface Event {
|
interface Event {
|
||||||
@@ -114,6 +116,29 @@ const extractEventInfo = (event: Event) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function AnalyticsPage() {
|
export default function AnalyticsPage() {
|
||||||
|
// 从 URL 获取查询参数
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const shorturlParam = searchParams.get('shorturl');
|
||||||
|
|
||||||
|
// 使用 Zustand store
|
||||||
|
const { selectedShortUrl } = useShortUrlStore();
|
||||||
|
|
||||||
|
// 存储 shorturl 参数
|
||||||
|
const [selectedShortUrlString, setSelectedShortUrlString] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// 当 URL 参数变化时更新状态
|
||||||
|
useEffect(() => {
|
||||||
|
if (shorturlParam) {
|
||||||
|
setSelectedShortUrlString(shorturlParam);
|
||||||
|
console.log('Selected shorturl from URL:', shorturlParam);
|
||||||
|
|
||||||
|
// 已经通过 Zustand store 传递了完整数据
|
||||||
|
if (selectedShortUrl) {
|
||||||
|
console.log('Complete shortUrl data from store:', selectedShortUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [shorturlParam, selectedShortUrl]);
|
||||||
|
|
||||||
// 默认日期范围为最近7天
|
// 默认日期范围为最近7天
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const [dateRange, setDateRange] = useState({
|
const [dateRange, setDateRange] = useState({
|
||||||
@@ -247,6 +272,33 @@ export default function AnalyticsPage() {
|
|||||||
<div className="flex justify-between items-center mb-8">
|
<div className="flex justify-between items-center mb-8">
|
||||||
<h1 className="text-2xl font-bold text-gray-900">Analytics Dashboard</h1>
|
<h1 className="text-2xl font-bold text-gray-900">Analytics Dashboard</h1>
|
||||||
<div className="flex flex-col gap-4 md:flex-row md:items-center">
|
<div className="flex flex-col gap-4 md:flex-row md:items-center">
|
||||||
|
{/* 如果有选定的 shorturl,可以显示一个提示,显示更多详细信息 */}
|
||||||
|
{selectedShortUrl && (
|
||||||
|
<div className="bg-blue-100 text-blue-800 px-3 py-2 rounded-md text-sm flex flex-col">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span className="font-medium">{selectedShortUrl.title || 'Untitled'}</span>
|
||||||
|
<span className="mx-2">-</span>
|
||||||
|
<span>{selectedShortUrl.shortUrl}</span>
|
||||||
|
</div>
|
||||||
|
{selectedShortUrl.tags && selectedShortUrl.tags.length > 0 && (
|
||||||
|
<div className="flex flex-wrap gap-1 mt-1">
|
||||||
|
{selectedShortUrl.tags.map((tag, index) => (
|
||||||
|
<span key={index} className="bg-blue-50 text-blue-700 text-xs px-1.5 py-0.5 rounded">
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 如果只有 URL 参数但没有完整数据,则显示简单提示 */}
|
||||||
|
{selectedShortUrlString && !selectedShortUrl && (
|
||||||
|
<div className="bg-blue-100 text-blue-800 px-3 py-1 rounded-md text-sm flex items-center">
|
||||||
|
<span>Filtered by Short URL:</span>
|
||||||
|
<span className="ml-2 font-medium">{selectedShortUrlString}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<TeamSelector
|
<TeamSelector
|
||||||
value={selectedTeamIds}
|
value={selectedTeamIds}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default function Header() {
|
|||||||
<ul className="flex space-x-4">
|
<ul className="flex space-x-4">
|
||||||
<li>
|
<li>
|
||||||
<Link href="/analytics" className="text-sm text-gray-700 hover:text-blue-500">
|
<Link href="/analytics" className="text-sm text-gray-700 hover:text-blue-500">
|
||||||
Dashboard
|
Analytics
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { getSupabaseClient } from '../utils/supabase';
|
|||||||
import { AuthChangeEvent } from '@supabase/supabase-js';
|
import { AuthChangeEvent } from '@supabase/supabase-js';
|
||||||
import { Loader2, ExternalLink, Search } from 'lucide-react';
|
import { Loader2, ExternalLink, Search } from 'lucide-react';
|
||||||
import { TeamSelector } from '@/app/components/ui/TeamSelector';
|
import { TeamSelector } from '@/app/components/ui/TeamSelector';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useShortUrlStore, ShortUrlData } from '@/app/utils/store';
|
||||||
|
|
||||||
// Define attribute type to avoid using 'any'
|
// Define attribute type to avoid using 'any'
|
||||||
interface LinkAttributes {
|
interface LinkAttributes {
|
||||||
@@ -108,7 +110,37 @@ export default function LinksPage() {
|
|||||||
const [totalLinks, setTotalLinks] = useState(0);
|
const [totalLinks, setTotalLinks] = useState(0);
|
||||||
const [totalPages, setTotalPages] = useState(0);
|
const [totalPages, setTotalPages] = useState(0);
|
||||||
const [searchDebounce, setSearchDebounce] = useState<NodeJS.Timeout | null>(null);
|
const [searchDebounce, setSearchDebounce] = useState<NodeJS.Timeout | null>(null);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 使用 Zustand store
|
||||||
|
const { setSelectedShortUrl } = useShortUrlStore();
|
||||||
|
|
||||||
|
// 处理链接记录点击
|
||||||
|
const handleLinkClick = (shortUrl: string, link: ShortLink, metadata: any) => {
|
||||||
|
// 编码 shortUrl 以确保 URL 安全
|
||||||
|
const encodedShortUrl = encodeURIComponent(shortUrl);
|
||||||
|
|
||||||
|
// 创建完整的 ShortUrlData 对象
|
||||||
|
const shortUrlData: ShortUrlData = {
|
||||||
|
id: link.id,
|
||||||
|
slug: metadata.slug,
|
||||||
|
originalUrl: metadata.originalUrl,
|
||||||
|
title: metadata.title,
|
||||||
|
shortUrl: shortUrl,
|
||||||
|
teams: metadata.teamNames,
|
||||||
|
tags: metadata.tagNames,
|
||||||
|
projects: metadata.projectNames,
|
||||||
|
createdAt: metadata.createdAt,
|
||||||
|
domain: metadata.domain
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用 Zustand store 保存数据
|
||||||
|
setSelectedShortUrl(shortUrlData);
|
||||||
|
|
||||||
|
// 导航到 analytics 页面并带上参数
|
||||||
|
router.push(`/analytics?shorturl=${encodedShortUrl}`);
|
||||||
|
};
|
||||||
|
|
||||||
// Extract link metadata from attributes
|
// Extract link metadata from attributes
|
||||||
const getLinkMetadata = (link: ShortLink) => {
|
const getLinkMetadata = (link: ShortLink) => {
|
||||||
try {
|
try {
|
||||||
@@ -391,7 +423,7 @@ export default function LinksPage() {
|
|||||||
const shortUrl = `https://${metadata.domain}/${metadata.slug}`;
|
const shortUrl = `https://${metadata.domain}/${metadata.slug}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={link.id} className="hover:bg-gray-50">
|
<tr key={link.id} className="hover:bg-gray-50 cursor-pointer" onClick={() => handleLinkClick(shortUrl, link, metadata)}>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<span className="font-medium text-gray-900">{metadata.title}</span>
|
<span className="font-medium text-gray-900">{metadata.title}</span>
|
||||||
|
|||||||
29
app/utils/store.ts
Normal file
29
app/utils/store.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
// 定义 ShortUrl 数据类型
|
||||||
|
export interface ShortUrlData {
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
originalUrl: string;
|
||||||
|
title?: string;
|
||||||
|
shortUrl: string;
|
||||||
|
teams?: any[];
|
||||||
|
projects?: any[];
|
||||||
|
tags?: any[];
|
||||||
|
createdAt?: string;
|
||||||
|
domain?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义 store 类型
|
||||||
|
interface ShortUrlStore {
|
||||||
|
selectedShortUrl: ShortUrlData | null;
|
||||||
|
setSelectedShortUrl: (shortUrl: ShortUrlData | null) => void;
|
||||||
|
clearSelectedShortUrl: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 store
|
||||||
|
export const useShortUrlStore = create<ShortUrlStore>((set) => ({
|
||||||
|
selectedShortUrl: null,
|
||||||
|
setSelectedShortUrl: (shortUrl) => set({ selectedShortUrl: shortUrl }),
|
||||||
|
clearSelectedShortUrl: () => set({ selectedShortUrl: null }),
|
||||||
|
}));
|
||||||
@@ -37,11 +37,13 @@
|
|||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"lucide-react": "^0.486.0",
|
"lucide-react": "^0.486.0",
|
||||||
"next": "15.2.3",
|
"next": "15.2.3",
|
||||||
|
"process": "^0.11.10",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"recharts": "^2.15.1",
|
"recharts": "^2.15.1",
|
||||||
"tailwind-merge": "^3.1.0",
|
"tailwind-merge": "^3.1.0",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0",
|
||||||
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
|||||||
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
@@ -50,6 +50,9 @@ importers:
|
|||||||
next:
|
next:
|
||||||
specifier: 15.2.3
|
specifier: 15.2.3
|
||||||
version: 15.2.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
version: 15.2.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||||
|
process:
|
||||||
|
specifier: ^0.11.10
|
||||||
|
version: 0.11.10
|
||||||
react:
|
react:
|
||||||
specifier: ^19.0.0
|
specifier: ^19.0.0
|
||||||
version: 19.0.0
|
version: 19.0.0
|
||||||
@@ -65,6 +68,9 @@ importers:
|
|||||||
uuid:
|
uuid:
|
||||||
specifier: ^10.0.0
|
specifier: ^10.0.0
|
||||||
version: 10.0.0
|
version: 10.0.0
|
||||||
|
zustand:
|
||||||
|
specifier: ^5.0.3
|
||||||
|
version: 5.0.3(@types/react@19.0.12)(react@19.0.0)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@eslint/eslintrc':
|
'@eslint/eslintrc':
|
||||||
specifier: ^3
|
specifier: ^3
|
||||||
@@ -2548,6 +2554,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
process@0.11.10:
|
||||||
|
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||||
|
engines: {node: '>= 0.6.0'}
|
||||||
|
|
||||||
prop-types@15.8.1:
|
prop-types@15.8.1:
|
||||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||||
|
|
||||||
@@ -3035,6 +3045,24 @@ packages:
|
|||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
zustand@5.0.3:
|
||||||
|
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
|
||||||
|
engines: {node: '>=12.20.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '>=18.0.0'
|
||||||
|
immer: '>=9.0.6'
|
||||||
|
react: '>=18.0.0'
|
||||||
|
use-sync-external-store: '>=1.2.0'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
immer:
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
use-sync-external-store:
|
||||||
|
optional: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@alloc/quick-lru@5.2.0': {}
|
'@alloc/quick-lru@5.2.0': {}
|
||||||
@@ -5650,6 +5678,8 @@ snapshots:
|
|||||||
|
|
||||||
prelude-ls@1.2.1: {}
|
prelude-ls@1.2.1: {}
|
||||||
|
|
||||||
|
process@0.11.10: {}
|
||||||
|
|
||||||
prop-types@15.8.1:
|
prop-types@15.8.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
@@ -6304,3 +6334,8 @@ snapshots:
|
|||||||
ws@8.18.1: {}
|
ws@8.18.1: {}
|
||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
|
zustand@5.0.3(@types/react@19.0.12)(react@19.0.0):
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.0.12
|
||||||
|
react: 19.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user