add tab management

This commit is contained in:
chark1es 2025-02-18 18:30:09 -08:00
parent 00ab7aa1a3
commit b127429c39
10 changed files with 669 additions and 190 deletions

View file

@ -6,7 +6,7 @@ import EventCheckIn from "./EventsSection/EventCheckIn";
import EventLoad from "./EventsSection/EventLoad";
---
<div id="eventsSection" class="dashboard-section hidden">
<div id="" class="">
<div class="mb-4 sm:mb-6 px-4 sm:px-6">
<h2 class="text-xl sm:text-2xl font-bold">Events</h2>
<p class="opacity-70 text-sm sm:text-base">

View file

@ -70,7 +70,7 @@ const totalPages = eventResponse.totalPages;
const currentPage = eventResponse.page;
---
<div id="eventManagementSection" class="dashboard-section hidden">
<div id=" class=" ">
<div
class="mb-4 md:mb-6 flex flex-col md:flex-row md:justify-between md:items-center gap-2"
>

View file

@ -4,7 +4,7 @@ import ShowProfileLogs from "./ProfileSection/ShowProfileLogs";
import { Stats } from "./ProfileSection/Stats";
---
<div id="profileSection" class="dashboard-section">
<div id="" class="">
<div class="mb-6">
<h2 class="text-2xl font-bold">Dashboard Overview</h2>
<p class="opacity-70">Welcome to your IEEE UCSD dashboard</p>

View file

@ -2,7 +2,7 @@
import { Icon } from "astro-icon/components";
---
<div id="reimbursementSection" class="dashboard-section hidden">
<div id="" class="">
<div class="mb-6">
<h2 class="text-2xl font-bold">Reimbursement</h2>
<p class="opacity-70">Manage your reimbursement requests</p>

View file

@ -2,7 +2,7 @@
import { Icon } from "astro-icon/components";
---
<div id="settingsSection" class="dashboard-section hidden">
<div id="" class="">
<div class="mb-6">
<h2 class="text-2xl font-bold">Settings</h2>
<p class="opacity-70">Manage your account settings</p>

View file

@ -0,0 +1,99 @@
---
// Sponsor Analytics Component
---
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title text-2xl mb-4">Analytics Dashboard</h2>
<!-- Metrics Overview -->
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-4 mb-6">
<div class="stat bg-base-200 rounded-box p-4">
<div class="stat-title">Resume Downloads</div>
<div class="stat-value text-primary">89</div>
<div class="stat-desc">↗︎ 14 (30 days)</div>
</div>
<div class="stat bg-base-200 rounded-box p-4">
<div class="stat-title">Event Attendance</div>
<div class="stat-value">45</div>
<div class="stat-desc">↘︎ 5 (30 days)</div>
</div>
<div class="stat bg-base-200 rounded-box p-4">
<div class="stat-title">Student Interactions</div>
<div class="stat-value text-secondary">124</div>
<div class="stat-desc">↗︎ 32 (30 days)</div>
</div>
<div class="stat bg-base-200 rounded-box p-4">
<div class="stat-title">Workshop Engagement</div>
<div class="stat-value">92%</div>
<div class="stat-desc">↗︎ 8% (30 days)</div>
</div>
</div>
<!-- Detailed Analytics -->
<div class="grid gap-6 md:grid-cols-2">
<!-- Event Performance -->
<div class="card bg-base-200">
<div class="card-body">
<h3 class="card-title text-lg">Event Performance</h3>
<div class="overflow-x-auto">
<table class="table table-sm">
<thead>
<tr>
<th>Event</th>
<th>Attendance</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
<tr>
<td>Tech Talk</td>
<td>32</td>
<td>4.8/5</td>
</tr>
<tr>
<td>Workshop</td>
<td>28</td>
<td>4.6/5</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Resume Analytics -->
<div class="card bg-base-200">
<div class="card-body">
<h3 class="card-title text-lg">Resume Analytics</h3>
<div class="overflow-x-auto">
<table class="table table-sm">
<thead>
<tr>
<th>Major</th>
<th>Downloads</th>
<th>Trend</th>
</tr>
</thead>
<tbody>
<tr>
<td>Computer Science</td>
<td>45</td>
<td>↗︎</td>
</tr>
<tr>
<td>Electrical Engineering</td>
<td>32</td>
<td>↗︎</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,78 @@
---
// Sponsor Dashboard Component
---
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title text-2xl mb-4">Sponsor Dashboard</h2>
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<!-- Sponsorship Status -->
<div class="card bg-base-200">
<div class="card-body">
<h3 class="card-title text-lg">Sponsorship Status</h3>
<p class="text-primary font-semibold">Active</p>
<p class="text-sm opacity-70">Valid until: Dec 31, 2024</p>
</div>
</div>
<!-- Partnership Level -->
<div class="card bg-base-200">
<div class="card-body">
<h3 class="card-title text-lg">Partnership Level</h3>
<p class="text-primary font-semibold">Platinum</p>
<p class="text-sm opacity-70">All benefits included</p>
</div>
</div>
<!-- Quick Actions -->
<div class="card bg-base-200">
<div class="card-body">
<h3 class="card-title text-lg">Quick Actions</h3>
<div class="flex gap-2 mt-2">
<button class="btn btn-primary btn-sm"
>Contact Us</button
>
<button class="btn btn-outline btn-sm"
>View Contract</button
>
</div>
</div>
</div>
</div>
<!-- Recent Activity -->
<div class="mt-6">
<h3 class="text-xl font-semibold mb-4">Recent Activity</h3>
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Activity</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>2024-01-15</td>
<td>Resume Book Access</td>
<td
><span class="badge badge-success"
>Completed</span
></td
>
</tr>
<tr>
<td>2024-01-10</td>
<td>Workshop Scheduling</td>
<td
><span class="badge badge-warning">Pending</span
></td
>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>

