show source per day
This commit is contained in:
parent
d1202a03fa
commit
26e0a4f92f
5 changed files with 165 additions and 111 deletions
|
@ -85,7 +85,13 @@ export function LinkList({ refresh = 0 }: LinkListProps) {
|
|||
const baseUrl = window.location.origin
|
||||
navigator.clipboard.writeText(`${baseUrl}/${shortCode}`)
|
||||
toast({
|
||||
description: "Link copied to clipboard",
|
||||
description: (
|
||||
<>
|
||||
Link copied to clipboard
|
||||
<br />
|
||||
You can add ?source=TextHere to the end of the link to track the source of clicks
|
||||
</>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -9,11 +9,11 @@ import {
|
|||
ResponsiveContainer,
|
||||
} from "recharts";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { toast } from "@/hooks/use-toast"
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import { getLinkClickStats, getLinkSourceStats } from '../api/client';
|
||||
import { ClickStats, SourceStats } from '../types/api';
|
||||
import { getLinkClickStats, getLinkSourceStats } from "../api/client";
|
||||
import { ClickStats, SourceStats } from "../types/api";
|
||||
|
||||
interface StatisticsModalProps {
|
||||
isOpen: boolean;
|
||||
|
@ -21,8 +21,45 @@ interface StatisticsModalProps {
|
|||
linkId: number;
|
||||
}
|
||||
|
||||
interface EnhancedClickStats extends ClickStats {
|
||||
sources?: { source: string; count: number }[];
|
||||
}
|
||||
|
||||
const CustomTooltip = ({
|
||||
active,
|
||||
payload,
|
||||
label,
|
||||
}: {
|
||||
active?: boolean;
|
||||
payload?: any[];
|
||||
label?: string;
|
||||
}) => {
|
||||
if (active && payload && payload.length > 0) {
|
||||
const data = payload[0].payload;
|
||||
return (
|
||||
<div className="bg-background text-foreground p-4 rounded-lg shadow-lg border">
|
||||
<p className="font-medium">{label}</p>
|
||||
<p className="text-sm">Clicks: {data.clicks}</p>
|
||||
{data.sources && data.sources.length > 0 && (
|
||||
<div className="mt-2">
|
||||
<p className="font-medium text-sm">Sources:</p>
|
||||
<ul className="text-sm">
|
||||
{data.sources.map((source: { source: string; count: number }) => (
|
||||
<li key={source.source}>
|
||||
{source.source}: {source.count}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export function StatisticsModal({ isOpen, onClose, linkId }: StatisticsModalProps) {
|
||||
const [clicksOverTime, setClicksOverTime] = useState<ClickStats[]>([]);
|
||||
const [clicksOverTime, setClicksOverTime] = useState<EnhancedClickStats[]>([]);
|
||||
const [sourcesData, setSourcesData] = useState<SourceStats[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
|
@ -35,7 +72,14 @@ export function StatisticsModal({ isOpen, onClose, linkId }: StatisticsModalProp
|
|||
getLinkClickStats(linkId),
|
||||
getLinkSourceStats(linkId),
|
||||
]);
|
||||
setClicksOverTime(clicksData);
|
||||
|
||||
// Enhance clicks data with source information
|
||||
const enhancedClicksData = clicksData.map((clickData) => ({
|
||||
...clickData,
|
||||
sources: sourcesData.filter((source) => source.date === clickData.date),
|
||||
}));
|
||||
|
||||
setClicksOverTime(enhancedClicksData);
|
||||
setSourcesData(sourcesData);
|
||||
} catch (error: any) {
|
||||
console.error("Failed to fetch statistics:", error);
|
||||
|
@ -75,7 +119,7 @@ export function StatisticsModal({ isOpen, onClose, linkId }: StatisticsModalProp
|
|||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="date" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="clicks"
|
||||
|
|
|
@ -32,6 +32,7 @@ export interface ClickStats {
|
|||
}
|
||||
|
||||
export interface SourceStats {
|
||||
date: string;
|
||||
source: string;
|
||||
count: number;
|
||||
}
|
||||
|
|
|
@ -643,15 +643,16 @@ pub async fn get_link_sources(
|
|||
sqlx::query_as::<_, SourceStats>(
|
||||
r#"
|
||||
SELECT
|
||||
DATE(created_at)::text as date,
|
||||
query_source as source,
|
||||
COUNT(*)::bigint as count
|
||||
FROM clicks
|
||||
WHERE link_id = $1
|
||||
AND query_source IS NOT NULL
|
||||
AND query_source != ''
|
||||
GROUP BY query_source
|
||||
ORDER BY COUNT(*) DESC
|
||||
LIMIT 10
|
||||
GROUP BY DATE(created_at), query_source
|
||||
ORDER BY DATE(created_at) ASC, COUNT(*) DESC
|
||||
LIMIT 300
|
||||
"#,
|
||||
)
|
||||
.bind(link_id)
|
||||
|
@ -662,15 +663,16 @@ pub async fn get_link_sources(
|
|||
sqlx::query_as::<_, SourceStats>(
|
||||
r#"
|
||||
SELECT
|
||||
DATE(created_at) as date,
|
||||
query_source as source,
|
||||
COUNT(*) as count
|
||||
FROM clicks
|
||||
WHERE link_id = ?
|
||||
AND query_source IS NOT NULL
|
||||
AND query_source != ''
|
||||
GROUP BY query_source
|
||||
ORDER BY COUNT(*) DESC
|
||||
LIMIT 10
|
||||
GROUP BY DATE(created_at), query_source
|
||||
ORDER BY DATE(created_at) ASC, COUNT(*) DESC
|
||||
LIMIT 300
|
||||
"#,
|
||||
)
|
||||
.bind(link_id)
|
||||
|
|
|
@ -150,6 +150,7 @@ pub struct ClickStats {
|
|||
|
||||
#[derive(sqlx::FromRow, Serialize)]
|
||||
pub struct SourceStats {
|
||||
pub date: String,
|
||||
pub source: String,
|
||||
pub count: i64,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue