fix database mismatch
This commit is contained in:
parent
16addfb9d0
commit
b7493224d2
4 changed files with 908 additions and 676 deletions
183
src/components/dashboard/AdminDashboard.astro
Normal file
183
src/components/dashboard/AdminDashboard.astro
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
---
|
||||||
|
// Admin Dashboard Component
|
||||||
|
import { Authentication } from "../../scripts/pocketbase/Authentication";
|
||||||
|
import { Get } from "../../scripts/pocketbase/Get";
|
||||||
|
|
||||||
|
const auth = Authentication.getInstance();
|
||||||
|
const get = Get.getInstance();
|
||||||
|
|
||||||
|
// Fetch some basic stats for the admin dashboard
|
||||||
|
let userCount = 0;
|
||||||
|
let officerCount = 0;
|
||||||
|
let eventCount = 0;
|
||||||
|
let reimbursementCount = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (auth.isAuthenticated()) {
|
||||||
|
const userResponse = await get.getList("users", 1, 1);
|
||||||
|
userCount = userResponse.totalItems;
|
||||||
|
|
||||||
|
const officerResponse = await get.getList("officers", 1, 1);
|
||||||
|
officerCount = officerResponse.totalItems;
|
||||||
|
|
||||||
|
const eventResponse = await get.getList("events", 1, 1);
|
||||||
|
eventCount = eventResponse.totalItems;
|
||||||
|
|
||||||
|
const reimbursementResponse = await get.getList("reimbursement", 1, 1);
|
||||||
|
reimbursementCount = reimbursementResponse.totalItems;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching admin dashboard data:", error);
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-2xl mb-4">Administrator Dashboard</h2>
|
||||||
|
|
||||||
|
<!-- Stats Overview -->
|
||||||
|
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||||
|
<!-- User Stats -->
|
||||||
|
<div class="card bg-base-200">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg">Users</h3>
|
||||||
|
<p class="text-primary text-3xl font-semibold">{userCount}</p>
|
||||||
|
<p class="text-sm opacity-70">Total registered users</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Officer Stats -->
|
||||||
|
<div class="card bg-base-200">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg">Officers</h3>
|
||||||
|
<p class="text-secondary text-3xl font-semibold">{officerCount}</p>
|
||||||
|
<p class="text-sm opacity-70">Active officers</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Event Stats -->
|
||||||
|
<div class="card bg-base-200">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg">Events</h3>
|
||||||
|
<p class="text-accent text-3xl font-semibold">{eventCount}</p>
|
||||||
|
<p class="text-sm opacity-70">Total events</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reimbursement Stats -->
|
||||||
|
<div class="card bg-base-200">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg">Reimbursements</h3>
|
||||||
|
<p class="text-info text-3xl font-semibold">{reimbursementCount}</p>
|
||||||
|
<p class="text-sm opacity-70">Total reimbursements</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Actions -->
|
||||||
|
<div class="mt-6">
|
||||||
|
<h3 class="text-xl font-semibold mb-4">Administrative Actions</h3>
|
||||||
|
<div class="flex flex-wrap gap-3">
|
||||||
|
<button class="btn btn-primary">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 mr-2"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
Manage Users
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 mr-2"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z"
|
||||||
|
clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
Manage Events
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-accent">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 mr-2"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M4 4a2 2 0 00-2 2v4a2 2 0 002 2V6h10a2 2 0 00-2-2H4zm2 6a2 2 0 012-2h8a2 2 0 012 2v4a2 2 0 01-2 2H8a2 2 0 01-2-2v-4zm6 4a2 2 0 100-4 2 2 0 000 4z"
|
||||||
|
clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
Manage Finances
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-info">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 mr-2"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"></path>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M4 5a2 2 0 012-2 3 3 0 003 3h2a3 3 0 003-3 2 2 0 012 2v11a2 2 0 01-2 2H6a2 2 0 01-2-2V5zm3 4a1 1 0 000 2h.01a1 1 0 100-2H7zm3 0a1 1 0 000 2h3a1 1 0 100-2h-3zm-3 4a1 1 0 100 2h.01a1 1 0 100-2H7zm3 0a1 1 0 100 2h3a1 1 0 100-2h-3z"
|
||||||
|
clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
System Logs
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recent System Activity -->
|
||||||
|
<div class="mt-6">
|
||||||
|
<h3 class="text-xl font-semibold mb-4">Recent System Activity</h3>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Time</th>
|
||||||
|
<th>User</th>
|
||||||
|
<th>Action</th>
|
||||||
|
<th>Details</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-sm">Just now</td>
|
||||||
|
<td>Admin</td>
|
||||||
|
<td>Login</td>
|
||||||
|
<td>Administrator logged in</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-sm">10 min ago</td>
|
||||||
|
<td>System</td>
|
||||||
|
<td>Update</td>
|
||||||
|
<td>Event request status changed</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-sm">1 hour ago</td>
|
||||||
|
<td>Jane Doe</td>
|
||||||
|
<td>Create</td>
|
||||||
|
<td>New reimbursement request submitted</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Client-side functionality can be added here if needed
|
||||||
|
// For example, refreshing data or handling button clicks
|
||||||
|
</script>
|
||||||
|
|
|
@ -65,6 +65,14 @@ sections:
|
||||||
component: "SponsorAnalytics"
|
component: "SponsorAnalytics"
|
||||||
class: "text-warning hover:text-warning-focus"
|
class: "text-warning hover:text-warning-focus"
|
||||||
|
|
||||||
|
# Administrator Menu
|
||||||
|
adminDashboard:
|
||||||
|
title: "Admin Dashboard"
|
||||||
|
icon: "heroicons:shield-check"
|
||||||
|
role: "administrator"
|
||||||
|
component: "AdminDashboard"
|
||||||
|
class: "text-error hover:text-error-focus"
|
||||||
|
|
||||||
# Settings (accessible to all except sponsors)
|
# Settings (accessible to all except sponsors)
|
||||||
settings:
|
settings:
|
||||||
title: "Settings"
|
title: "Settings"
|
||||||
|
@ -98,8 +106,8 @@ categories:
|
||||||
|
|
||||||
admin:
|
admin:
|
||||||
title: "Admin Menu"
|
title: "Admin Menu"
|
||||||
sections: []
|
sections: ["adminDashboard"]
|
||||||
role: "admin"
|
role: "administrator"
|
||||||
|
|
||||||
sponsor:
|
sponsor:
|
||||||
title: "Sponsor Portal"
|
title: "Sponsor Portal"
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
---
|
---
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import { hasAccess, type OfficerStatus } from "../utils/roleAccess";
|
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { Authentication } from "../scripts/pocketbase/Authentication";
|
||||||
|
import { Get } from "../scripts/pocketbase/Get";
|
||||||
|
import { SendLog } from "../scripts/pocketbase/SendLog";
|
||||||
|
import { hasAccess, type OfficerStatus } from "../utils/roleAccess";
|
||||||
|
import { OfficerTypes } from "../schemas/pocketbase/schema";
|
||||||
|
|
||||||
const title = "Dashboard";
|
const title = "Dashboard";
|
||||||
|
|
||||||
|
@ -22,8 +26,8 @@ const components = Object.fromEntries(
|
||||||
);
|
);
|
||||||
console.log(`Loaded component: ${section.component}`); // Debug log
|
console.log(`Loaded component: ${section.component}`); // Debug log
|
||||||
return [section.component, component.default];
|
return [section.component, component.default];
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("Available components:", Object.keys(components)); // Debug log
|
console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
|
@ -59,33 +63,19 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
<!-- User Profile -->
|
<!-- User Profile -->
|
||||||
<div class="p-6 border-b border-base-200">
|
<div class="p-6 border-b border-base-200">
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
<div
|
<div id="userProfileSkeleton" class="flex items-center gap-4">
|
||||||
id="userProfileSkeleton"
|
|
||||||
class="flex items-center gap-4"
|
|
||||||
>
|
|
||||||
<div class="avatar flex items-center justify-center">
|
<div class="avatar flex items-center justify-center">
|
||||||
<div
|
<div class="w-12 h-12 rounded-xl bg-base-300 animate-pulse"></div>
|
||||||
class="w-12 h-12 rounded-xl bg-base-300 animate-pulse"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div
|
<div class="h-6 w-32 bg-base-300 animate-pulse rounded mb-2">
|
||||||
class="h-6 w-32 bg-base-300 animate-pulse rounded mb-2"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="h-5 w-20 bg-base-300 animate-pulse rounded"
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="h-5 w-20 bg-base-300 animate-pulse rounded"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Signed Out State -->
|
<!-- Signed Out State -->
|
||||||
<div
|
<div id="userProfileSignedOut" class="flex items-center gap-4 hidden">
|
||||||
id="userProfileSignedOut"
|
|
||||||
class="flex items-center gap-4 hidden"
|
|
||||||
>
|
|
||||||
<div class="avatar flex items-center justify-center">
|
<div class="avatar flex items-center justify-center">
|
||||||
<div
|
<div
|
||||||
class="w-12 h-12 rounded-xl bg-base-300 text-base-content/30 flex items-center justify-center"
|
class="w-12 h-12 rounded-xl bg-base-300 text-base-content/30 flex items-center justify-center"
|
||||||
|
@ -94,22 +84,15 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3
|
<h3 class="font-medium text-lg text-base-content/70">
|
||||||
class="font-medium text-lg text-base-content/70"
|
|
||||||
>
|
|
||||||
Signed Out
|
Signed Out
|
||||||
</h3>
|
</h3>
|
||||||
<div class="badge badge-outline mt-1 opacity-50">
|
<div class="badge badge-outline mt-1 opacity-50">Guest</div>
|
||||||
Guest
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Actual Profile -->
|
<!-- Actual Profile -->
|
||||||
<div
|
<div id="userProfileSummary" class="flex items-center gap-4 hidden">
|
||||||
id="userProfileSummary"
|
|
||||||
class="flex items-center gap-4 hidden"
|
|
||||||
>
|
|
||||||
<div class="avatar flex items-center justify-center">
|
<div class="avatar flex items-center justify-center">
|
||||||
<div
|
<div
|
||||||
class="w-12 h-12 rounded-xl bg-[#06659d] text-white ring ring-base-200 ring-offset-base-100 ring-offset-2 inline-flex items-center justify-center"
|
class="w-12 h-12 rounded-xl bg-[#06659d] text-white ring ring-base-200 ring-offset-base-100 ring-offset-2 inline-flex items-center justify-center"
|
||||||
|
@ -121,9 +104,7 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-medium text-lg" id="userName">
|
<h3 class="font-medium text-lg" id="userName">Loading...</h3>
|
||||||
Loading...
|
|
||||||
</h3>
|
|
||||||
<div
|
<div
|
||||||
class="badge badge-outline mt-1 border-[#06659d] text-[#06659d]"
|
class="badge badge-outline mt-1 border-[#06659d] text-[#06659d]"
|
||||||
id="userRole"
|
id="userRole"
|
||||||
|
@ -162,64 +143,41 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
<div id="actualMenu" class="hidden">
|
<div id="actualMenu" class="hidden">
|
||||||
{
|
{
|
||||||
Object.entries(dashboardConfig.categories).map(
|
Object.entries(dashboardConfig.categories).map(
|
||||||
([categoryKey, category]: [
|
([categoryKey, category]: [string, any]) => (
|
||||||
string,
|
|
||||||
any,
|
|
||||||
]) => (
|
|
||||||
<>
|
<>
|
||||||
<li
|
<li
|
||||||
class={`menu-title font-medium opacity-70 ${
|
class={`menu-title font-medium opacity-70 ${
|
||||||
category.role &&
|
category.role && category.role !== "none"
|
||||||
category.role !== "none"
|
|
||||||
? "hidden"
|
? "hidden"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
data-role-required={
|
data-role-required={category.role || "none"}
|
||||||
category.role || "none"
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<span>{category.title}</span>
|
<span>{category.title}</span>
|
||||||
</li>
|
</li>
|
||||||
{category.sections.map(
|
{category.sections.map((sectionKey: string) => {
|
||||||
(sectionKey: string) => {
|
const section = dashboardConfig.sections[sectionKey];
|
||||||
const section =
|
|
||||||
dashboardConfig
|
|
||||||
.sections[
|
|
||||||
sectionKey
|
|
||||||
];
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
class={
|
class={
|
||||||
section.role &&
|
section.role && section.role !== "none"
|
||||||
section.role !==
|
|
||||||
"none"
|
|
||||||
? "hidden"
|
? "hidden"
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
data-role-required={
|
data-role-required={section.role}
|
||||||
section.role
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class={`dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5 ${section.class || ""}`}
|
class={`dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5 ${section.class || ""}`}
|
||||||
data-section={
|
data-section={sectionKey}
|
||||||
sectionKey
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon name={section.icon} class="h-5 w-5" />
|
||||||
name={
|
|
||||||
section.icon
|
|
||||||
}
|
|
||||||
class="h-5 w-5"
|
|
||||||
/>
|
|
||||||
{section.title}
|
{section.title}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
})}
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -232,15 +190,10 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
class="flex-1 overflow-x-hidden overflow-y-auto bg-base-200 w-full xl:w-[calc(100%-20rem)]"
|
class="flex-1 overflow-x-hidden overflow-y-auto bg-base-200 w-full xl:w-[calc(100%-20rem)]"
|
||||||
>
|
>
|
||||||
<!-- Mobile Header -->
|
<!-- Mobile Header -->
|
||||||
<header
|
<header class="bg-base-100 p-4 shadow-md xl:hidden sticky top-0 z-40">
|
||||||
class="bg-base-100 p-4 shadow-md xl:hidden sticky top-0 z-40"
|
|
||||||
>
|
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button id="mobileSidebarToggle" class="btn btn-square btn-ghost">
|
||||||
id="mobileSidebarToggle"
|
|
||||||
class="btn btn-square btn-ghost"
|
|
||||||
>
|
|
||||||
<Icon name="heroicons:bars-3" class="h-6 w-6" />
|
<Icon name="heroicons:bars-3" class="h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
<h1 class="text-xl font-bold">IEEE UCSD</h1>
|
<h1 class="text-xl font-bold">IEEE UCSD</h1>
|
||||||
|
@ -252,11 +205,8 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
<div class="p-4 md:p-6 max-w-[1600px] mx-auto">
|
<div class="p-4 md:p-6 max-w-[1600px] mx-auto">
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
<div id="pageLoadingState" class="w-full">
|
<div id="pageLoadingState" class="w-full">
|
||||||
<div
|
<div class="flex flex-col items-center justify-center p-4 sm:p-8">
|
||||||
class="flex flex-col items-center justify-center p-4 sm:p-8"
|
<div class="loading loading-spinner loading-lg"></div>
|
||||||
>
|
|
||||||
<div class="loading loading-spinner loading-lg">
|
|
||||||
</div>
|
|
||||||
<p class="mt-4 opacity-70">Loading dashboard...</p>
|
<p class="mt-4 opacity-70">Loading dashboard...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -282,9 +232,7 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
<!-- Not Authenticated State -->
|
<!-- Not Authenticated State -->
|
||||||
<div id="notAuthenticatedState" class="hidden w-full">
|
<div id="notAuthenticatedState" class="hidden w-full">
|
||||||
<div class="card bg-base-100 shadow-xl mx-2 sm:mx-0">
|
<div class="card bg-base-100 shadow-xl mx-2 sm:mx-0">
|
||||||
<div
|
<div class="card-body items-center text-center p-4 sm:p-8">
|
||||||
class="card-body items-center text-center p-4 sm:p-8"
|
|
||||||
>
|
|
||||||
<div class="mb-4 sm:mb-6">
|
<div class="mb-4 sm:mb-6">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
@ -301,11 +249,9 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
<h2 class="card-title text-xl sm:text-2xl mb-2">
|
<h2 class="card-title text-xl sm:text-2xl mb-2">
|
||||||
Sign in to Access Dashboard
|
Sign in to Access Dashboard
|
||||||
</h2>
|
</h2>
|
||||||
<p
|
<p class="opacity-70 mb-4 sm:mb-6 text-sm sm:text-base">
|
||||||
class="opacity-70 mb-4 sm:mb-6 text-sm sm:text-base"
|
Please sign in with your IEEE UCSD account to access the
|
||||||
>
|
dashboard.
|
||||||
Please sign in with your IEEE UCSD account
|
|
||||||
to access the dashboard.
|
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
class="login-button btn btn-primary btn-lg gap-2 w-full sm:w-auto"
|
class="login-button btn btn-primary btn-lg gap-2 w-full sm:w-auto"
|
||||||
|
@ -337,14 +283,12 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
// Skip if no component is defined
|
// Skip if no component is defined
|
||||||
if (!section.component) return null;
|
if (!section.component) return null;
|
||||||
|
|
||||||
const Component =
|
const Component = components[section.component];
|
||||||
components[section.component];
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id={`${sectionKey}Section`}
|
id={`${sectionKey}Section`}
|
||||||
class={`dashboard-section hidden ${
|
class={`dashboard-section hidden ${
|
||||||
section.role &&
|
section.role && section.role !== "none"
|
||||||
section.role !== "none"
|
|
||||||
? "role-restricted"
|
? "role-restricted"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
|
@ -353,7 +297,7 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
<Component />
|
<Component />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -368,6 +312,7 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
import { Get } from "../scripts/pocketbase/Get";
|
import { Get } from "../scripts/pocketbase/Get";
|
||||||
import { SendLog } from "../scripts/pocketbase/SendLog";
|
import { SendLog } from "../scripts/pocketbase/SendLog";
|
||||||
import { hasAccess, type OfficerStatus } from "../utils/roleAccess";
|
import { hasAccess, type OfficerStatus } from "../utils/roleAccess";
|
||||||
|
import { OfficerTypes } from "../schemas/pocketbase/schema";
|
||||||
|
|
||||||
const auth = Authentication.getInstance();
|
const auth = Authentication.getInstance();
|
||||||
const get = Get.getInstance();
|
const get = Get.getInstance();
|
||||||
|
@ -377,7 +322,7 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
const pageLoadingState = document.getElementById("pageLoadingState");
|
const pageLoadingState = document.getElementById("pageLoadingState");
|
||||||
const pageErrorState = document.getElementById("pageErrorState");
|
const pageErrorState = document.getElementById("pageErrorState");
|
||||||
const notAuthenticatedState = document.getElementById(
|
const notAuthenticatedState = document.getElementById(
|
||||||
"notAuthenticatedState"
|
"notAuthenticatedState",
|
||||||
);
|
);
|
||||||
const mainContent = document.getElementById("mainContent");
|
const mainContent = document.getElementById("mainContent");
|
||||||
const sidebar = document.querySelector("aside");
|
const sidebar = document.querySelector("aside");
|
||||||
|
@ -392,9 +337,7 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
// Special handling for sponsor role
|
// Special handling for sponsor role
|
||||||
if (officerStatus === "sponsor") {
|
if (officerStatus === "sponsor") {
|
||||||
// Hide all sections first
|
// Hide all sections first
|
||||||
document
|
document.querySelectorAll("[data-role-required]").forEach((element) => {
|
||||||
.querySelectorAll("[data-role-required]")
|
|
||||||
.forEach((element) => {
|
|
||||||
element.classList.add("hidden");
|
element.classList.add("hidden");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -410,7 +353,7 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
// For non-sponsor roles, handle normally
|
// For non-sponsor roles, handle normally
|
||||||
document.querySelectorAll("[data-role-required]").forEach((element) => {
|
document.querySelectorAll("[data-role-required]").forEach((element) => {
|
||||||
const requiredRole = element.getAttribute(
|
const requiredRole = element.getAttribute(
|
||||||
"data-role-required"
|
"data-role-required",
|
||||||
) as OfficerStatus;
|
) as OfficerStatus;
|
||||||
|
|
||||||
// Skip elements that don't have a role requirement
|
// Skip elements that don't have a role requirement
|
||||||
|
@ -498,8 +441,51 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
|
|
||||||
const displayName = extendedUser.name || "Unknown User";
|
const displayName = extendedUser.name || "Unknown User";
|
||||||
const displayRole = extendedUser.member_type || "Member";
|
const displayRole = extendedUser.member_type || "Member";
|
||||||
const officerStatus = (extendedUser.officer_status ||
|
|
||||||
"") as OfficerStatus;
|
// Map the officer type from the database to our OfficerStatus type
|
||||||
|
let officerStatus: OfficerStatus = "";
|
||||||
|
|
||||||
|
// Get the officer record for this user if it exists
|
||||||
|
const officerRecords = await get.getList(
|
||||||
|
"officers",
|
||||||
|
1,
|
||||||
|
50,
|
||||||
|
`user="${user.id}"`,
|
||||||
|
"",
|
||||||
|
{
|
||||||
|
fields: ["id", "type"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (officerRecords && officerRecords.items.length > 0) {
|
||||||
|
const officerType = officerRecords.items[0].type;
|
||||||
|
|
||||||
|
// Map the officer type to our OfficerStatus
|
||||||
|
switch (officerType) {
|
||||||
|
case OfficerTypes.ADMINISTRATOR:
|
||||||
|
officerStatus = "administrator";
|
||||||
|
break;
|
||||||
|
case OfficerTypes.EXECUTIVE:
|
||||||
|
officerStatus = "executive";
|
||||||
|
break;
|
||||||
|
case OfficerTypes.GENERAL:
|
||||||
|
officerStatus = "general";
|
||||||
|
break;
|
||||||
|
case OfficerTypes.HONORARY:
|
||||||
|
officerStatus = "honorary";
|
||||||
|
break;
|
||||||
|
case OfficerTypes.PAST:
|
||||||
|
officerStatus = "past";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
officerStatus = "";
|
||||||
|
}
|
||||||
|
} else if (extendedUser.member_type === "Sponsor") {
|
||||||
|
officerStatus = "sponsor";
|
||||||
|
} else {
|
||||||
|
officerStatus = "none";
|
||||||
|
}
|
||||||
|
|
||||||
const initials = (extendedUser.name || "U")
|
const initials = (extendedUser.name || "U")
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map((n: string) => n[0])
|
.map((n: string) => n[0])
|
||||||
|
@ -523,8 +509,7 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
|
|
||||||
if (userName) userName.textContent = fallbackValues.name;
|
if (userName) userName.textContent = fallbackValues.name;
|
||||||
if (userRole) userRole.textContent = fallbackValues.role;
|
if (userRole) userRole.textContent = fallbackValues.role;
|
||||||
if (userInitials)
|
if (userInitials) userInitials.textContent = fallbackValues.initials;
|
||||||
userInitials.textContent = fallbackValues.initials;
|
|
||||||
|
|
||||||
updateSectionVisibility("" as OfficerStatus);
|
updateSectionVisibility("" as OfficerStatus);
|
||||||
}
|
}
|
||||||
|
@ -561,30 +546,25 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
try {
|
try {
|
||||||
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
|
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
|
||||||
if (pageErrorState) pageErrorState.classList.add("hidden");
|
if (pageErrorState) pageErrorState.classList.add("hidden");
|
||||||
if (notAuthenticatedState)
|
if (notAuthenticatedState) notAuthenticatedState.classList.add("hidden");
|
||||||
notAuthenticatedState.classList.add("hidden");
|
|
||||||
|
|
||||||
// Show loading states
|
// Show loading states
|
||||||
const userProfileSkeleton = document.getElementById(
|
const userProfileSkeleton = document.getElementById(
|
||||||
"userProfileSkeleton"
|
"userProfileSkeleton",
|
||||||
);
|
);
|
||||||
const userProfileSignedOut = document.getElementById(
|
const userProfileSignedOut = document.getElementById(
|
||||||
"userProfileSignedOut"
|
"userProfileSignedOut",
|
||||||
);
|
);
|
||||||
const userProfileSummary =
|
const userProfileSummary = document.getElementById("userProfileSummary");
|
||||||
document.getElementById("userProfileSummary");
|
|
||||||
const menuLoadingSkeleton = document.getElementById(
|
const menuLoadingSkeleton = document.getElementById(
|
||||||
"menuLoadingSkeleton"
|
"menuLoadingSkeleton",
|
||||||
);
|
);
|
||||||
const actualMenu = document.getElementById("actualMenu");
|
const actualMenu = document.getElementById("actualMenu");
|
||||||
|
|
||||||
if (userProfileSkeleton)
|
if (userProfileSkeleton) userProfileSkeleton.classList.remove("hidden");
|
||||||
userProfileSkeleton.classList.remove("hidden");
|
|
||||||
if (userProfileSummary) userProfileSummary.classList.add("hidden");
|
if (userProfileSummary) userProfileSummary.classList.add("hidden");
|
||||||
if (userProfileSignedOut)
|
if (userProfileSignedOut) userProfileSignedOut.classList.add("hidden");
|
||||||
userProfileSignedOut.classList.add("hidden");
|
if (menuLoadingSkeleton) menuLoadingSkeleton.classList.remove("hidden");
|
||||||
if (menuLoadingSkeleton)
|
|
||||||
menuLoadingSkeleton.classList.remove("hidden");
|
|
||||||
if (actualMenu) actualMenu.classList.add("hidden");
|
if (actualMenu) actualMenu.classList.add("hidden");
|
||||||
|
|
||||||
// Check authentication
|
// Check authentication
|
||||||
|
@ -592,8 +572,7 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
if (pageLoadingState) pageLoadingState.classList.add("hidden");
|
if (pageLoadingState) pageLoadingState.classList.add("hidden");
|
||||||
if (notAuthenticatedState)
|
if (notAuthenticatedState)
|
||||||
notAuthenticatedState.classList.remove("hidden");
|
notAuthenticatedState.classList.remove("hidden");
|
||||||
if (userProfileSkeleton)
|
if (userProfileSkeleton) userProfileSkeleton.classList.add("hidden");
|
||||||
userProfileSkeleton.classList.add("hidden");
|
|
||||||
if (userProfileSignedOut)
|
if (userProfileSignedOut)
|
||||||
userProfileSignedOut.classList.remove("hidden");
|
userProfileSignedOut.classList.remove("hidden");
|
||||||
return;
|
return;
|
||||||
|
@ -603,40 +582,83 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
await updateUserProfile(user);
|
await updateUserProfile(user);
|
||||||
|
|
||||||
// Show actual profile and hide skeleton
|
// Show actual profile and hide skeleton
|
||||||
if (userProfileSkeleton)
|
if (userProfileSkeleton) userProfileSkeleton.classList.add("hidden");
|
||||||
userProfileSkeleton.classList.add("hidden");
|
if (userProfileSummary) userProfileSummary.classList.remove("hidden");
|
||||||
if (userProfileSummary)
|
|
||||||
userProfileSummary.classList.remove("hidden");
|
|
||||||
|
|
||||||
// Hide all sections first
|
// Hide all sections first
|
||||||
document
|
document.querySelectorAll(".dashboard-section").forEach((section) => {
|
||||||
.querySelectorAll(".dashboard-section")
|
|
||||||
.forEach((section) => {
|
|
||||||
section.classList.add("hidden");
|
section.classList.add("hidden");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show appropriate default section based on role
|
// Show appropriate default section based on role
|
||||||
|
// Get the officer record for this user if it exists
|
||||||
|
let officerStatus: OfficerStatus = "none";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const officerRecords = await get.getList(
|
||||||
|
"officers",
|
||||||
|
1,
|
||||||
|
50,
|
||||||
|
`user="${user.id}"`,
|
||||||
|
"",
|
||||||
|
{
|
||||||
|
fields: ["id", "type"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (officerRecords && officerRecords.items.length > 0) {
|
||||||
|
const officerType = officerRecords.items[0].type;
|
||||||
|
|
||||||
|
// Map the officer type to our OfficerStatus
|
||||||
|
switch (officerType) {
|
||||||
|
case OfficerTypes.ADMINISTRATOR:
|
||||||
|
officerStatus = "administrator";
|
||||||
|
break;
|
||||||
|
case OfficerTypes.EXECUTIVE:
|
||||||
|
officerStatus = "executive";
|
||||||
|
break;
|
||||||
|
case OfficerTypes.GENERAL:
|
||||||
|
officerStatus = "general";
|
||||||
|
break;
|
||||||
|
case OfficerTypes.HONORARY:
|
||||||
|
officerStatus = "honorary";
|
||||||
|
break;
|
||||||
|
case OfficerTypes.PAST:
|
||||||
|
officerStatus = "past";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
officerStatus = "none";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
const extendedUser = await get.getOne("users", user.id, {
|
const extendedUser = await get.getOne("users", user.id, {
|
||||||
fields: ["officer_status"],
|
fields: ["member_type"],
|
||||||
});
|
});
|
||||||
const officerStatus = (extendedUser.officer_status ||
|
|
||||||
"") as OfficerStatus;
|
if (extendedUser.member_type === "Sponsor") {
|
||||||
|
officerStatus = "sponsor";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error determining officer status:", error);
|
||||||
|
officerStatus = "none";
|
||||||
|
}
|
||||||
|
|
||||||
let defaultSection;
|
let defaultSection;
|
||||||
let defaultButton;
|
let defaultButton;
|
||||||
|
|
||||||
if (officerStatus === "sponsor") {
|
if (officerStatus === "sponsor") {
|
||||||
defaultSection = document.getElementById(
|
defaultSection = document.getElementById("sponsorDashboardSection");
|
||||||
"sponsorDashboardSection"
|
|
||||||
);
|
|
||||||
defaultButton = document.querySelector(
|
defaultButton = document.querySelector(
|
||||||
'[data-section="sponsorDashboard"]'
|
'[data-section="sponsorDashboard"]',
|
||||||
|
);
|
||||||
|
} else if (officerStatus === "administrator") {
|
||||||
|
defaultSection = document.getElementById("adminDashboardSection");
|
||||||
|
defaultButton = document.querySelector(
|
||||||
|
'[data-section="adminDashboard"]',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
defaultSection = document.getElementById("profileSection");
|
defaultSection = document.getElementById("profileSection");
|
||||||
defaultButton = document.querySelector(
|
defaultButton = document.querySelector('[data-section="profile"]');
|
||||||
'[data-section="profile"]'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultSection) {
|
if (defaultSection) {
|
||||||
|
@ -650,8 +672,7 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
handleNavigation();
|
handleNavigation();
|
||||||
|
|
||||||
// Show actual menu and hide skeleton
|
// Show actual menu and hide skeleton
|
||||||
if (menuLoadingSkeleton)
|
if (menuLoadingSkeleton) menuLoadingSkeleton.classList.add("hidden");
|
||||||
menuLoadingSkeleton.classList.add("hidden");
|
|
||||||
if (actualMenu) actualMenu.classList.remove("hidden");
|
if (actualMenu) actualMenu.classList.remove("hidden");
|
||||||
|
|
||||||
// Show main content and hide loading
|
// Show main content and hide loading
|
||||||
|
@ -672,8 +693,7 @@ console.log("Available components:", Object.keys(components)); // Debug log
|
||||||
.querySelector(".login-button")
|
.querySelector(".login-button")
|
||||||
?.addEventListener("click", async () => {
|
?.addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
if (pageLoadingState)
|
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
|
||||||
pageLoadingState.classList.remove("hidden");
|
|
||||||
if (notAuthenticatedState)
|
if (notAuthenticatedState)
|
||||||
notAuthenticatedState.classList.add("hidden");
|
notAuthenticatedState.classList.add("hidden");
|
||||||
await auth.login();
|
await auth.login();
|
||||||
|
|
|
@ -1,15 +1,36 @@
|
||||||
export type OfficerStatus = "admin" | "executive" | "general" | "past" | "sponsor" | "none" | "";
|
export type OfficerStatus =
|
||||||
|
| "administrator"
|
||||||
|
| "executive"
|
||||||
|
| "general"
|
||||||
|
| "honorary"
|
||||||
|
| "past"
|
||||||
|
| "sponsor"
|
||||||
|
| "none"
|
||||||
|
| "";
|
||||||
type RoleHierarchy = Record<OfficerStatus, OfficerStatus[]>;
|
type RoleHierarchy = Record<OfficerStatus, OfficerStatus[]>;
|
||||||
|
|
||||||
export function hasAccess(userRole: OfficerStatus, requiredRole: OfficerStatus): boolean {
|
export function hasAccess(
|
||||||
|
userRole: OfficerStatus,
|
||||||
|
requiredRole: OfficerStatus,
|
||||||
|
): boolean {
|
||||||
const roleHierarchy: RoleHierarchy = {
|
const roleHierarchy: RoleHierarchy = {
|
||||||
"admin": ["admin", "sponsor", "executive", "general", "past", "none", ""],
|
administrator: [
|
||||||
"executive": ["executive", "general", "past", "none", ""],
|
"administrator",
|
||||||
"general": ["general", "past", "none", ""],
|
"executive",
|
||||||
"past": ["past", "none", ""],
|
"general",
|
||||||
"sponsor": ["sponsor"], // Sponsor can only access sponsor-specific content
|
"honorary",
|
||||||
"none": ["none", ""],
|
"past",
|
||||||
"": [""]
|
"sponsor",
|
||||||
|
"none",
|
||||||
|
"",
|
||||||
|
],
|
||||||
|
executive: ["executive", "general", "honorary", "past", "none", ""],
|
||||||
|
general: ["general", "honorary", "past", "none", ""],
|
||||||
|
honorary: ["honorary", "none", ""],
|
||||||
|
past: ["past", "none", ""],
|
||||||
|
sponsor: ["sponsor"], // Sponsor can only access sponsor-specific content
|
||||||
|
none: ["none", ""],
|
||||||
|
"": [""],
|
||||||
};
|
};
|
||||||
|
|
||||||
return roleHierarchy[userRole]?.includes(requiredRole) || false;
|
return roleHierarchy[userRole]?.includes(requiredRole) || false;
|
||||||
|
|
Loading…
Reference in a new issue