diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index 0684a09..23b827b 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -1,237 +1,1186 @@ "use client"; +import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { signOut } from "@/lib/auth-client"; import { trpc } from "@/lib/trpc"; +import { PIN_CATEGORIES, getPinColor } from "@/data/pin-categories"; +import { useTheme } from "@/lib/ThemeContext"; export default function AdminDashboard() { const router = useRouter(); - const { data } = trpc.user.getCurrent.useQuery(); + + const { data, isLoading } = trpc.user.getCurrent.useQuery(); + + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + const { theme, toggleTheme } = useTheme(); + const [activeSection, setActiveSection] = useState("overview"); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActiveSection(entry.target.id); + } + }); + }, + { + root: document.querySelector('.content-area'), + rootMargin: "-10% 0px -70% 0px" + } + ); + + const sections = document.querySelectorAll(".dashboard-section"); + sections.forEach((section) => observer.observe(section)); + + return () => observer.disconnect(); + }, []); + + const scrollToSection = (sectionId: string) => { + const element = document.getElementById(sectionId); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + setActiveSection(sectionId); + if (window.innerWidth <= 768) setIsSidebarOpen(false); + } + }; + + const goToMap = () => router.push("/"); const handleSignOut = async () => { await signOut(); router.refresh(); }; - const goToDashboard = () => router.push("/dashboard"); - const goToMap = () => router.push("/"); + const globalPinStats = { + totalPins: 1240, + verifiedPins: 1105, + pendingPins: 85, + rejectedPins: 50, + categoryBreakdown: { + academic: 450, + food: 320, + social: 150, + transit: 200, + utility: 120, + } + }; + + const globalVerificationRate = Math.round((globalPinStats.verifiedPins / globalPinStats.totalPins) * 100) || 0; + + const globalUserStats = { + totalUsers: 342, + totalComments: 1840, + avgPins: 3.6, + avgComments: 5.3, + newUsers7Days: 14, + newUsers30Days: 45, + }; + + const globalPendingPins = [ + { id: "gp1", title: "Palma Hall Annex", lat: 14.6534, lng: 121.0691, type: "academic", submittedBy: "u1" }, + { id: "gp2", title: "KNL Tricycle Terminal", lat: 14.6552, lng: 121.0621, type: "transit", submittedBy: "u2" }, + { id: "gp3", title: "Gyud Food", lat: 14.6542, lng: 121.0665, type: "food", submittedBy: "u3" }, + ]; + + const globalVerifiedPins = [ + { id: "v1", title: "Main Library", lat: 14.6540, lng: 121.0660, type: "academic", submittedBy: "u1" }, + { id: "v2", title: "Area 2 Kiosk 4", lat: 14.6530, lng: 121.0685, type: "food", submittedBy: "u2" }, + { id: "v3", title: "AS Parking", lat: 14.6538, lng: 121.0688, type: "utility", submittedBy: "u3" }, + ]; + + const recentUsers = [ + { id: "u1", name: "User 1", email: "user1@up.edu.ph", joinedAt: "2 hours ago" }, + { id: "u2", name: "User 2", email: "user2@up.edu.ph", joinedAt: "5 hours ago" }, + { id: "u3", name: "User 3", email: "user3@up.edu.ph", joinedAt: "1 day ago" }, + { id: "u4", name: "User 4", email: "user4@up.edu.ph", joinedAt: "2 days ago" }, + ]; + + const topUsers = [ + { id: "u1", name: "User 1", pinCount: 142, rank: 1 }, + { id: "u2", name: "User 2", pinCount: 89, rank: 2 }, + { id: "u3", name: "User 3", pinCount: 75, rank: 3 }, + { id: "u4", name: "User 4", pinCount: 60, rank: 4 }, + ]; return ( -
- -
- -
- - {/* HEADER SECTION */} -
-
- {/* Admin Shield/Lock Icon */} - - - - -
- -

- ADMIN: {data?.name ? data.name.toUpperCase() : "UNKNOWN"} -

-

You have accessed the restricted area. 🚨

+
+ {/* --- MOBILE OVERLAY --- */} + {isSidebarOpen && ( +
setIsSidebarOpen(false)} + /> + )} + + {/* --- SIDEBAR --- */} + + +
+ {/* --- HEADER --- */} +
+
+ +

Admin Dashboard

