@@ -11,40 +11,46 @@ import { getPage } from "../../../utils/useSSR";
1111
1212// Helper function to detect device type from user agent
1313const getDeviceType = ( userAgent : string ) : string => {
14- if ( ! userAgent ) return ' Unknown' ;
15-
14+ if ( ! userAgent ) return " Unknown" ;
15+
1616 const ua = userAgent . toLowerCase ( ) ;
17-
18- if ( ua . includes ( 'mobile' ) || ua . includes ( 'android' ) || ua . includes ( 'iphone' ) ||
19- ua . includes ( 'ipod' ) || ua . includes ( 'windows phone' ) || ua . includes ( 'blackberry' ) ) {
20- return 'Mobile' ;
17+
18+ if (
19+ ua . includes ( "mobile" ) ||
20+ ua . includes ( "android" ) ||
21+ ua . includes ( "iphone" ) ||
22+ ua . includes ( "ipod" ) ||
23+ ua . includes ( "windows phone" ) ||
24+ ua . includes ( "blackberry" )
25+ ) {
26+ return "Mobile" ;
2127 }
22-
23- if ( ua . includes ( ' tablet' ) || ua . includes ( ' ipad' ) ) {
24- return ' Tablet' ;
28+
29+ if ( ua . includes ( " tablet" ) || ua . includes ( " ipad" ) ) {
30+ return " Tablet" ;
2531 }
26-
27- return ' Desktop' ;
32+
33+ return " Desktop" ;
2834} ;
2935
3036// Get device analytics using existing data
3137async function getDeviceAnalytics ( page_id : string , range : number ) {
3238 const date = new Date ( Date . now ( ) - range * 24 * 60 * 60 * 1000 ) ;
33-
39+
3440 const { data } = await supabaseAdmin
35- . from ( ' page_views' )
36- . select ( ' user_agent' )
37- . eq ( ' page_id' , page_id )
38- . gte ( ' created_at' , date . toISOString ( ) )
39- . not ( ' user_agent' , 'is' , null ) ;
40-
41+ . from ( " page_views" )
42+ . select ( " user_agent" )
43+ . eq ( " page_id" , page_id )
44+ . gte ( " created_at" , date . toISOString ( ) )
45+ . not ( " user_agent" , "is" , null ) ;
46+
4147 const deviceCounts = { } ;
42-
48+
4349 data ?. forEach ( ( row ) => {
4450 const device = getDeviceType ( row . user_agent ) ;
4551 deviceCounts [ device ] = ( deviceCounts [ device ] || 0 ) + 1 ;
4652 } ) ;
47-
53+
4854 return Object . entries ( deviceCounts )
4955 . map ( ( [ device , count ] ) => ( {
5056 data_name : device ,
@@ -56,21 +62,23 @@ async function getDeviceAnalytics(page_id: string, range: number) {
5662// Get peak hours analytics using existing data
5763async function getPeakHoursAnalytics ( page_id : string , range : number ) {
5864 const date = new Date ( Date . now ( ) - range * 24 * 60 * 60 * 1000 ) ;
59-
65+
6066 const { data } = await supabaseAdmin
61- . from ( ' page_views' )
62- . select ( ' created_at' )
63- . eq ( ' page_id' , page_id )
64- . gte ( ' created_at' , date . toISOString ( ) ) ;
65-
67+ . from ( " page_views" )
68+ . select ( " created_at" )
69+ . eq ( " page_id" , page_id )
70+ . gte ( " created_at" , date . toISOString ( ) ) ;
71+
6672 const hourCounts = { } ;
67-
73+
6874 data ?. forEach ( ( row ) => {
6975 const hour = new Date ( row . created_at ) . getHours ( ) ;
70- const hourLabel = `${ hour . toString ( ) . padStart ( 2 , '0' ) } :00 - ${ ( hour + 1 ) . toString ( ) . padStart ( 2 , '0' ) } :00` ;
76+ const hourLabel = `${ hour . toString ( ) . padStart ( 2 , "0" ) } :00 - ${ ( hour + 1 )
77+ . toString ( )
78+ . padStart ( 2 , "0" ) } :00`;
7179 hourCounts [ hourLabel ] = ( hourCounts [ hourLabel ] || 0 ) + 1 ;
7280 } ) ;
73-
81+
7482 return Object . entries ( hourCounts )
7583 . map ( ( [ hour , count ] ) => ( {
7684 data_name : hour ,
@@ -80,37 +88,9 @@ async function getPeakHoursAnalytics(page_id: string, range: number) {
8088 . slice ( 0 , 5 ) ;
8189}
8290
83- // Component to render icons based on type
84- const MetricIcon = ( { iconType } : { iconType : string } ) => {
85- switch ( iconType ) {
86- case 'eye' :
87- return (
88- < svg className = "w-5 h-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
89- < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
90- < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
91- </ svg >
92- ) ;
93- case 'users' :
94- return (
95- < svg className = "w-5 h-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
96- < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
97- </ svg >
98- ) ;
99- case 'lightning' :
100- return (
101- < svg className = "w-5 h-5" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
102- < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M13 10V3L4 14h7v7l9-11h-7z" />
103- </ svg >
104- ) ;
105- default :
106- return null ;
107- }
108- } ;
109-
11091const StatsTable = ( { data = [ ] , title, total = 5 } ) => {
111- // Calculate total count for percentage calculation
11292 const totalCount = data . reduce ( ( sum , item ) => sum + item . data_count , 0 ) ;
113-
93+
11494 return (
11595 < div className = "inline-block min-w-full align-middle" >
11696 < div className = "overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg" >
@@ -139,8 +119,11 @@ const StatsTable = ({ data = [], title, total = 5 }) => {
139119 </ thead >
140120 < tbody className = "divide-y divide-gray-200 dark:divide-gray-800 bg-white dark:bg-black" >
141121 { data . map ( ( { data_name, data_count } ) => {
142- const percentage = totalCount > 0 ? ( ( data_count / totalCount ) * 100 ) . toFixed ( 1 ) : 0 ;
143-
122+ const percentage =
123+ totalCount > 0
124+ ? ( ( data_count / totalCount ) * 100 ) . toFixed ( 1 )
125+ : 0 ;
126+
144127 return (
145128 < tr key = { data_name } className = "relative" >
146129 < td className = "whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 dark:text-gray-100 sm:pl-6 truncate overflow-hidden max-w-[200px]" >
@@ -207,17 +190,10 @@ export async function getServerSideProps({ req, res, query }) {
207190 const page = await getPage ( supabase , page_id ) ;
208191
209192 const rangeNum = Number ( range ) || 7 ;
210-
211- // Current period date
212- const date = new Date (
213- Date . now ( ) - rangeNum * 24 * 60 * 60 * 1000
214- ) ;
215-
216- // Previous period date (for comparison)
217- const prevDate = new Date (
218- Date . now ( ) - ( rangeNum * 2 ) * 24 * 60 * 60 * 1000
219- ) ;
220-
193+
194+ const date = new Date ( Date . now ( ) - rangeNum * 24 * 60 * 60 * 1000 ) ;
195+ const prevDate = new Date ( Date . now ( ) - rangeNum * 2 * 24 * 60 * 60 * 1000 ) ;
196+
221197 console . log ( `Fetching stats for page ${ page_id } from ${ date . toISOString ( ) } ` ) ;
222198
223199 // Get current period stats
@@ -245,13 +221,19 @@ export async function getServerSideProps({ req, res, query }) {
245221 . throwOnError ( ) ;
246222
247223 // Calculate growth rates
248- const pageViewsGrowth = prev_page_views > 0
249- ? ( ( page_views - prev_page_views ) / prev_page_views * 100 ) . toFixed ( 1 )
250- : page_views > 0 ? 100 : 0 ;
251-
252- const visitorsGrowth = prev_visitors > 0
253- ? ( ( visitors - prev_visitors ) / prev_visitors * 100 ) . toFixed ( 1 )
254- : visitors > 0 ? 100 : 0 ;
224+ const pageViewsGrowth =
225+ prev_page_views > 0
226+ ? ( ( ( page_views - prev_page_views ) / prev_page_views ) * 100 ) . toFixed ( 1 )
227+ : page_views > 0
228+ ? 100
229+ : 0 ;
230+
231+ const visitorsGrowth =
232+ prev_visitors > 0
233+ ? ( ( ( visitors - prev_visitors ) / prev_visitors ) * 100 ) . toFixed ( 1 )
234+ : visitors > 0
235+ ? 100
236+ : 0 ;
255237
256238 // Calculate engagement rate (page views per visitor)
257239 const engagementRate = visitors > 0 ? ( page_views / visitors ) . toFixed ( 1 ) : 0 ;
@@ -261,13 +243,13 @@ export async function getServerSideProps({ req, res, query }) {
261243 data_name : "Page Views" ,
262244 data_count : Number ( page_views ) . toLocaleString ( ) ,
263245 growth : pageViewsGrowth ,
264- trend : Number ( pageViewsGrowth ) >= 0 ? 'up' : ' down' ,
246+ trend : Number ( pageViewsGrowth ) >= 0 ? "up" : " down" ,
265247 } ,
266248 {
267249 data_name : "Visitors" ,
268250 data_count : Number ( visitors ) . toLocaleString ( ) ,
269251 growth : visitorsGrowth ,
270- trend : Number ( visitorsGrowth ) >= 0 ? 'up' : ' down' ,
252+ trend : Number ( visitorsGrowth ) >= 0 ? "up" : " down" ,
271253 } ,
272254 {
273255 data_name : "Engagement Rate" ,
@@ -280,7 +262,7 @@ export async function getServerSideProps({ req, res, query }) {
280262
281263 // Get device type data by analyzing user agents
282264 const deviceData = await getDeviceAnalytics ( page_id , rangeNum ) ;
283-
265+
284266 // Get peak hours data
285267 const peakHoursData = await getPeakHoursAnalytics ( page_id , rangeNum ) ;
286268
@@ -351,21 +333,47 @@ export default function PageAnalytics({
351333 </ dt >
352334 < dd className = "text-4xl font-bold text-gray-900 dark:text-gray-100 mb-1" >
353335 { data_count }
354- { suffix && < span className = "text-lg font-normal text-gray-500 dark:text-gray-400 ml-1" > { suffix } </ span > }
336+ { suffix && (
337+ < span className = "text-lg font-normal text-gray-500 dark:text-gray-400 ml-1" >
338+ { suffix }
339+ </ span >
340+ ) }
355341 </ dd >
356342 { growth !== null && (
357343 < div className = "flex items-center text-sm" >
358- < span className = { `flex items-center font-medium ${
359- trend === 'up' ? 'text-green-600' : trend === 'down' ? 'text-red-600' : 'text-gray-500'
360- } `} >
361- { trend === 'up' && (
362- < svg className = "w-4 h-4 mr-1" fill = "currentColor" viewBox = "0 0 20 20" >
363- < path fillRule = "evenodd" d = "M5.293 9.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 7.414V15a1 1 0 11-2 0V7.414L6.707 9.707a1 1 0 01-1.414 0z" clipRule = "evenodd" />
344+ < span
345+ className = { `flex items-center font-medium ${
346+ trend === "up"
347+ ? "text-green-600"
348+ : trend === "down"
349+ ? "text-red-600"
350+ : "text-gray-500"
351+ } `}
352+ >
353+ { trend === "up" && (
354+ < svg
355+ className = "w-4 h-4 mr-1"
356+ fill = "currentColor"
357+ viewBox = "0 0 20 20"
358+ >
359+ < path
360+ fillRule = "evenodd"
361+ d = "M5.293 9.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 7.414V15a1 1 0 11-2 0V7.414L6.707 9.707a1 1 0 01-1.414 0z"
362+ clipRule = "evenodd"
363+ />
364364 </ svg >
365365 ) }
366- { trend === 'down' && (
367- < svg className = "w-4 h-4 mr-1" fill = "currentColor" viewBox = "0 0 20 20" >
368- < path fillRule = "evenodd" d = "M14.707 10.293a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L9 12.586V5a1 1 0 012 0v7.586l2.293-2.293a1 1 0 011.414 0z" clipRule = "evenodd" />
366+ { trend === "down" && (
367+ < svg
368+ className = "w-4 h-4 mr-1"
369+ fill = "currentColor"
370+ viewBox = "0 0 20 20"
371+ >
372+ < path
373+ fillRule = "evenodd"
374+ d = "M14.707 10.293a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L9 12.586V5a1 1 0 012 0v7.586l2.293-2.293a1 1 0 011.414 0z"
375+ clipRule = "evenodd"
376+ />
369377 </ svg >
370378 ) }
371379 { Math . abs ( Number ( growth ) ) } % vs previous period
0 commit comments