From 99dbaba85b9f0e110664b2d096ec34298c0eb6f8 Mon Sep 17 00:00:00 2001 From: Claudio Sciotto Date: Mon, 3 Feb 2025 11:29:34 -0500 Subject: [PATCH 1/5] Analytics fix* --- next.config.mjs | 4 +++- src/app/dashboard/analytics/page.tsx | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/next.config.mjs b/next.config.mjs index 4678774..88d7f53 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,6 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + analytics: true, +}; export default nextConfig; diff --git a/src/app/dashboard/analytics/page.tsx b/src/app/dashboard/analytics/page.tsx index aa19429..7909005 100644 --- a/src/app/dashboard/analytics/page.tsx +++ b/src/app/dashboard/analytics/page.tsx @@ -6,7 +6,10 @@ import { Analytics } from "@vercel/analytics/react"; export default function AnalyticsDashboard() { return ( - +
+

Analytics

+ +
); } From 786be488da2c065bea0046fbfba6fd2c9a8b3516 Mon Sep 17 00:00:00 2001 From: Claudio Sciotto Date: Mon, 3 Feb 2025 12:00:13 -0500 Subject: [PATCH 2/5] test debug false --- src/app/dashboard/analytics/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/dashboard/analytics/page.tsx b/src/app/dashboard/analytics/page.tsx index 7909005..e72db68 100644 --- a/src/app/dashboard/analytics/page.tsx +++ b/src/app/dashboard/analytics/page.tsx @@ -8,7 +8,7 @@ export default function AnalyticsDashboard() {

Analytics

- +
); From 600776a5e1e58e6fcaab04a690253a3390011999 Mon Sep 17 00:00:00 2001 From: Claudio Sciotto Date: Mon, 3 Feb 2025 13:03:27 -0500 Subject: [PATCH 3/5] Analytics done --- next.config.mjs | 4 +- src/app/client-layout.tsx | 18 ++-- src/app/dashboard/analytics/page.tsx | 132 ++++++++++++++++++++++-- src/lib/firebase/analytics.tsx | 38 +++++++ src/lib/firebase/firebase.js | 6 +- src/lib/firebase/firebaseOperations.tsx | 16 ++- 6 files changed, 190 insertions(+), 24 deletions(-) create mode 100644 src/lib/firebase/analytics.tsx diff --git a/next.config.mjs b/next.config.mjs index 88d7f53..4678774 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,6 +1,4 @@ /** @type {import('next').NextConfig} */ -const nextConfig = { - analytics: true, -}; +const nextConfig = {}; export default nextConfig; diff --git a/src/app/client-layout.tsx b/src/app/client-layout.tsx index 365cd5e..026afe6 100644 --- a/src/app/client-layout.tsx +++ b/src/app/client-layout.tsx @@ -8,6 +8,7 @@ import { QueryClientProvider } from "@tanstack/react-query"; import { queryClient } from "@/lib/react-query/queryClient"; import { useEffect, useState } from "react"; import { usePathname } from "next/navigation"; +import { trackPageView } from "@/lib/firebase/analytics"; export default function ClientLayout({ children, @@ -22,13 +23,16 @@ export default function ClientLayout({ const isAdminRoute = pathname?.startsWith("/dashboard"); useEffect(() => { - if(isAdminRoute) { - document.body.style.backgroundColor = "#fff" - } - else { - document.body.style.backgroundColor = "#080d14" - } - },[isAdminRoute]) + if (isAdminRoute) { + document.body.style.backgroundColor = "#fff"; + } else { + document.body.style.backgroundColor = "#080d14"; + } + }, [isAdminRoute]); + + useEffect(() => { + trackPageView(pathname); + }, [pathname]); return ( diff --git a/src/app/dashboard/analytics/page.tsx b/src/app/dashboard/analytics/page.tsx index e72db68..b675c85 100644 --- a/src/app/dashboard/analytics/page.tsx +++ b/src/app/dashboard/analytics/page.tsx @@ -1,15 +1,133 @@ "use client"; +import { useEffect, useState } from "react"; +import { + collection, + query, + orderBy, + getDocs, + where, + Timestamp, + deleteDoc, +} from "firebase/firestore"; +import { db } from "@/lib/firebase/firebase"; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, +} from "recharts"; -import ProtectedRoute from "@/components/admin/auth/ProtectedRoute"; -import { Analytics } from "@vercel/analytics/react"; +interface PageViewData { + date: string; + views: number; +} + +// AnalyticsDashboard.tsx +interface WeeklyViewData { + weekLabel: string; + views: number; +} export default function AnalyticsDashboard() { + const [weeklyData, setWeeklyData] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchWeeklyViews = async () => { + const yearAgo = new Date(); + yearAgo.setFullYear(yearAgo.getFullYear() - 1); + + const q = query( + collection(db, "weeklyViews"), + where("timestamp", ">=", yearAgo), + orderBy("timestamp", "asc"), + ); + + const snapshot = await getDocs(q); + const viewsByWeek: { [key: string]: number } = {}; + + snapshot.forEach((doc) => { + const data = doc.data(); + const weekLabel = `W${data.weekNumber}-${data.year}`; + viewsByWeek[weekLabel] = (viewsByWeek[weekLabel] || 0) + 1; + }); + + const formattedData = Object.entries(viewsByWeek).map( + ([weekLabel, views]) => ({ + weekLabel, + views, + }), + ); + + setWeeklyData(formattedData); + setLoading(false); + }; + + fetchWeeklyViews(); + + // Cleanup old data + const cleanupOldData = async () => { + const yearAgo = new Date(); + yearAgo.setFullYear(yearAgo.getFullYear() - 1); + + const oldViews = query( + collection(db, "weeklyViews"), + where("timestamp", "<=", yearAgo), + ); + + const snapshot = await getDocs(oldViews); + snapshot.forEach((doc) => deleteDoc(doc.ref)); + }; + + cleanupOldData(); + }, []); + + if (loading) + return ( +
Loading...
+ ); + + const totalViews = weeklyData.reduce((sum, week) => sum + week.views, 0); + const avgViews = Math.round(totalViews / weeklyData.length); + return ( - -
-

Analytics

- +
+
+
+

Total Views (in past year)

+

{totalViews}

+
+ +
+

Average Weekly Views

+

{avgViews}

+
+
+ +
+

Weekly Page Views

+
+ + + + + + + + + + +
- +
); } diff --git a/src/lib/firebase/analytics.tsx b/src/lib/firebase/analytics.tsx new file mode 100644 index 0000000..3b4dda6 --- /dev/null +++ b/src/lib/firebase/analytics.tsx @@ -0,0 +1,38 @@ +// analytics.ts +import { db } from "@/lib/firebase/firebase"; +import { + collection, + addDoc, + getDocs, + query, + where, + serverTimestamp, +} from "firebase/firestore"; + +export const getPageViews = async (startDate: Date, endDate: Date) => { + const q = query( + collection(db, "pageViews"), + where("timestamp", ">=", startDate), + where("timestamp", "<=", endDate), + ); + return await getDocs(q); +}; + +function getWeekNumber(date: Date): number { + const firstDayOfYear = new Date(date.getFullYear(), 0, 1); + const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000; + return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7); +} + +export const trackPageView = async (page: string) => { + const now = new Date(); + const weekNumber = getWeekNumber(now); + const year = now.getFullYear(); + + await addDoc(collection(db, "weeklyViews"), { + page, + weekNumber, + year, + timestamp: serverTimestamp(), + }); +}; diff --git a/src/lib/firebase/firebase.js b/src/lib/firebase/firebase.js index 545d8c7..8f4afe3 100644 --- a/src/lib/firebase/firebase.js +++ b/src/lib/firebase/firebase.js @@ -3,6 +3,7 @@ import { initializeApp } from "firebase/app"; import { getFirestore } from "firebase/firestore"; import { getAuth, Auth } from "firebase/auth"; import { getStorage } from "firebase/storage"; +import { getAnalytics } from "firebase/analytics"; // TODO: Add SDKs for Firebase products that you want to use // https://firebase.google.com/docs/web/setup#available-libraries @@ -16,7 +17,7 @@ const firebaseConfig = { storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, - measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID + measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, }; // Initialize Firebase @@ -24,5 +25,6 @@ const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); const storage = getStorage(app); +const analytics = getAnalytics(app); -export { db, auth, app, storage }; \ No newline at end of file +export { db, auth, app, storage, analytics }; diff --git a/src/lib/firebase/firebaseOperations.tsx b/src/lib/firebase/firebaseOperations.tsx index ca34da7..f6cf20a 100644 --- a/src/lib/firebase/firebaseOperations.tsx +++ b/src/lib/firebase/firebaseOperations.tsx @@ -1,5 +1,12 @@ -import { db } from '@/lib/firebase/firebase'; -import { collection, getDocs, addDoc, updateDoc, deleteDoc, doc } from 'firebase/firestore'; +import { db } from "@/lib/firebase/firebase"; +import { + collection, + getDocs, + addDoc, + updateDoc, + deleteDoc, + doc, +} from "firebase/firestore"; export interface Project { id: string; //ID of the project NOT A COLUMN IN THE DATABASE @@ -21,11 +28,10 @@ export interface Project { Builders: string[]; // An array of builder names or identifiers } - // READ Operation export const getAllProjects = async () => { const querySnapshot = await getDocs(collection(db, "Projects")); - return querySnapshot.docs.map(doc => ({ + return querySnapshot.docs.map((doc) => ({ id: doc.id, ...doc.data(), })) as Project[]; @@ -42,7 +48,7 @@ async function addData() { const docRef = await addDoc(collection(db, "Projects"), { name: "Claudio Sciotto", email: "claudio@example.com", - age: 22 + age: 22, }); console.log("Document written with ID: ", docRef.id); } catch (e) { From 6d608b01771261e585580fb2527f07afbafe5870 Mon Sep 17 00:00:00 2001 From: Claudio Sciotto Date: Mon, 3 Feb 2025 13:39:32 -0500 Subject: [PATCH 4/5] Finished Analytics --- src/app/dashboard/analytics/page.tsx | 41 ++++++++++------------- src/lib/firebase/addMockData.tsx | 49 ++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 24 deletions(-) create mode 100644 src/lib/firebase/addMockData.tsx diff --git a/src/app/dashboard/analytics/page.tsx b/src/app/dashboard/analytics/page.tsx index b675c85..5abbf10 100644 --- a/src/app/dashboard/analytics/page.tsx +++ b/src/app/dashboard/analytics/page.tsx @@ -20,6 +20,7 @@ import { Legend, ResponsiveContainer, } from "recharts"; +import AddMockDataButton from "@/lib/firebase/addMockData"; interface PageViewData { date: string; @@ -50,40 +51,31 @@ export default function AnalyticsDashboard() { const snapshot = await getDocs(q); const viewsByWeek: { [key: string]: number } = {}; + // Get unique week-year combinations snapshot.forEach((doc) => { const data = doc.data(); - const weekLabel = `W${data.weekNumber}-${data.year}`; - viewsByWeek[weekLabel] = (viewsByWeek[weekLabel] || 0) + 1; + const key = `${data.year}-${data.weekNumber}`; + viewsByWeek[key] = (viewsByWeek[key] || 0) + 1; }); - const formattedData = Object.entries(viewsByWeek).map( - ([weekLabel, views]) => ({ - weekLabel, - views, - }), - ); + // Sort by year and week + const formattedData = Object.entries(viewsByWeek) + .map(([key, views]) => { + const [year, week] = key.split("-"); + return { + weekLabel: `W${week}-${year}`, + views, + sortKey: `${year}${week.padStart(2, "0")}`, + }; + }) + .sort((a, b) => a.sortKey.localeCompare(b.sortKey)) + .map(({ weekLabel, views }) => ({ weekLabel, views })); setWeeklyData(formattedData); setLoading(false); }; fetchWeeklyViews(); - - // Cleanup old data - const cleanupOldData = async () => { - const yearAgo = new Date(); - yearAgo.setFullYear(yearAgo.getFullYear() - 1); - - const oldViews = query( - collection(db, "weeklyViews"), - where("timestamp", "<=", yearAgo), - ); - - const snapshot = await getDocs(oldViews); - snapshot.forEach((doc) => deleteDoc(doc.ref)); - }; - - cleanupOldData(); }, []); if (loading) @@ -107,6 +99,7 @@ export default function AnalyticsDashboard() {