+
+
+ + {/* --- MAIN --- */} +
+
+
+

+ {isLoading ? "LOADING..." : `Welcome, ${data?.name ? data.name.toUpperCase() : "ADMIN"}!`} +

+

You have accessed the restricted area. 🚨

+
+ +
+
+

OVERVIEW

+
+
+
+

OVERALL PIN STATISTICS

+
+
+ {/* Top Stats Grid */} +
+
+ TOTAL PINS IN MAP + {globalPinStats.totalPins} +
+
+ AWAITING ACTION + + {globalPinStats.pendingPins} + +
+
+ +
+
+ GLOBAL VERIFICATION + + {globalVerificationRate}% + +
+
+
+
+
+ {globalPinStats.verifiedPins} VERIFIED + {globalPinStats.pendingPins} PENDING + {globalPinStats.rejectedPins} REJECTED +
+
+ +
+ CATEGORY BREAKDOWN +
+ {PIN_CATEGORIES.map((category) => { + const count = globalPinStats.categoryBreakdown[category.id as keyof typeof globalPinStats.categoryBreakdown] || 0; + const percentage = globalPinStats.totalPins > 0 ? (count / globalPinStats.totalPins) * 100 : 0; + + return ( +
+
+ {category.label} + {count} +
+
+
+
+
+ ); + })} +
+
+
+
+ +
+
+

OVERALL USER STATISTICS

+
+ +
+
+
+ TOTAL USERS + {globalUserStats.totalUsers} +
+
+ TOTAL COMMENTS + {globalUserStats.totalComments} +
+
+ AVERAGE PINS / USER + {globalUserStats.avgPins} +
+
+ AVERAGE COMMENTS / USER + {globalUserStats.avgComments} +
+
+ NEW USERS FOR THE LAST WEEK + {globalUserStats.newUsers7Days} +
+
+ NEW USERS FOR THE LAST MONTH + {globalUserStats.newUsers30Days} +
+
+
+
+
+
+ +
+

PIN MANAGEMENT

+
+
+
+

PENDING PIN VERIFICATIONS

+
+ +
+
+ {globalPendingPins.map((pin) => { + const color = getPinColor(pin.type); + return ( +
+
+
+ {pin.title.charAt(0).toUpperCase()} +
+ +
+ {pin.title} + + By {pin.submittedBy} • {pin.lat.toFixed(4)}, {pin.lng.toFixed(4)} + +
+
+ +
+ + + + + +
+ +
+ ); + })} +
+
+
+ +
+
+

RECENTLY VERIFIED PINS

+
+ +
+
+ {globalVerifiedPins.map((pin) => { + const color = getPinColor(pin.type); + return ( +
+
+
+ {pin.title.charAt(0).toUpperCase()} +
+ +
+ {pin.title} + + By {pin.submittedBy} • {pin.lat.toFixed(4)}, {pin.lng.toFixed(4)} + +
+
+ +
+ +
+
+ ); + })} +
+
+
+
+
+ +
+

USER MANAGEMENT

+
+
+
+

NEWEST USERS

+
+
+
+ {recentUsers.map((user) => ( +
+
+
+ {user.name.charAt(0).toUpperCase()} +
+
+ {user.name} + + {user.email} • {user.joinedAt} + +
+
+ + + +
+ ))} +
+
+
+ +
+
+

TOP USERS BY PINS

+
+
+
+ {topUsers.map((user, index) => ( +
+ +
+
+ {user.name.charAt(0).toUpperCase()} +
+
+ {user.name} + Rank #{user.rank} Operator +
+
+ +
+ + {user.pinCount} + + PINS +
+ +
+ ))} +
+
+
+
+
+ +
+
+
- -
+ + ); } \ No newline at end of file diff --git a/apps/web/app/dashboard/page.tsx b/apps/web/app/dashboard/page.tsx index aa93bb8..edf1f13 100644 --- a/apps/web/app/dashboard/page.tsx +++ b/apps/web/app/dashboard/page.tsx @@ -23,6 +23,7 @@ export default function Dashboard() { }; const goToMap = () => router.push("/"); + const goToAdmin = () => router.push("/admin"); const handleSignOut = async () => { await signOut(); @@ -75,6 +76,25 @@ export default function Dashboard() {
MAIN + {(data as any)?.userRole === "admin" && ( + + )}