View file

@ -1,29 +1,91 @@
member:
tabs:
- "Dashboard"
- "Events"
- "Reimbursements"
sections:
# Base Menu (accessible to all except sponsors)
profile:
title: "Dashboard"
icon: "heroicons:home"
role: "none"
component: "ProfileSection"
class: "text-primary hover:text-primary-focus"
officer:
tabs:
- "Event Management"
include:
- "member"
roles:
- "IEEE Officer"
events:
title: "Events"
icon: "heroicons:calendar"
role: "none"
component: "EventsSection"
class: "text-secondary hover:text-secondary-focus"
executive:
include:
- "officer"
roles:
- "IEEE Executive"
reimbursement:
title: "Reimbursement"
icon: "heroicons:credit-card"
role: "none"
component: "ReimbursementSection"
class: "text-accent hover:text-accent-focus"
administrator:
include:
- "executive"
roles:
- "IEEE Administrator"
# Officer Menu
eventManagement:
title: "Event Management"
icon: "heroicons:cog-6-tooth"
role: "general"
component: "Officer_EventManagement"
class: "text-info hover:text-info-focus"
sponsor:
roles:
- "IEEE Sponsor"
# Sponsor Menu
sponsorDashboard:
title: "Sponsor Dashboard"
icon: "heroicons:briefcase"
role: "sponsor"
component: "SponsorDashboard"
class: "text-warning hover:text-warning-focus"
sponsorAnalytics:
title: "Analytics"
icon: "heroicons:chart-bar"
role: "sponsor"
component: "SponsorAnalytics"
class: "text-warning hover:text-warning-focus"
# Settings (accessible to all except sponsors)
settings:
title: "Settings"
icon: "heroicons:cog-6-tooth"
role: "none"
component: "SettingsSection"
class: "text-neutral hover:text-neutral-focus"
logout:
title: "Logout"
icon: "heroicons:arrow-left-on-rectangle"
role: "none"
class: "text-error hover:text-error-focus"
# Menu Categories
categories:
main:
title: "Main Menu"
sections: ["profile", "events", "reimbursement"]
role: "none"
officer:
title: "Officer Menu"
sections: ["eventManagement"]
role: "general"
executive:
title: "Executive Menu"
sections: []
role: "executive"
admin:
title: "Admin Menu"
sections: []
role: "admin"
sponsor:
title: "Sponsor Portal"
sections: ["sponsorDashboard", "sponsorAnalytics"]
role: "sponsor"
account:
title: "Account"
sections: ["settings", "logout"]
role: "none"

View file