{avgViews}

+

Weekly Page Views

diff --git a/src/lib/firebase/addMockData.tsx b/src/lib/firebase/addMockData.tsx new file mode 100644 index 0000000..44b99a6 --- /dev/null +++ b/src/lib/firebase/addMockData.tsx @@ -0,0 +1,49 @@ +// addMockData.ts +import { addDoc, collection, serverTimestamp } from "firebase/firestore"; +import { db } from "@/lib/firebase/firebase"; + +function getWeekNumber(date: Date): number { + const firstDayOfYear = new Date(date.getFullYear(), 0, 1); + const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000; + return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7); +} + +const pages = ["/home", "/projects", "/events", "/about"]; +const currentWeek = getWeekNumber(new Date()); +const prevWeek = currentWeek - 1; +const nextWeek = currentWeek + 1; +const year = new Date().getFullYear(); + +export default function AddMockDataButton() { + const addMockData = async () => { + const weeks = [ + { week: prevWeek, year }, + { week: currentWeek, year }, + { week: nextWeek, year }, + ]; + + for (const { week, year } of weeks) { + for (const page of pages) { + const viewCount = Math.floor(Math.random() * 20) + 30; + for (let i = 0; i < viewCount; i++) { + await addDoc(collection(db, "weeklyViews"), { + page, + weekNumber: week, + year, + timestamp: serverTimestamp(), + }); + } + } + } + alert("Mock data added for previous, current and next week"); + }; + + return ( + + ); +} From 5cb7576625989720b312ebb314eb88bce56e1868 Mon Sep 17 00:00:00 2001 From: Claudio Sciotto Date: Mon, 3 Feb 2025 13:48:56 -0500 Subject: [PATCH 5/5] Fixed deployment* --- src/app/dashboard/analytics/page.tsx | 2 -- src/lib/firebase/addMockData.tsx | 49 ---------------------------- 2 files changed, 51 deletions(-) delete mode 100644 src/lib/firebase/addMockData.tsx diff --git a/src/app/dashboard/analytics/page.tsx b/src/app/dashboard/analytics/page.tsx index 5abbf10..e51d49b 100644 --- a/src/app/dashboard/analytics/page.tsx +++ b/src/app/dashboard/analytics/page.tsx @@ -20,7 +20,6 @@ import { Legend, ResponsiveContainer, } from "recharts"; -import AddMockDataButton from "@/lib/firebase/addMockData"; interface PageViewData { date: string; @@ -99,7 +98,6 @@ export default function AnalyticsDashboard() {

