From f829e608bfaf675642a868423d978157dd6ab287 Mon Sep 17 00:00:00 2001 From: chark1es Date: Wed, 5 Mar 2025 17:33:17 -0800 Subject: [PATCH] admin dashboard --- src/components/dashboard/AdminDashboard.astro | 808 ++++++++++++++---- .../AdminDashboard/AdminSystemActivity.tsx | 216 +++++ 2 files changed, 874 insertions(+), 150 deletions(-) create mode 100644 src/components/dashboard/AdminDashboard/AdminSystemActivity.tsx diff --git a/src/components/dashboard/AdminDashboard.astro b/src/components/dashboard/AdminDashboard.astro index e2c9caf..7c95122 100644 --- a/src/components/dashboard/AdminDashboard.astro +++ b/src/components/dashboard/AdminDashboard.astro @@ -2,182 +2,690 @@ // Admin Dashboard Component import { Authentication } from "../../scripts/pocketbase/Authentication"; import { Get } from "../../scripts/pocketbase/Get"; +import { SendLog } from "../../scripts/pocketbase/SendLog"; +import { + Collections, + type User, + type Event, + type Officer, + type Reimbursement, +} from "../../schemas/pocketbase"; +import { Icon } from "astro-icon/components"; +import AdminSystemActivity from "./AdminDashboard/AdminSystemActivity"; const auth = Authentication.getInstance(); const get = Get.getInstance(); -// Fetch some basic stats for the admin dashboard +// Fetch initial data for the dashboard +let users: User[] = []; +let officers: Officer[] = []; +let events: Event[] = []; +let reimbursements: Reimbursement[] = []; let userCount = 0; let officerCount = 0; let eventCount = 0; let reimbursementCount = 0; +let pendingReimbursements = 0; +let upcomingEvents = 0; + +// Interface for expanded reimbursement data +interface ExpandedReimbursement extends Reimbursement { + expand?: { + submitted_by?: User; + }; +} try { - if (auth.isAuthenticated()) { - const userResponse = await get.getList("users", 1, 1); - userCount = userResponse.totalItems; + if (auth.isAuthenticated()) { + // Get users with pagination + const userResponse = await get.getList( + Collections.USERS, + 1, + 50, + "", + "-created" + ); + users = userResponse.items; + userCount = userResponse.totalItems; - const officerResponse = await get.getList("officers", 1, 1); - officerCount = officerResponse.totalItems; + // Get officers with user expansion + const officerResponse = await get.getList( + Collections.OFFICERS, + 1, + 50, + "", + "-created", + { expand: "user" } + ); + officers = officerResponse.items; + officerCount = officerResponse.totalItems; - const eventResponse = await get.getList("events", 1, 1); - eventCount = eventResponse.totalItems; + // Get events + const eventResponse = await get.getList( + Collections.EVENTS, + 1, + 50, + "", + "-start_date" + ); + events = eventResponse.items; + eventCount = eventResponse.totalItems; - const reimbursementResponse = await get.getList("reimbursement", 1, 1); - reimbursementCount = reimbursementResponse.totalItems; - } + // Get upcoming events + const now = new Date().toISOString(); + const upcomingEventsResponse = await get.getList( + Collections.EVENTS, + 1, + 1, + `start_date > "${now}" && published = true`, + "start_date" + ); + upcomingEvents = upcomingEventsResponse.totalItems; + + // Get reimbursements with user expansion + const reimbursementResponse = await get.getList( + Collections.REIMBURSEMENTS, + 1, + 50, + "", + "-created", + { expand: "submitted_by" } + ); + reimbursements = reimbursementResponse.items; + reimbursementCount = reimbursementResponse.totalItems; + + // Get pending reimbursements + const pendingReimbursementsResponse = await get.getList( + Collections.REIMBURSEMENTS, + 1, + 1, + `status = "submitted" || status = "under_review"`, + "-created" + ); + pendingReimbursements = pendingReimbursementsResponse.totalItems; + } } catch (error) { - console.error("Error fetching admin dashboard data:", error); + console.error("Error fetching admin dashboard data:", error); } + +// Format date for display +const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); +}; + +// Get user name from reimbursement +const getUserName = (reimbursement: ExpandedReimbursement) => { + if (reimbursement.expand?.submitted_by?.name) { + return reimbursement.expand.submitted_by.name; + } + + // Try to find user by ID if expansion failed + const user = users.find((u) => u.id === reimbursement.submitted_by); + return user?.name || "Unknown User"; +}; ---
-
-

Administrator Dashboard

+
+

+ + Administrator Dashboard +
Real-time
+

- -
- -
-
-

Users

-

{userCount}

-

Total registered users

-
-
+ +
+ +
+
+
+

Users

+
+ +
+
+

+ {userCount} +

+
+

Total registered

+
Active
+
+
+
- -
-
-

Officers

-

{officerCount}

-

Active officers

-
-
+ +
+
+
+

Officers

+
+ +
+
+

+ {officerCount} +

+
+

Active officers

+ +
+
+
- -
-
-

Events

-

{eventCount}

-

Total events

-
-
+ +
+
+
+

Events

+
+ +
+
+

+ {eventCount} +

+
+

Total events

+ +
+
+
- -
-
-

Reimbursements

-

{reimbursementCount}

-

Total reimbursements

+ +
+
+
+

Upcoming

+
+ +
+
+

