From d5ea1a793bb1d23cf8497b88fd5341b17e9e6ff1 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Wed, 4 Feb 2026 19:11:03 +0100 Subject: [PATCH] feat: add Campaigns component and API integration for campaign statistics in SiteDashboardPage --- app/sites/[id]/page.tsx | 6 ++ components/dashboard/Campaigns.tsx | 157 +++++++++++++++++++++++++++++ lib/api/stats.ts | 31 ++++++ 3 files changed, 194 insertions(+) create mode 100644 components/dashboard/Campaigns.tsx diff --git a/app/sites/[id]/page.tsx b/app/sites/[id]/page.tsx index 7f674eb..d3ffc9e 100644 --- a/app/sites/[id]/page.tsx +++ b/app/sites/[id]/page.tsx @@ -18,6 +18,7 @@ import TechSpecs from '@/components/dashboard/TechSpecs' import Chart from '@/components/dashboard/Chart' import PerformanceStats from '@/components/dashboard/PerformanceStats' import GoalStats from '@/components/dashboard/GoalStats' +import Campaigns from '@/components/dashboard/Campaigns' export default function SiteDashboardPage() { const { user } = useAuth() @@ -377,6 +378,11 @@ export default function SiteDashboardPage() { /> + {/* Campaigns Report */} +
+ +
+
diff --git a/components/dashboard/Campaigns.tsx b/components/dashboard/Campaigns.tsx new file mode 100644 index 0000000..96e7465 --- /dev/null +++ b/components/dashboard/Campaigns.tsx @@ -0,0 +1,157 @@ +'use client' + +import { useState, useEffect } from 'react' +import { formatNumber } from '@/lib/utils/format' +import { Modal } from '@ciphera-net/ui' +import { getCampaigns, CampaignStat } from '@/lib/api/stats' + +interface CampaignsProps { + siteId: string + dateRange: { start: string, end: string } +} + +const LIMIT = 7 + +export default function Campaigns({ siteId, dateRange }: CampaignsProps) { + const [data, setData] = useState([]) + const [isLoading, setIsLoading] = useState(true) + const [isModalOpen, setIsModalOpen] = useState(false) + const [fullData, setFullData] = useState([]) + const [isLoadingFull, setIsLoadingFull] = useState(false) + + useEffect(() => { + const fetchData = async () => { + setIsLoading(true) + try { + const result = await getCampaigns(siteId, dateRange.start, dateRange.end) + setData(result) + } catch (e) { + console.error(e) + } finally { + setIsLoading(false) + } + } + fetchData() + }, [siteId, dateRange]) + + useEffect(() => { + if (isModalOpen) { + const fetchFullData = async () => { + setIsLoadingFull(true) + try { + const result = await getCampaigns(siteId, dateRange.start, dateRange.end) + setFullData(result) + } catch (e) { + console.error(e) + } finally { + setIsLoadingFull(false) + } + } + fetchFullData() + } else { + setFullData([]) + } + }, [isModalOpen, siteId, dateRange]) + + const hasData = data.length > 0 + const displayedData = hasData ? data.slice(0, LIMIT) : [] + const emptySlots = Math.max(0, LIMIT - displayedData.length) + const showViewAll = hasData && data.length > LIMIT + + return ( + <> +
+
+

+ Campaigns +

+ {showViewAll && ( + + )} +
+ +
+ {isLoading ? ( +
+

Loading...

+
+ ) : hasData ? ( + <> +
+
Source
+
Medium
+
Campaign
+
Visitors
+
+ {displayedData.map((item, index) => ( +
+
+ {item.source} +
+
+ {item.medium || '-'} +
+
+ {item.campaign || '-'} +
+
+ {formatNumber(item.visitors)} +
+
+ ))} + {Array.from({ length: emptySlots }).map((_, i) => ( + +
+ + setIsModalOpen(false)} + title="All Campaigns" + > +
+ {isLoadingFull ? ( +
Loading...
+ ) : ( + <> +
+
Source
+
Medium
+
Campaign
+
Visitors
+
+ {(fullData.length > 0 ? fullData : data).map((item, index) => ( +
+
+ {item.source} +
+
+ {item.medium || '-'} +
+
+ {item.campaign || '-'} +
+
+ {formatNumber(item.visitors)} +
+
+ ))} + + )} +
+
+ + ) +} diff --git a/lib/api/stats.ts b/lib/api/stats.ts index 68c6bdd..683ef92 100644 --- a/lib/api/stats.ts +++ b/lib/api/stats.ts @@ -39,6 +39,14 @@ export interface GoalCountStat { display_name?: string | null } +export interface CampaignStat { + source: string + medium: string + campaign: string + visitors: number + pageviews: number +} + export interface TopReferrer { referrer: string pageviews: number @@ -268,6 +276,29 @@ export async function getPublicPerformanceByPage( return res?.performance_by_page ?? [] } +export async function getGoalStats(siteId: string, startDate?: string, endDate?: string, limit = 20): Promise { + const params = new URLSearchParams() + if (startDate) params.append('start_date', startDate) + if (endDate) params.append('end_date', endDate) + params.append('limit', limit.toString()) + return apiRequest<{ goal_counts: GoalCountStat[] }>(`/sites/${siteId}/goals/stats?${params.toString()}`).then(r => r?.goal_counts || []) +} + +export async function getCampaigns(siteId: string, startDate?: string, endDate?: string): Promise { + const params = new URLSearchParams() + if (startDate) params.append('start_date', startDate) + if (endDate) params.append('end_date', endDate) + return apiRequest<{ campaigns: CampaignStat[] }>(`/sites/${siteId}/campaigns?${params.toString()}`).then(r => r?.campaigns || []) +} + +export async function getPublicCampaigns(siteId: string, startDate?: string, endDate?: string, auth?: AuthParams): Promise { + const params = new URLSearchParams() + if (startDate) params.append('start_date', startDate) + if (endDate) params.append('end_date', endDate) + appendAuthParams(params, auth) + return apiRequest<{ campaigns: CampaignStat[] }>(`/public/sites/${siteId}/campaigns?${params.toString()}`).then(r => r?.campaigns || []) +} + export interface DashboardData { site: Site stats: Stats