{avgViews}

-

Weekly Page Views

diff --git a/src/lib/firebase/addMockData.tsx b/src/lib/firebase/addMockData.tsx deleted file mode 100644 index 44b99a6..0000000 --- a/src/lib/firebase/addMockData.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// addMockData.ts -import { addDoc, collection, serverTimestamp } from "firebase/firestore"; -import { db } from "@/lib/firebase/firebase"; - -function getWeekNumber(date: Date): number { - const firstDayOfYear = new Date(date.getFullYear(), 0, 1); - const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000; - return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7); -} - -const pages = ["/home", "/projects", "/events", "/about"]; -const currentWeek = getWeekNumber(new Date()); -const prevWeek = currentWeek - 1; -const nextWeek = currentWeek + 1; -const year = new Date().getFullYear(); - -export default function AddMockDataButton() { - const addMockData = async () => { - const weeks = [ - { week: prevWeek, year }, - { week: currentWeek, year }, - { week: nextWeek, year }, - ]; - - for (const { week, year } of weeks) { - for (const page of pages) { - const viewCount = Math.floor(Math.random() * 20) + 30; - for (let i = 0; i < viewCount; i++) { - await addDoc(collection(db, "weeklyViews"), { - page, - weekNumber: week, - year, - timestamp: serverTimestamp(), - }); - } - } - } - alert("Mock data added for previous, current and next week"); - }; - - return ( - - ); -}