new sections

This commit is contained in:
chark1es 2025-02-05 14:05:33 -08:00
parent 18fbf786a7
commit 6cbe277c8c
3 changed files with 338 additions and 11 deletions

View file

@ -1,13 +1,13 @@
---
import Layout from "../layouts/Layout.astro";
import UserProfile from "../components/auth/UserProfile.astro";
import DefaultProfileView from "../components/profile/DefaultProfileView.astro";
import OfficerProfileView from "../components/profile/OfficerView.astro";
import UserSettings from "../components/profile/UserSettings.astro";
import FileViewerModal from "../components/modals/FileViewerModal";
import Layout from "../../layouts/Layout.astro";
import UserProfile from "../../components/auth/UserProfile.astro";
import DefaultProfileView from "../../components/profile/DefaultProfileView.astro";
import OfficerProfileView from "../../components/profile/OfficerView.astro";
import UserSettings from "../../components/profile/UserSettings.astro";
import FileViewerModal from "../../components/modals/FileViewerModal";
import yaml from "js-yaml";
import profileConfig from "../config/profileConfig.yaml?raw";
import textConfig from "../config/text.yml?raw";
import profileConfig from "../../config/profileConfig.yaml?raw";
import textConfig from "../../config/text.yml?raw";
const title = "User Profile";
const config = yaml.load(profileConfig) as any;
@ -202,10 +202,10 @@ const text = yaml.load(textConfig) as any;
</Layout>
<script>
import { Authentication } from "../components/pocketbase/Authentication";
import { Authentication } from "../../components/pocketbase/Authentication";
import yaml from "js-yaml";
import profileConfig from "../config/profileConfig.yaml?raw";
import textConfig from "../config/text.yml?raw";
import profileConfig from "../../config/profileConfig.yaml?raw";
import textConfig from "../../config/text.yml?raw";
const config = yaml.load(profileConfig) as any;
const text = yaml.load(textConfig) as any;

View file

@ -0,0 +1,327 @@
---
import Layout from "../../layouts/Layout.astro";
import UserProfile from "../../components/auth/UserProfile.astro";
import yaml from "js-yaml";
import profileConfig from "../../config/profileConfig.yaml?raw";
import textConfig from "../../config/text.yml?raw";
const title = "Officer Dashboard";
const config = yaml.load(profileConfig) as any;
const text = yaml.load(textConfig) as any;
---
<Layout {title}>
<main class="min-h-screen bg-base-100/50 rounded-[1.5rem]">
<div class="container mx-auto px-6 py-6 mt-10">
<!-- Header Section with Breadcrumbs -->
<div class="mb-8">
<h1 class="text-4xl font-bold">Officer Dashboard</h1>
<p class="text-base-content/70 mt-2">
Manage IEEE UCSD membership and view organization statistics
</p>
</div>
<!-- Loading State -->
<div id="pageLoadingState" class="w-full">
<div class="flex flex-col items-center justify-center p-8">
<div class="loading loading-spinner loading-lg"></div>
<p class="mt-4 text-base-content/70">Loading dashboard...</p>
</div>
</div>
<!-- Error State -->
<div id="pageErrorState" class="hidden w-full">
<div class="alert alert-error">
<div class="flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
clip-rule="evenodd"></path>
</svg>
<span>Access Denied: Officer permissions required</span>
</div>
</div>
</div>
<!-- Main Content Grid -->
<div
id="mainContent"
class="hidden grid grid-cols-1 xl:grid-cols-12 gap-8"
>
<!-- Left Column - User Info -->
<div class="xl:col-span-4 2xl:col-span-3">
<div class="sticky top-[100px]">
<UserProfile />
</div>
</div>
<!-- Right Column - Content -->
<div class="xl:col-span-8 2xl:col-span-9">
<!-- Stats Overview -->
<div class="stats shadow w-full mb-8 bg-base-100">
<div class="stat">
<div class="stat-figure text-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
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>
</div>
<div class="stat-title">Total Members</div>
<div class="stat-value text-primary" id="totalMembersValue">
-
</div>
<div class="stat-desc" id="totalMembersChange">Loading...</div>
</div>
<div class="stat">
<div class="stat-figure text-secondary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z"
></path>
</svg>
</div>
<div class="stat-title">New Members</div>
<div class="stat-value text-secondary" id="newMembersValue">
-
</div>
<div class="stat-desc" id="newMembersChange">Last 30 days</div>
</div>
<div class="stat">
<div class="stat-figure text-accent">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"
></path>
</svg>
</div>
<div class="stat-title">Active Officers</div>
<div class="stat-value text-accent" id="activeOfficersValue">
-
</div>
<div class="stat-desc" id="activeOfficersChange">
Currently active
</div>
</div>
</div>
<!-- Member Activity Chart -->
<div class="card bg-base-100 shadow-xl mb-8">
<div class="card-body">
<h2 class="card-title">Member Activity Overview</h2>
<div
class="w-full h-[300px] bg-base-200 rounded-box"
id="activityChart"
>
<div class="flex items-center justify-center h-full">
<span class="loading loading-spinner loading-lg"></span>
</div>
</div>
</div>
</div>
<!-- Recent Members Table -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title mb-4">Recent Members</h2>
<div class="overflow-x-auto">
<table class="table table-zebra">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Join Date</th>
<th>Status</th>
</tr>
</thead>
<tbody id="recentMembersTable">
<tr>
<td colspan="4" class="text-center">
<span class="loading loading-spinner loading-sm"></span>
Loading members...
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</Layout>
<script>
import { Authentication } from "../../components/pocketbase/Authentication";
import { Get } from "../../components/pocketbase/Get";
import { SendLog } from "../../components/pocketbase/SendLog";
// Initialize services
const auth = Authentication.getInstance();
const get = Get.getInstance();
const logger = SendLog.getInstance();
// Get DOM elements
const pageLoadingState = document.getElementById("pageLoadingState");
const pageErrorState = document.getElementById("pageErrorState");
const mainContent = document.getElementById("mainContent");
const totalMembersValue = document.getElementById("totalMembersValue");
const totalMembersChange = document.getElementById("totalMembersChange");
const newMembersValue = document.getElementById("newMembersValue");
const activeOfficersValue = document.getElementById("activeOfficersValue");
const recentMembersTable = document.getElementById("recentMembersTable");
// Function to format date
function formatDate(dateStr: string): string {
return new Date(dateStr).toLocaleDateString();
}
// Function to check if user is an officer
const isOfficer = (user: any) => {
if (!user) return false;
const memberType = user.member_type || "";
return ["IEEE Officer", "IEEE Executive", "IEEE Administrator"].includes(
memberType,
);
};
// Function to update stats
async function updateStats() {
try {
// Get all users
const allUsers = await get.getAll("users");
const totalMembers = allUsers.length;
// Calculate new members in last 30 days
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const newMembers = allUsers.filter(
(user: any) => new Date(user.created) > thirtyDaysAgo,
).length;
// Calculate active officers
const activeOfficers = allUsers.filter((user: any) =>
["IEEE Officer", "IEEE Executive", "IEEE Administrator"].includes(
user.member_type,
),
).length;
// Update UI
if (totalMembersValue)
totalMembersValue.textContent = totalMembers.toString();
if (totalMembersChange)
totalMembersChange.textContent = `Total registered users`;
if (newMembersValue) newMembersValue.textContent = newMembers.toString();
if (activeOfficersValue)
activeOfficersValue.textContent = activeOfficers.toString();
// Update recent members table
if (recentMembersTable) {
const recentUsers = allUsers
.sort(
(a: any, b: any) =>
new Date(b.created).getTime() - new Date(a.created).getTime(),
)
.slice(0, 5);
recentMembersTable.innerHTML = recentUsers
.map(
(user: any) => `
<tr>
<td>${user.name || "Unnamed User"}</td>
<td>${user.email || "No email"}</td>
<td>${formatDate(user.created)}</td>
<td>
<div class="badge badge-${
user.member_type === "IEEE Officer"
? "info"
: user.member_type === "IEEE Executive"
? "secondary"
: user.member_type === "IEEE Administrator"
? "error"
: "success"
} badge-sm">
${user.member_type || "Member"}
</div>
</td>
</tr>
`,
)
.join("");
}
} catch (error) {
console.error("Failed to update stats:", error);
await logger.send(
"error",
"officer dashboard",
`Failed to update stats: ${error instanceof Error ? error.message : "Unknown error"}`,
);
}
}
// Initialize page
const initializePage = async () => {
try {
// Show loading state
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
if (pageErrorState) pageErrorState.classList.add("hidden");
if (mainContent) mainContent.classList.add("hidden");
// Check auth state and permissions
const user = auth.getCurrentUser();
if (!user || !isOfficer(user)) {
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (pageErrorState) pageErrorState.classList.remove("hidden");
return;
}
// Update stats
await updateStats();
// Hide loading state and show content
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (mainContent) mainContent.classList.remove("hidden");
} catch (error) {
console.error("Failed to initialize page:", error);
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (pageErrorState) pageErrorState.classList.remove("hidden");
if (mainContent) mainContent.classList.add("hidden");
}
};
// Check on load and auth changes
initializePage();
auth.onAuthStateChange(() => {
initializePage();
});
</script>
<style>
.hidden {
display: none !important;
}
</style>

View file