From b4611816524977f7593aa45ffbc10fcd4abd9b8e Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 19:53:07 +0800 Subject: [PATCH 01/19] feat: reuse code from `dashboard` for `admin` --- apps/web/app/admin/page.tsx | 655 ++++++++++++++++++++++++------------ 1 file changed, 432 insertions(+), 223 deletions(-) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index 0684a09..46876b1 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -1,237 +1,446 @@ "use client"; +import { useState } 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 router = useRouter(); - const handleSignOut = async () => { - await signOut(); - router.refresh(); - }; + const { data, isLoading } = trpc.user.getCurrent.useQuery(); + + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + const { theme, toggleTheme } = useTheme(); - const goToDashboard = () => router.push("/dashboard"); - const goToMap = () => router.push("/"); + const goToMap = () => router.push("/"); + + const handleSignOut = async () => { + await signOut(); + router.refresh(); + }; - return ( -
- -
- -
- - {/* HEADER SECTION */} -
-
- {/* Admin Shield/Lock Icon */} - - - - -
- -

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

-

You have accessed the restricted area. 🚨

-
- - {/* ACTIONS PORTAL */} -
- - - -
- - {/* FOOTER & SIGN OUT */} -
- -
+ return ( +
+ {/* --- 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. 🚨

+
+ + {/* --- DASHBOARD GRID --- */} +
+
+
- ); +
+ + +
+ ); } \ No newline at end of file From af820a6496101b9cdf65ced364e8f8576ffe03a6 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 20:00:13 +0800 Subject: [PATCH 02/19] feat: ready blank cards --- apps/web/app/admin/page.tsx | 68 ++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index 46876b1..acf6f06 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -120,11 +120,77 @@ export default function AdminDashboard() { {/* --- DASHBOARD GRID --- */}
+
+
+

OVERALL PIN STATISTICS

+
+
+
+ Overall Pin Statistics Go Here +
+
+
+ +
+
+

OVERALL USER STATISTICS

+
+
+
+ Overall User Statistics Go Here +
+
+
+ +
+
+

PENDING PIN VERIFICATIONS

+
+
+
+ Pending Pins Queue Go Here +
+
+
+ +
+
+

RECENTLY VERIFIED PINS

+
+
+
+ Newly Verified Pins Go Here +
+
+
+ +
+
+

NEWEST REGISTRATIONS

+
+
+
+ Newest Accounts List Go Here +
+
+
+ +
+
+

TOP USERS

+
+
+
+ Top Users (Leaderboard) Go Here +
+
+
+
- + - - ); + + ); } \ No newline at end of file From f88502d22c41ffd552737a195d46385cb819fbe3 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 20:04:30 +0800 Subject: [PATCH 04/19] feat: create mock global pin stats --- apps/web/app/admin/page.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index 27c9441..dc18b8e 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -22,6 +22,22 @@ export default function AdminDashboard() { router.refresh(); }; + 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; + return (
{/* --- MOBILE OVERLAY --- */} From b359a063068243c0e6b33541f8806bacf2ddeb42 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 20:12:58 +0800 Subject: [PATCH 05/19] feat: fill overall pin statistics card --- apps/web/app/admin/page.tsx | 200 +++++++++++++++++++++++++++++++++++- 1 file changed, 197 insertions(+), 3 deletions(-) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index dc18b8e..39fe937 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -140,9 +140,75 @@ export default function AdminDashboard() {

OVERALL PIN STATISTICS

-
-
- Overall Pin Statistics Go Here +
+ + {/* Top Stats Grid */} +
+
+ TOTAL PINS IN MAP + {globalPinStats.totalPins} +
+
+ AWAITING ACTION + + {globalPinStats.pendingPins} + +
+
+ + {/* Verification Integrity Bar */} +
+
+ GLOBAL VERIFICATION + + {globalVerificationRate}% + +
+
+
+
+
+ {globalPinStats.verifiedPins} VERIFIED + {globalPinStats.pendingPins} PENDING + {globalPinStats.rejectedPins} REJECTED +
+
+ + {/* Category Distribution */} +
+ 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} +
+
+
+
+
+ ); + })} +
@@ -507,6 +573,134 @@ export default function AdminDashboard() { .locate-btn { background: transparent; border: 1px solid var(--border-color); border-radius: 8px; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; color: var(--text-secondary); cursor: pointer; transition: all 0.2s; flex-shrink: 0; } .locate-btn:hover { background: color-mix(in srgb, var(--neon-blue) 15%, transparent); border-color: var(--neon-blue); color: var(--neon-blue); transform: scale(1.05); } + .telemetry-body { + gap: 24px; + } + + .stat-label { + font-family: var(--font-chakra); + font-size: 10px; + font-weight: 800; + color: var(--text-secondary); + letter-spacing: 0.15em; + } + + .telemetry-top-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + } + + .stat-block { + background: var(--bg-panel-hover); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 16px; + display: flex; + flex-direction: column; + gap: 4px; + } + + .stat-value { + font-family: var(--font-cubao-wide); + font-size: 32px; + color: var(--text-primary); + letter-spacing: 0.05em; + } + + .integrity-section { + display: flex; + flex-direction: column; + gap: 8px; + } + + .integrity-header { + display: flex; + justify-content: space-between; + align-items: flex-end; + } + + .integrity-percent { + font-family: var(--font-chakra); + font-size: 14px; + font-weight: 800; + color: var(--neon-green); + } + + .progress-track { + height: 6px; + background: var(--bg-panel-hover); + border-radius: 3px; + overflow: hidden; + } + + .progress-fill { + height: 100%; + background: var(--pin-food); + box-shadow: 0 0 10px color-mix(in srgb, var(--pin-food) 50%, transparent); + border-radius: 3px; + transition: width 1s cubic-bezier(0.175, 0.885, 0.32, 1.275); + } + + .integrity-details { + display: flex; + gap: 12px; + font-family: var(--font-chakra); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.05em; + } + + .detail-item.verified { color: var(--neon-green); } + .detail-item.pending { color: var(--neon-yellow, #FFD700); } + .detail-item.rejected { color: #ff4d4d; } + + /* Category Distribution */ + .distribution-section { + display: flex; + flex-direction: column; + gap: 12px; + } + + .category-list { + display: flex; + flex-direction: column; + gap: 10px; + } + + .category-row { + display: flex; + flex-direction: column; + gap: 4px; + } + + .cat-info { + display: flex; + justify-content: space-between; + font-family: var(--font-chakra); + font-size: 11px; + font-weight: 700; + letter-spacing: 0.1em; + } + + .cat-count { + color: var(--text-primary); + font-family: var(--font-nunito); + } + + .cat-track { + height: 4px; + background: var(--bg-panel-hover); + border-radius: 2px; + overflow: hidden; + } + + .cat-fill { + height: 100%; + border-radius: 2px; + transition: width 1s cubic-bezier(0.175, 0.885, 0.32, 1.275); + } + /* Custom Scrollbar */ .custom-vertical-scrollbar::-webkit-scrollbar { width: 8px; } .custom-vertical-scrollbar::-webkit-scrollbar-track { background: transparent; } From 3cc11770cdfb13205957b74115a87f9962a2a59a Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 20:17:59 +0800 Subject: [PATCH 06/19] feat: create mock global user stats --- apps/web/app/admin/page.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index 39fe937..02e8db0 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -38,6 +38,15 @@ export default function AdminDashboard() { 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, + }; + return (
{/* --- MOBILE OVERLAY --- */} From 9745cd0f70141baac3f81858e7c29754a01f78e8 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 20:23:15 +0800 Subject: [PATCH 07/19] feat: fill overall user statistics card --- apps/web/app/admin/page.tsx | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index 02e8db0..32d233b 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -226,9 +226,33 @@ export default function AdminDashboard() {

OVERALL USER STATISTICS

-
-
- Overall User Statistics Go Here + +
+
+
+ TOTAL USERS + {globalUserStats.totalUsers} +
+
+ TOTAL COMMENTS + {globalUserStats.totalComments} +
+
+ AVERAGE PINS / USER + {globalUserStats.avgPins} +
+
+ AVERAGE COMMENTS / USER + {globalUserStats.avgComments} +
+
+ NEW USERS FOR THE LAST 7 DAYS + {globalUserStats.newUsers7Days} +
+
+ NEW USERS FOR THE LAST 30 DAYS + {globalUserStats.newUsers30Days} +
From 91a3a0f90d38875d11012561192ef131989be336 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 20:25:53 +0800 Subject: [PATCH 08/19] feat: create mock global pending pins --- apps/web/app/admin/page.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index 32d233b..a142e55 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -47,6 +47,12 @@ export default function AdminDashboard() { 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" }, + ]; + return (
{/* --- MOBILE OVERLAY --- */} From 5c5741718be6f35753f8f39671c5e5398a897195 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 20:30:14 +0800 Subject: [PATCH 09/19] feat: fill overall pending pin verifications card --- apps/web/app/admin/page.tsx | 81 ++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index a142e55..bc64928 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -267,9 +267,59 @@ export default function AdminDashboard() {

PENDING PIN VERIFICATIONS

+
-
- Pending Pins Queue Go Here +
+ {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)} + +
+
+ +
+ + + +
+ +
+ ); + })}
@@ -740,6 +790,33 @@ export default function AdminDashboard() { transition: width 1s cubic-bezier(0.175, 0.885, 0.32, 1.275); } + /* Admin Action Buttons */ + .approve-btn { + background: transparent; + border: 1px solid var(--border-color); + border-radius: 8px; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); + flex-shrink: 0; + } + + .approve-btn:hover { + background: color-mix(in srgb, var(--neon-green) 15%, transparent); + border-color: var(--neon-green); + color: var(--neon-green); + transform: scale(1.05); + } + + .approve-btn:active { + transform: scale(0.95); + } + /* Custom Scrollbar */ .custom-vertical-scrollbar::-webkit-scrollbar { width: 8px; } .custom-vertical-scrollbar::-webkit-scrollbar-track { background: transparent; } From fbcfab3ae521e63379dd4130fc49bd4335471544 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 20:34:06 +0800 Subject: [PATCH 10/19] feat: add reject pin button --- apps/web/app/admin/page.tsx | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index bc64928..c6b9c6e 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -305,6 +305,18 @@ export default function AdminDashboard() { + +
+ {/* 4. Newly Verified Pins */}

RECENTLY VERIFIED PINS

+
-
- Newly Verified Pins Go Here +
+ {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)} + +
+
+ +
+ +
+
+ ); + })}
From a3e2c90a623d6e8ccacaf93516c9f4b4252a6952 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 20:44:18 +0800 Subject: [PATCH 13/19] feat: create mock users lists --- apps/web/app/admin/page.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index ac2509e..7922199 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -59,6 +59,20 @@ export default function AdminDashboard() { { 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 (
{/* --- MOBILE OVERLAY --- */} From ced0b815ba2bf7fac72fe8be2ad0776ccf447fd9 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 20:52:15 +0800 Subject: [PATCH 14/19] feat: fill newest users card and top users card --- apps/web/app/admin/page.tsx | 183 ++++++++++++++++++++++++++++++++++-- 1 file changed, 176 insertions(+), 7 deletions(-) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index 7922199..2ab798f 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -356,7 +356,6 @@ export default function AdminDashboard() {
- {/* 4. Newly Verified Pins */}

RECENTLY VERIFIED PINS

@@ -408,22 +407,77 @@ export default function AdminDashboard() {
-

NEWEST REGISTRATIONS

+

NEWEST USERS

-
- Newest Accounts List Go Here +
+ {recentUsers.map((user) => ( +
+
+
+ {user.name.charAt(0).toUpperCase()} +
+
+ {user.name} + + {user.email} • {user.joinedAt} + +
+
+ + + +
+ ))}
-

TOP USERS

+

TOP USERS BY PINS

-
- Top Users (Leaderboard) Go Here +
+ {topUsers.map((user, index) => ( +
+ +
+
+ {user.name.charAt(0).toUpperCase()} +
+
+ {user.name} + Rank #{user.rank} Operator +
+
+ +
+ + {user.pinCount} + + PINS +
+ +
+ ))}
@@ -914,6 +968,121 @@ export default function AdminDashboard() { transform: scale(0.95); } + /* --- USER LIST STYLES --- */ + .user-list { + display: flex; + flex-direction: column; + gap: 12px; + } + + .user-list-item { + display: flex; + align-items: center; + justify-content: space-between; + background: var(--bg-panel-hover); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 12px 16px; + transition: all 0.2s ease; + } + + .user-list-item:hover { + border-color: color-mix(in srgb, var(--text-secondary) 50%, transparent); + background: color-mix(in srgb, var(--bg-panel-hover) 80%, var(--border-color)); + } + + .user-info-group { + display: flex; + align-items: center; + gap: 16px; + } + + /* Circular Avatar Layout */ + .user-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + border: 1.5px solid var(--neon-blue); + color: var(--neon-blue); + background: color-mix(in srgb, var(--neon-blue) 15%, transparent); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + font-family: var(--font-cubao-wide); + font-size: 16px; + } + + .user-text { + display: flex; + flex-direction: column; + gap: 4px; + } + + .user-name { + font-family: var(--font-chakra); + font-size: 14px; + font-weight: 700; + color: var(--text-primary); + letter-spacing: 0.05em; + } + + .user-meta { + font-family: ui-monospace, SFMono-Regular, Consolas, monospace; + font-size: 11px; + color: var(--text-secondary); + } + + /* Admin Action Buttons */ + .view-user-btn { + background: transparent; + border: 1px solid var(--border-color); + border-radius: 8px; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); + flex-shrink: 0; + } + + .view-user-btn:hover { + background: color-mix(in srgb, var(--neon-blue) 15%, transparent); + border-color: var(--neon-blue); + color: var(--neon-blue); + transform: scale(1.05); + } + + .view-user-btn:active { + transform: scale(0.95); + } + + /* Leaderboard Count Alignment */ + .pin-count-display { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 2px; + } + + .count-number { + font-family: var(--font-cubao-wide); + font-size: 20px; + color: var(--text-primary); + line-height: 1; + } + + .count-label { + font-family: var(--font-chakra); + font-size: 10px; + font-weight: 800; + color: var(--text-secondary); + letter-spacing: 0.1em; + } + /* Custom Scrollbar */ .custom-vertical-scrollbar::-webkit-scrollbar { width: 8px; } .custom-vertical-scrollbar::-webkit-scrollbar-track { background: transparent; } From 880c40ccbdbf03c70139b8ce7180f133d09b9dda Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 20:56:42 +0800 Subject: [PATCH 15/19] fix: user "week" and "month" so the card doesn't wrap --- apps/web/app/admin/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index 2ab798f..915dca3 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -272,11 +272,11 @@ export default function AdminDashboard() { {globalUserStats.avgComments}
- NEW USERS FOR THE LAST 7 DAYS + NEW USERS FOR THE LAST WEEK {globalUserStats.newUsers7Days}
- NEW USERS FOR THE LAST 30 DAYS + NEW USERS FOR THE LAST MONTH {globalUserStats.newUsers30Days}
From fc3f12b022822768400c06e40933d8321ca038fd Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 21:00:38 +0800 Subject: [PATCH 16/19] feat: import `ScrollSpy` --- apps/web/app/admin/page.tsx | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index 915dca3..1533b49 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { signOut } from "@/lib/auth-client"; import { trpc } from "@/lib/trpc"; @@ -14,6 +14,37 @@ export default function AdminDashboard() { 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("/"); From e331669d5466bb3c80acda4ae21e08419701b9b6 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 21:02:58 +0800 Subject: [PATCH 17/19] feat: update navigation in sidebar --- apps/web/app/admin/page.tsx | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index 1533b49..3879fb3 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -123,9 +123,28 @@ export default function AdminDashboard() {
- {/* --- DASHBOARD GRID --- */} -
-
-
-

OVERALL PIN STATISTICS

-
-
- - {/* Top Stats Grid */} -
-
- TOTAL PINS IN MAP - {globalPinStats.totalPins} +
+
+

OVERVIEW

+
+
+
+

OVERALL PIN STATISTICS

-
- AWAITING ACTION - - {globalPinStats.pendingPins} - +
+ + {/* 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} +
+
+
+
+
+ ); + })} +
+
- {/* Verification Integrity Bar */} -
-
- GLOBAL VERIFICATION - - {globalVerificationRate}% - -
-
-
-
-
- {globalPinStats.verifiedPins} VERIFIED - {globalPinStats.pendingPins} PENDING - {globalPinStats.rejectedPins} REJECTED +
+
+

OVERALL USER STATISTICS

-
- {/* Category Distribution */} -
- 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} -
-
-
-
-
- ); - })} +
+
+
+ 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

+
-
-
-

OVERALL USER STATISTICS

-
+
+
+ {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)} + +
+
+ +
+ + + + + +
-
-
-
- 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} +
+ ); + })} +
-
-
-
-
-

PENDING PIN VERIFICATIONS

-
+
+
+

RECENTLY VERIFIED PINS

+
-
-
- {globalPendingPins.map((pin) => { - const color = getPinColor(pin.type); - return ( -
-
-
- {pin.title.charAt(0).toUpperCase()} +
+
+ {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)} + +
+
+ +
+ +
- -
- {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} + +
-
-
- - -
- -
- ); - })} + ))} +
+
-
-
-
-
-

RECENTLY VERIFIED PINS

-
+
+
+

TOP USERS BY PINS

+
+
+
+ {topUsers.map((user, index) => ( +
-
-
- {globalVerifiedPins.map((pin) => { - const color = getPinColor(pin.type); - return ( -
-
-
- {pin.title.charAt(0).toUpperCase()} +
+
+ {user.name.charAt(0).toUpperCase()} +
+
+ {user.name} + Rank #{user.rank} Operator +
-
- {pin.title} - - By {pin.submittedBy} • {pin.lat.toFixed(4)}, {pin.lng.toFixed(4)} +
+ + {user.pinCount} + PINS
-
-
- -
-
- ); - })} -
-
-
- -
-
-

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 -
- -
- ))} +
-
+
@@ -742,6 +754,26 @@ export default function AdminDashboard() { margin: 0; } + /* --- DASHBOARD SECTIONS --- */ + .dashboard-sections { + display: flex; + flex-direction: column; + gap: 64px; + } + + .dashboard-section { + scroll-margin-top: 24px; + } + + .section-title { + font-size: 20px; + color: var(--text-primary); + letter-spacing: 0.1em; + margin: 0 0 24px 0; + padding-bottom: 12px; + border-bottom: 1px solid var(--border-color); + } + /* --- DASHBOARD GRID & CARDS --- */ .dashboard-grid { display: grid; From 91642701b1fda5233f69b75f3243eb267526f34d Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Wed, 25 Mar 2026 21:19:50 +0800 Subject: [PATCH 19/19] feat: add "Go to Admin Dashboard" in user dashboard --- apps/web/app/dashboard/page.tsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) 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" && ( + + )}