+ {upcomingEvents} +

+
+

Future events

+
Active
+
+
+
+ + +
+
+
+

Reimbursements

+
+ +
+
+

+ {reimbursementCount} +

+
+

Total requests

+ +
+
+
+ + +
+
+
+

Pending

+
+ +
+
+

+ {pendingReimbursements} +

+
+

Awaiting review

+ { + pendingReimbursements > 0 && ( +
+ Action needed +
+ ) + } +
+
+
+
+ + +
+

+ + Administrative Actions +

+
+ + + + +
+
+ + +
+ +
+
+

+ + User Management +

+
+ + + + + + + + + + + + { + users.map((user) => ( + + + + + + + + )) + } + +
NameEmailRoleLast LoginActions
{user.name}{user.email} + {user.member_id || "Member"} + + {user.last_login + ? formatDate( + user.last_login + ) + : "Never"} + +
+ + +
+
+
+
+
+ + + + + + + + + +
+ + +
+

+ + Recent System Activity +
+ Live updates +
+

+
+ +
-
- - -
-

Administrative Actions

-
- - - - -
-
- - -
-

Recent System Activity

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TimeUserActionDetails
Just nowAdminLoginAdministrator logged in
10 min agoSystemUpdateEvent request status changed
1 hour agoJane DoeCreateNew reimbursement request submitted
-
-
-
+ diff --git a/src/components/dashboard/AdminDashboard/AdminSystemActivity.tsx b/src/components/dashboard/AdminDashboard/AdminSystemActivity.tsx new file mode 100644 index 0000000..7af9ae6 --- /dev/null +++ b/src/components/dashboard/AdminDashboard/AdminSystemActivity.tsx @@ -0,0 +1,216 @@ +import { useEffect, useState } from "react"; +import { Authentication } from "../../../scripts/pocketbase/Authentication"; +import { Get } from "../../../scripts/pocketbase/Get"; +import { Collections } from "../../../schemas/pocketbase"; +import { Icon } from "astro-icon/components"; +import type { Log } from "../../../schemas/pocketbase"; + +// Extend the Log type to include expand property +interface ExtendedLog extends Log { + expand?: { + user?: { + name: string; + email: string; + id: string; + }; + }; +} + +interface AdminSystemActivityProps { + limit?: number; + autoRefresh?: boolean; + refreshInterval?: number; +} + +export default function AdminSystemActivity({ + limit = 10, + autoRefresh = true, + refreshInterval = 30000, // 30 seconds +}: AdminSystemActivityProps) { + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchLogs = async () => { + try { + setLoading(true); + const auth = Authentication.getInstance(); + const get = Get.getInstance(); + + if (!auth.isAuthenticated()) { + setError("Not authenticated"); + setLoading(false); + return; + } + + // Fetch logs with user expansion + const logsResponse = await get.getList( + Collections.LOGS, + 1, + limit, + "", + "-created", + { expand: "user" } + ); + + setLogs(logsResponse.items); + setError(null); + } catch (err) { + console.error("Error fetching logs:", err); + setError("Failed to load system logs"); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + // Initial fetch + fetchLogs(); + + // Set up auto-refresh if enabled + let intervalId: number | undefined; + if (autoRefresh && typeof window !== 'undefined') { + intervalId = window.setInterval(fetchLogs, refreshInterval); + } + + // Cleanup interval on unmount + return () => { + if (intervalId !== undefined && typeof window !== 'undefined') { + window.clearInterval(intervalId); + } + }; + }, [limit, autoRefresh, refreshInterval]); + + // Format date to a readable format + const formatDate = (dateString: string) => { + const date = new Date(dateString); + + // Check if the date is today + const today = new Date(); + const isToday = date.toDateString() === today.toDateString(); + + if (isToday) { + // If today, show time only + return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + } else { + // Otherwise show date and time + return date.toLocaleString([], { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + } + }; + + // Get appropriate icon for log type + const getLogTypeIcon = (type: string) => { + switch (type.toLowerCase()) { + case "login": + return "heroicons:login"; + case "logout": + return "heroicons:logout"; + case "create": + return "heroicons:plus-circle"; + case "update": + return "heroicons:pencil"; + case "delete": + return "heroicons:trash"; + case "error": + return "heroicons:exclamation-circle"; + default: + return "heroicons:information-circle"; + } + }; + + // Get appropriate color for log type + const getLogTypeColor = (type: string) => { + switch (type.toLowerCase()) { + case "login": + return "text-success"; + case "logout": + return "text-info"; + case "create": + return "text-primary"; + case "update": + return "text-secondary"; + case "delete": + return "text-error"; + case "error": + return "text-error"; + default: + return "text-base-content"; + } + }; + + if (loading && logs.length === 0) { + return ( +
+
+ Loading system logs... +
+ ); + } + + if (error) { + return ( +
+ + {error} +
+ ); + } + + if (logs.length === 0) { + return ( +
+ + No system logs found +
+ ); + } + + return ( +
+ + + + + + + + + + + {logs.map((log) => ( + + + + + + + ))} + +
TimeUserActionDetails
+ {formatDate(log.created)} + + {log.expand?.user?.name || "System"} + +
+ + {log.type} +
+
{log.message}
+ + {loading && logs.length > 0 && ( +
+
+ Refreshing... +
+ )} +
+ ); +} \ No newline at end of file