diff --git a/app/sites/[id]/page.tsx b/app/sites/[id]/page.tsx index a68bc6a..0d254cf 100644 --- a/app/sites/[id]/page.tsx +++ b/app/sites/[id]/page.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react' import { useParams, useRouter } from 'next/navigation' import { getSite, type Site } from '@/lib/api/sites' -import { getStats, getRealtime, getDailyStats, getTopPages, getTopReferrers, getCountries, getCities } from '@/lib/api/stats' +import { getStats, getRealtime, getDailyStats, getTopPages, getTopReferrers, getCountries, getCities, getBrowsers, getOS, getDevices } from '@/lib/api/stats' import { formatNumber, getDateRange } from '@/lib/utils/format' import { toast } from 'sonner' import LoadingOverlay from '@/components/LoadingOverlay' @@ -12,6 +12,7 @@ import RealtimeVisitors from '@/components/dashboard/RealtimeVisitors' import TopPages from '@/components/dashboard/TopPages' import TopReferrers from '@/components/dashboard/TopReferrers' import Countries from '@/components/dashboard/Countries' +import TechSpecs from '@/components/dashboard/TechSpecs' import Chart from '@/components/dashboard/Chart' export default function SiteDashboardPage() { @@ -28,6 +29,9 @@ export default function SiteDashboardPage() { const [topReferrers, setTopReferrers] = useState([]) const [countries, setCountries] = useState([]) const [cities, setCities] = useState([]) + const [browsers, setBrowsers] = useState([]) + const [os, setOS] = useState([]) + const [devices, setDevices] = useState([]) const [dateRange, setDateRange] = useState(getDateRange(30)) useEffect(() => { @@ -41,7 +45,7 @@ export default function SiteDashboardPage() { const loadData = async () => { try { setLoading(true) - const [siteData, statsData, realtimeData, dailyData, pagesData, referrersData, countriesData, citiesData] = await Promise.all([ + const [siteData, statsData, realtimeData, dailyData, pagesData, referrersData, countriesData, citiesData, browsersData, osData, devicesData] = await Promise.all([ getSite(siteId), getStats(siteId, dateRange.start, dateRange.end), getRealtime(siteId), @@ -50,6 +54,9 @@ export default function SiteDashboardPage() { getTopReferrers(siteId, dateRange.start, dateRange.end, 10), getCountries(siteId, dateRange.start, dateRange.end, 10), getCities(siteId, dateRange.start, dateRange.end, 10), + getBrowsers(siteId, dateRange.start, dateRange.end, 10), + getOS(siteId, dateRange.start, dateRange.end, 10), + getDevices(siteId, dateRange.start, dateRange.end, 10), ]) setSite(siteData) setStats(statsData || { pageviews: 0, visitors: 0 }) @@ -59,6 +66,9 @@ export default function SiteDashboardPage() { setTopReferrers(Array.isArray(referrersData) ? referrersData : []) setCountries(Array.isArray(countriesData) ? countriesData : []) setCities(Array.isArray(citiesData) ? citiesData : []) + setBrowsers(Array.isArray(browsersData) ? browsersData : []) + setOS(Array.isArray(osData) ? osData : []) + setDevices(Array.isArray(devicesData) ? devicesData : []) } catch (error: any) { toast.error('Failed to load data: ' + (error.message || 'Unknown error')) } finally { @@ -138,6 +148,10 @@ export default function SiteDashboardPage() { + +
+ +
) } diff --git a/components/dashboard/TechSpecs.tsx b/components/dashboard/TechSpecs.tsx new file mode 100644 index 0000000..9688695 --- /dev/null +++ b/components/dashboard/TechSpecs.tsx @@ -0,0 +1,90 @@ +'use client' + +import { useState } from 'react' +import { formatNumber } from '@/lib/utils/format' + +interface TechSpecsProps { + browsers: Array<{ browser: string; pageviews: number }> + os: Array<{ os: string; pageviews: number }> + devices: Array<{ device: string; pageviews: number }> +} + +type Tab = 'browsers' | 'os' | 'devices' + +export default function TechSpecs({ browsers, os, devices }: TechSpecsProps) { + const [activeTab, setActiveTab] = useState('browsers') + + const renderContent = () => { + let data: Array<{ name: string; pageviews: number }> = [] + + if (activeTab === 'browsers') { + data = browsers.map(b => ({ name: b.browser, pageviews: b.pageviews })) + } else if (activeTab === 'os') { + data = os.map(o => ({ name: o.os, pageviews: o.pageviews })) + } else if (activeTab === 'devices') { + data = devices.map(d => ({ name: d.device, pageviews: d.pageviews })) + } + + if (!data || data.length === 0) { + return

No data available

+ } + + return ( +
+ {data.map((item, index) => ( +
+
+ {item.name === 'Unknown' ? 'Unknown' : item.name} +
+
+ {formatNumber(item.pageviews)} +
+
+ ))} +
+ ) + } + + return ( +
+
+

+ Technology +

+
+ + + +
+
+ {renderContent()} +
+ ) +} diff --git a/lib/api/stats.ts b/lib/api/stats.ts index bb46322..fe232cb 100644 --- a/lib/api/stats.ts +++ b/lib/api/stats.ts @@ -32,6 +32,21 @@ export interface RegionStat { pageviews: number } +export interface BrowserStat { + browser: string + pageviews: number +} + +export interface OSStat { + os: string + pageviews: number +} + +export interface DeviceStat { + device: string + pageviews: number +} + export interface DailyStat { date: string pageviews: number @@ -94,6 +109,30 @@ export async function getRegions(siteId: string, startDate?: string, endDate?: s return apiRequest<{ regions: RegionStat[] }>(`/sites/${siteId}/regions?${params.toString()}`).then(r => r?.regions || []) } +export async function getBrowsers(siteId: string, startDate?: string, endDate?: string, limit = 10): 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<{ browsers: BrowserStat[] }>(`/sites/${siteId}/browsers?${params.toString()}`).then(r => r?.browsers || []) +} + +export async function getOS(siteId: string, startDate?: string, endDate?: string, limit = 10): 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<{ os: OSStat[] }>(`/sites/${siteId}/os?${params.toString()}`).then(r => r?.os || []) +} + +export async function getDevices(siteId: string, startDate?: string, endDate?: string, limit = 10): 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<{ devices: DeviceStat[] }>(`/sites/${siteId}/devices?${params.toString()}`).then(r => r?.devices || []) +} + export async function getDailyStats(siteId: string, startDate?: string, endDate?: string): Promise { const params = new URLSearchParams() if (startDate) params.append('start_date', startDate)