@ -1,12 +1,32 @@
---
import yaml from "js-yaml";
import { Icon } from "astro-icon/components";
import ProfileSection from "../components/dashboard/ProfileSection.astro";
import EventsSection from "../components/dashboard/EventsSection.astro";
import ReimbursementSection from "../components/dashboard/ReimbursementSection.astro";
import SettingsSection from "../components/dashboard/SettingsSection.astro";
import Officer_EventManagement from "../components/dashboard/Officer_EventManagement.astro";
import { hasAccess, type OfficerStatus } from "../utils/roleAccess";
import fs from "node:fs";
import path from "node:path";
const title = "Dashboard";
// Load and parse dashboard config
const configPath = path.join(process.cwd(), "src", "config", "dashboard.yaml");
const dashboardConfig = yaml.load(fs.readFileSync(configPath, "utf8")) as any;
// Dynamically import all dashboard components
const components = Object.fromEntries(
await Promise.all(
Object.values(dashboardConfig.sections)
.filter((section: any) => section.component) // Only process sections with components
.map(async (section: any) => {
const component = await import(
`../components/dashboard/${section.component}.astro`
);
console.log(`Loaded component: ${section.component}`); // Debug log
return [section.component, component.default];
})
)
);
console.log("Available components:", Object.keys(components)); // Debug log
---
<!doctype html>
@ -38,9 +58,57 @@ const title = "Dashboard";
<!-- User Profile -->
<div class="p-6 border-b border-base-200">
<!-- Loading State -->
<div
id="userProfileSkeleton"
class="flex items-center gap-4"
>
<div class="avatar flex items-center justify-center">
<div
class="w-12 h-12 rounded-xl bg-base-300 animate-pulse"
>
</div>
</div>
<div class="flex-1">
<div
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>
<!-- Signed Out State -->
<div
id="userProfileSignedOut"
class="flex items-center gap-4 hidden"
>
<div class="avatar flex items-center justify-center">
<div
class="w-12 h-12 rounded-xl bg-base-300 text-base-content/30 flex items-center justify-center"
>
<Icon name="heroicons:user" class="h-6 w-6" />
</div>
</div>
<div>
<h3
class="font-medium text-lg text-base-content/70"
>
Signed Out
</h3>
<div class="badge badge-outline mt-1 opacity-50">
Guest
</div>
</div>
</div>
<!-- Actual Profile -->
<div
id="userProfileSummary"
class="flex items-center gap-4 hidden"
>
<div class="avatar flex items-center justify-center">
<div
@ -69,87 +137,92 @@ const title = "Dashboard";
<!-- Navigation -->
<nav class="flex-1 overflow-y-auto scrollbar-hide py-6">
<ul class="menu gap-2 px-4 text-base-content/80">
<!-- All Members -->
<!-- Loading Skeleton -->
<div id="menuLoadingSkeleton">
{
[1, 2, 3].map((group) => (
<>
<li class="menu-title font-medium opacity-70">
<span>Main Menu</span>
<div class="h-4 w-24 bg-base-300 animate-pulse rounded" />
</li>
{[1, 2, 3].map((item) => (
<li>
<button
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5 active:bg-base-200 active:font-medium active:text-primary"
data-section="profile"
>
<Icon
name="heroicons:home"
class="h-5 w-5 group-[.active]:text-primary"
/>
Dashboard
</button>
</li>
<li>
<button
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
data-section="events"
>
<Icon
name="heroicons:calendar"
class="h-5 w-5"
/>
Events
</button>
</li>
<li>
<button
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
data-section="reimbursement"
>
<Icon
name="heroicons:credit-card"
class="h-5 w-5"
/>
Reimbursement
</button>
</li>
<!-- Officers Only -->
<li>
<button
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5 active:bg-base-200 active:font-medium active:text-primary"
data-section="eventManagement"
>
<Icon
name="heroicons:home"
class="h-5 w-5 group-[.active]:text-primary"
/>
Event Management
</button>
<div class="flex items-center gap-4 py-2">
<div class="h-5 w-5 bg-base-300 animate-pulse rounded" />
<div class="h-4 w-32 bg-base-300 animate-pulse rounded" />
</div>
</li>
))}
</>
))
}
</div>
<li class="menu-title mt-6 font-medium opacity-70">
<span>Account</span>
<!-- Actual Menu -->
<div id="actualMenu" class="hidden">
{
Object.entries(dashboardConfig.categories).map(
([categoryKey, category]: [
string,
any,
]) => (
<>
<li
class={`menu-title font-medium opacity-70 ${
category.role &&
category.role !== "none"
? "hidden"
: ""
}`}
data-role-required={
category.role || "none"
}
>
<span>{category.title}</span>
</li>
<li>
{category.sections.map(
(sectionKey: string) => {
const section =
dashboardConfig
.sections[
sectionKey
];
return (
<li
class={
section.role &&
section.role !==
"none"
? "hidden"
: ""
}
data-role-required={
section.role
}
>
<button
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
data-section="settings"
class={`dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5 ${section.class || ""}`}
data-section={
sectionKey
}
>
<Icon
name="heroicons:cog-6-tooth"
name={
section.icon
}
class="h-5 w-5"
/>
Settings
</button>
</li>
<li>
<button
id="logoutButton"
class="gap-4 text-error hover:bg-error/10 transition-all duration-200 outline-none focus:outline-none"
>
<Icon
name="heroicons:arrow-right-on-rectangle"
class="h-5 w-5"
/>
Logout
{section.title}
</button>
</li>
);
}
)}
</>
)
)
}
</div>
</ul>
</nav>
</aside>
@ -257,12 +330,32 @@ const title = "Dashboard";
</div>
<!-- Main Content -->
<div id="mainContent" class="hidden space-y-4 sm:space-y-6">
<ProfileSection />
<EventsSection />
<ReimbursementSection />
<SettingsSection />
<Officer_EventManagement />
<div id="mainContent" class="space-y-4 sm:space-y-6">
{
Object.entries(dashboardConfig.sections).map(
([sectionKey, section]: [string, any]) => {
// Skip if no component is defined
if (!section.component) return null;
const Component =
components[section.component];
return (
<div
id={`${sectionKey}Section`}
class={`dashboard-section hidden ${
section.role &&
section.role !== "none"
? "role-restricted"
: ""
}`}
data-role-required={section.role}
>
<Component />
</div>
);
}
)
}
</div>
</div>
</main>
@ -274,6 +367,7 @@ const title = "Dashboard";
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";
const auth = Authentication.getInstance();
const get = Get.getInstance();
@ -293,29 +387,132 @@ const title = "Dashboard";
const userName = document.getElementById("userName");
const userRole = document.getElementById("userRole");
// Display user profile information
const updateUserProfile = async (user: any) => {
// Function to update section visibility based on role
const updateSectionVisibility = (officerStatus: OfficerStatus) => {
// Special handling for sponsor role
if (officerStatus === "sponsor") {
// Hide all sections first
document
.querySelectorAll("[data-role-required]")
.forEach((element) => {
element.classList.add("hidden");
});
// Only show sponsor sections
document
.querySelectorAll('[data-role-required="sponsor"]')
.forEach((element) => {
element.classList.remove("hidden");
});
return;
}
// For non-sponsor roles, handle normally
document.querySelectorAll("[data-role-required]").forEach((element) => {
const requiredRole = element.getAttribute(
"data-role-required"
) as OfficerStatus;
// Skip elements that don't have a role requirement
if (!requiredRole || requiredRole === "none") {
element.classList.remove("hidden");
return;
}
// Check if user has permission for this role
const hasPermission = hasAccess(officerStatus, requiredRole);
// Only show elements if user has permission
element.classList.toggle("hidden", !hasPermission);
});
};
// Handle navigation
const handleNavigation = () => {
const navButtons = document.querySelectorAll(".dashboard-nav-btn");
const sections = document.querySelectorAll(".dashboard-section");
const mainContentDiv = document.getElementById("mainContent");
// Ensure mainContent is visible
if (mainContentDiv) {
mainContentDiv.classList.remove("hidden");
}
navButtons.forEach((button) => {
button.addEventListener("click", () => {
const sectionKey = button.getAttribute("data-section");
// Handle logout button
if (sectionKey === "logout") {
auth.logout();
window.location.reload();
return;
}
// Remove active class from all buttons
navButtons.forEach((btn) => {
btn.classList.remove("active", "bg-base-200");
});
// Add active class to clicked button
button.classList.add("active", "bg-base-200");
// Hide all sections
sections.forEach((section) => {
section.classList.add("hidden");
});
// Show selected section
const sectionId = `${sectionKey}Section`;
const targetSection = document.getElementById(sectionId);
if (targetSection) {
targetSection.classList.remove("hidden");
console.log(`Showing section: ${sectionId}`); // Debug log
}
// Close mobile sidebar if needed
if (window.innerWidth < 1024 && sidebar) {
sidebar.classList.add("-translate-x-full");
document.body.classList.remove("overflow-hidden");
const overlay = document.getElementById("sidebarOverlay");
overlay?.remove();
}
});
});
};
// Display user profile information and handle role-based access
const updateUserProfile = async (user: { id: string }) => {
if (!user) return;
try {
// Get user information including member type
const extendedUser = await get.getOne("users", user.id, {
fields: ["id", "name", "member_type", "expand.member_type"],
fields: [
"id",
"name",
"member_type",
"officer_status",
"expand.member_type",
],
});
// Update display elements
const displayName = extendedUser.name || "Unknown User";
const displayRole = extendedUser.member_type || "Member";
const officerStatus = (extendedUser.officer_status ||
"") as OfficerStatus;
const initials = (extendedUser.name || "U")
.split(" ")
.map((n: string) => n[0])
.join("")
.toUpperCase();
// Update elements
// Update profile display
if (userName) userName.textContent = displayName;
if (userRole) userRole.textContent = displayRole;
if (userInitials) userInitials.textContent = initials;
// Update section visibility based on role
updateSectionVisibility(officerStatus);
} catch (error) {
console.error("Error fetching user profile:", error);
const fallbackValues = {
@ -324,15 +521,16 @@ const title = "Dashboard";
initials: "?",
};
// Update elements with fallback values
if (userName) userName.textContent = fallbackValues.name;
if (userRole) userRole.textContent = fallbackValues.role;
if (userInitials)
userInitials.textContent = fallbackValues.initials;
updateSectionVisibility("" as OfficerStatus);
}
};
// Mobile sidebar toggle with overlay
// Mobile sidebar toggle
const mobileSidebarToggle = document.getElementById("mobileSidebarToggle");
if (mobileSidebarToggle && sidebar) {
const toggleSidebar = () => {
@ -341,13 +539,11 @@ const title = "Dashboard";
if (isOpen) {
sidebar.classList.add("-translate-x-full");
document.body.classList.remove("overflow-hidden");
// Remove overlay if it exists
const overlay = document.getElementById("sidebarOverlay");
overlay?.remove();
} else {
sidebar.classList.remove("-translate-x-full");
document.body.classList.add("overflow-hidden");
// Add overlay
const overlay = document.createElement("div");
overlay.id = "sidebarOverlay";
overlay.className =
@ -360,51 +556,6 @@ const title = "Dashboard";
mobileSidebarToggle.addEventListener("click", toggleSidebar);
}
// Close sidebar on window resize if screen becomes larger
window.addEventListener("resize", () => {
if (window.innerWidth >= 1024) {
const overlay = document.getElementById("sidebarOverlay");
if (overlay) {
overlay.remove();
document.body.classList.remove("overflow-hidden");
}
if (sidebar) {
sidebar.classList.remove("-translate-x-full");
}
}
});
// Handle navigation
const handleNavigation = () => {
const navButtons = document.querySelectorAll(".dashboard-nav-btn");
const sections = document.querySelectorAll(".dashboard-section");
navButtons.forEach((button) => {
button.addEventListener("click", () => {
// Remove active class from all buttons
navButtons.forEach((btn) =>
btn.classList.remove("active", "bg-base-200")
);
// Add active class to clicked button
button.classList.add("active", "bg-base-200");
// Hide all sections
sections.forEach((section) => section.classList.add("hidden"));
// Show selected section
const sectionId = `${button.getAttribute("data-section")}Section`;
document.getElementById(sectionId)?.classList.remove("hidden");
// Close sidebar and cleanup overlay on mobile
if (window.innerWidth < 1024 && sidebar) {
sidebar.classList.add("-translate-x-full");
document.body.classList.remove("overflow-hidden");
const overlay = document.getElementById("sidebarOverlay");
overlay?.remove();
}
});
});
};
// Initialize page
const initializePage = async () => {
try {
@ -412,23 +563,98 @@ const title = "Dashboard";
if (pageErrorState) pageErrorState.classList.add("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.add("hidden");
if (mainContent) mainContent.classList.add("hidden");
// Show loading states
const userProfileSkeleton = document.getElementById(
"userProfileSkeleton"
);
const userProfileSignedOut = document.getElementById(
"userProfileSignedOut"
);
const userProfileSummary =
document.getElementById("userProfileSummary");
const menuLoadingSkeleton = document.getElementById(
"menuLoadingSkeleton"
);
const actualMenu = document.getElementById("actualMenu");
if (userProfileSkeleton)
userProfileSkeleton.classList.remove("hidden");
if (userProfileSummary) userProfileSummary.classList.add("hidden");
if (userProfileSignedOut)
userProfileSignedOut.classList.add("hidden");
if (menuLoadingSkeleton)
menuLoadingSkeleton.classList.remove("hidden");
if (actualMenu) actualMenu.classList.add("hidden");
// Check authentication
if (!auth.isAuthenticated()) {
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.remove("hidden");
if (userProfileSkeleton)
userProfileSkeleton.classList.add("hidden");
if (userProfileSignedOut)
userProfileSignedOut.classList.remove("hidden");
return;
}
const user = auth.getCurrentUser();
await updateUserProfile(user);
// Show actual profile and hide skeleton
if (userProfileSkeleton)
userProfileSkeleton.classList.add("hidden");
if (userProfileSummary)
userProfileSummary.classList.remove("hidden");
// Hide all sections first
document
.querySelectorAll(".dashboard-section")
.forEach((section) => {
section.classList.add("hidden");
});
// Show appropriate default section based on role
const extendedUser = await get.getOne("users", user.id, {
fields: ["officer_status"],
});
const officerStatus = (extendedUser.officer_status ||
"") as OfficerStatus;
let defaultSection;
let defaultButton;
if (officerStatus === "sponsor") {
defaultSection = document.getElementById(
"sponsorDashboardSection"
);
defaultButton = document.querySelector(
'[data-section="sponsorDashboard"]'
);
} else {
defaultSection = document.getElementById("profileSection");
defaultButton = document.querySelector(
'[data-section="profile"]'
);
}
if (defaultSection) {
defaultSection.classList.remove("hidden");
}
if (defaultButton) {
defaultButton.classList.add("active", "bg-base-200");
}
// Initialize navigation
handleNavigation();
// Show main content
// Show actual menu and hide skeleton
if (menuLoadingSkeleton)
menuLoadingSkeleton.classList.add("hidden");
if (actualMenu) actualMenu.classList.remove("hidden");
// Show main content and hide loading
if (mainContent) mainContent.classList.remove("hidden");
if (pageLoadingState) pageLoadingState.classList.add("hidden");
} catch (error) {
@ -466,21 +692,19 @@ const title = "Dashboard";
// Handle responsive sidebar
if (sidebar) {
// Hide sidebar by default on mobile
if (window.innerWidth < 1024) {
sidebar.classList.add("-translate-x-full");
}
// Add transition class
sidebar.classList.add(
"transition-transform",
"duration-300",
"ease-in-out",
"lg:translate-x-0",
"fixed",
"lg:relative",
"h-full",
"z-50"
);
window.addEventListener("resize", () => {
if (window.innerWidth >= 1024) {
const overlay = document.getElementById("sidebarOverlay");
if (overlay) {
overlay.remove();
document.body.classList.remove("overflow-hidden");
}
sidebar.classList.remove("-translate-x-full");
}
});
}
</script>

16
src/utils/roleAccess.ts Normal file
View file

@ -0,0 +1,16 @@
export type OfficerStatus = "admin" | "executive" | "general" | "past" | "sponsor" | "none" | "";
type RoleHierarchy = Record<OfficerStatus, OfficerStatus[]>;
export function hasAccess(userRole: OfficerStatus, requiredRole: OfficerStatus): boolean {
const roleHierarchy: RoleHierarchy = {
"admin": ["admin", "sponsor", "executive", "general", "past", "none", ""],
"executive": ["executive", "general", "past", "none", ""],
"general": ["general", "past", "none", ""],
"past": ["past", "none", ""],
"sponsor": ["sponsor"], // Sponsor can only access sponsor-specific content
"none": ["none", ""],
"": [""]
};
return roleHierarchy[userRole]?.includes(requiredRole) || false;
}