added officer event_management boilerplate

This commit is contained in:
chark1es 2025-02-10 16:43:40 -08:00
parent 2a1614edbc
commit 0607b67156
3 changed files with 425 additions and 5 deletions

View file

@ -0,0 +1,404 @@
---
import { Icon } from "astro-icon/components";
import { Get } from "../pocketbase/Get";
import { Authentication } from "../pocketbase/Authentication";
// Get instances
const get = Get.getInstance();
const auth = Authentication.getInstance();
// Interface for Event type
interface Event {
id: string;
event_name: string;
event_description: string;
start_date: string;
location: string;
event_code: string;
}
interface ListResponse<T> {
page: number;
perPage: number;
totalItems: number;
totalPages: number;
items: T[];
}
// Initialize variables
let eventResponse: ListResponse<Event> = {
page: 1,
perPage: 5,
totalItems: 0,
totalPages: 0,
items: [],
};
let upcomingEvents: Event[] = [];
// Fetch events
try {
if (auth.isAuthenticated()) {
eventResponse = await get.getList<Event>("events", 1, 5, "", "-start_date");
upcomingEvents = eventResponse.items;
}
} catch (error) {
console.error("Failed to fetch events:", error);
}
const totalEvents = eventResponse.totalItems;
const totalPages = eventResponse.totalPages;
const currentPage = eventResponse.page;
// Add type declaration for window
declare global {
interface Window {
[key: string]: any;
openEditModal: (event: Event) => void;
}
}
---
<div id="eventManagementSection" class="dashboard-section hidden">
<div class="mb-6 flex justify-between items-center">
<div>
<h2 class="text-2xl font-bold">Event Management</h2>
<p class="opacity-70">Manage and create IEEE UCSD events</p>
</div>
<button class="btn btn-primary gap-2">
<Icon name="heroicons:plus" class="h-5 w-5" />
Add New Event
</button>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div
class="stats shadow-lg bg-base-100 rounded-2xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform"
>
<div class="stat">
<div class="stat-title font-medium opacity-80">Total Events</div>
<div class="stat-value text-primary" id="totalEvents">-</div>
<div class="stat-desc flex items-center gap-2 mt-1">
<div class="badge badge-primary badge-sm">All Time</div>
</div>
</div>
</div>
<div
class="stats shadow-lg bg-base-100 rounded-2xl border border-base-200 hover:border-secondary transition-all duration-300 hover:-translate-y-1 transform"
>
<div class="stat">
<div class="stat-title font-medium opacity-80">Showing</div>
<div class="stat-value text-secondary" id="showingEvents">-</div>
<div class="stat-desc flex items-center gap-2 mt-1">
<div class="badge badge-secondary badge-sm" id="totalEventsLabel">
of - Events
</div>
</div>
</div>
</div>
<div
class="stats shadow-lg bg-base-100 rounded-2xl border border-base-200 hover:border-accent transition-all duration-300 hover:-translate-y-1 transform"
>
<div class="stat">
<div class="stat-title font-medium opacity-80">Pages</div>
<div class="stat-value text-accent" id="currentPage">-</div>
<div class="stat-desc flex items-center gap-2 mt-1">
<div class="badge badge-accent badge-sm" id="totalPagesLabel">
of -
</div>
</div>
</div>
</div>
</div>
<!-- Events List -->
<div
class="card bg-base-100 shadow-lg border border-base-200 hover:border-primary transition-all duration-300"
>
<div class="card-body">
<h3 class="card-title text-xl font-bold flex items-center gap-3">
<div class="badge badge-primary p-3">
<Icon name="heroicons:calendar" class="h-5 w-5" />
</div>
Events List
</h3>
<div class="divider"></div>
<!-- Event Items -->
<div class="space-y-4" id="eventsList">
<div class="text-center py-8 text-base-content/70">
<Icon
name="heroicons:calendar"
class="h-12 w-12 mx-auto mb-4 opacity-50"
/>
<p>Loading events...</p>
</div>
</div>
<!-- Load More Button -->
<div class="flex justify-center mt-6 hidden" id="loadMoreContainer">
<button class="btn btn-outline btn-primary gap-2" id="loadMoreButton">
<Icon name="heroicons:arrow-down" class="h-5 w-5" />
Load More Events
</button>
</div>
</div>
</div>
</div>
<!-- Edit Event Modal -->
<dialog id="editEventModal" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">Edit Event</h3>
<form id="editEventForm" class="space-y-4">
<input type="hidden" id="editEventId" />
<div class="form-control">
<label class="label">
<span class="label-text">Event Name</span>
</label>
<input
type="text"
id="editEventName"
class="input input-bordered"
required
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Description</span>
</label>
<textarea
id="editEventDescription"
class="textarea textarea-bordered"
rows="3"></textarea>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Location</span>
</label>
<input
type="text"
id="editEventLocation"
class="input input-bordered"
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Start Date</span>
</label>
<input
type="datetime-local"
id="editEventStartDate"
class="input input-bordered"
required
/>
</div>
<div class="modal-action">
<button type="submit" class="btn btn-primary">Save Changes</button>
<button type="button" class="btn" onclick="editEventModal.close()"
>Cancel</button
>
</div>
</form>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
<script>
import { Get } from "../pocketbase/Get";
import { Authentication } from "../pocketbase/Authentication";
const get = Get.getInstance();
const auth = Authentication.getInstance();
let currentPage = 1;
let totalPages = 0;
// Make openEditModal available globally
window.openEditModal = function (event: any) {
const modal = document.getElementById(
"editEventModal",
) as HTMLDialogElement;
const form = document.getElementById("editEventForm") as HTMLFormElement;
const idInput = document.getElementById("editEventId") as HTMLInputElement;
const nameInput = document.getElementById(
"editEventName",
) as HTMLInputElement;
const descInput = document.getElementById(
"editEventDescription",
) as HTMLTextAreaElement;
const locationInput = document.getElementById(
"editEventLocation",
) as HTMLInputElement;
const startDateInput = document.getElementById(
"editEventStartDate",
) as HTMLInputElement;
idInput.value = event.id;
nameInput.value = event.event_name;
descInput.value = event.event_description || "";
locationInput.value = event.location || "";
startDateInput.value = new Date(event.start_date)
.toISOString()
.slice(0, 16);
modal.showModal();
};
async function fetchEvents() {
try {
const eventsList = document.getElementById("eventsList");
if (!eventsList) return;
if (!auth.isAuthenticated()) {
eventsList.innerHTML = `
<div class="text-center py-8 text-base-content/70">
<Icon name="heroicons:calendar" class="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>Please log in to view events</p>
</div>
`;
return;
}
const response = await get.getList(
"events",
currentPage,
5,
"",
"-start_date",
);
// Update stats
const totalEventsEl = document.getElementById("totalEvents");
const showingEventsEl = document.getElementById("showingEvents");
const totalEventsLabelEl = document.getElementById("totalEventsLabel");
const currentPageEl = document.getElementById("currentPage");
const totalPagesLabelEl = document.getElementById("totalPagesLabel");
if (totalEventsEl)
totalEventsEl.textContent = response.totalItems.toString();
if (showingEventsEl)
showingEventsEl.textContent = response.items.length.toString();
if (totalEventsLabelEl)
totalEventsLabelEl.textContent = `of ${response.totalItems} Events`;
if (currentPageEl) currentPageEl.textContent = response.page.toString();
if (totalPagesLabelEl)
totalPagesLabelEl.textContent = `of ${response.totalPages}`;
// Update events list
if (response.items.length === 0) {
eventsList.innerHTML = `
<div class="text-center py-8 text-base-content/70">
<Icon name="heroicons:calendar" class="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>No events found</p>
</div>
`;
} else {
eventsList.innerHTML = response.items
.map((event, index) => {
const dateStr = new Date(event.start_date).toLocaleDateString(
"en-US",
{
month: "long",
day: "numeric",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
},
);
const locationStr = event.location ? `${event.location}` : "";
const codeStr = event.event_code ? `${event.event_code}` : "";
const detailsStr = [locationStr, codeStr]
.filter(Boolean)
.join(" | code: ");
const eventJson = JSON.stringify(event)
.replace(/'/g, "\\'")
.replace(/"/g, '\\"');
return `
<div class="flex items-center justify-between p-4 bg-base-200 rounded-xl hover:bg-base-300 transition-all duration-300">
<div class="flex items-center gap-4">
<div class="badge badge-lg p-3 badge-primary">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z" />
</svg>
</div>
<div>
<h4 class="font-semibold">${event.event_name}</h4>
<p class="text-sm opacity-70">${dateStr}${detailsStr ? ` • ${detailsStr}` : ""}</p>
</div>
</div>
<div class="flex gap-2">
<button class="btn btn-ghost btn-sm" onclick='window.openEditModal(JSON.parse("${eventJson}"))'>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>
</button>
<button class="btn btn-ghost btn-sm text-error">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
</button>
</div>
</div>
`;
})
.join("");
}
// Update load more button
const loadMoreContainer = document.getElementById("loadMoreContainer");
if (loadMoreContainer) {
if (response.page < response.totalPages) {
loadMoreContainer.classList.remove("hidden");
} else {
loadMoreContainer.classList.add("hidden");
}
}
totalPages = response.totalPages;
} catch (error) {
const eventsList = document.getElementById("eventsList");
if (eventsList) {
eventsList.innerHTML = `
<div class="text-center py-8 text-base-content/70">
<Icon name="heroicons:calendar" class="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>Error loading events: ${error instanceof Error ? error.message : "Unknown error"}</p>
</div>
`;
}
}
}
// Load more events when button is clicked
const loadMoreButton = document.getElementById("loadMoreButton");
if (loadMoreButton) {
loadMoreButton.addEventListener("click", async () => {
if (currentPage < totalPages) {
currentPage++;
await fetchEvents();
}
});
}
// Handle edit form submission
const editForm = document.getElementById("editEventForm") as HTMLFormElement;
if (editForm) {
editForm.addEventListener("submit", async (e) => {
e.preventDefault();
const modal = document.getElementById(
"editEventModal",
) as HTMLDialogElement;
// TODO: Implement save changes functionality
modal.close();
await fetchEvents(); // Refresh the list
});
}
// Initial fetch
fetchEvents();
</script>

View file

@ -1,4 +1,5 @@
officerRoles:
officer:
roles:
- "IEEE Officer"
- "IEEE Executive"
- "IEEE Administrator"

View file

@ -5,7 +5,7 @@ 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";
const title = "Dashboard";
---
@ -61,6 +61,7 @@ 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 -->
<li class="menu-title font-medium opacity-70">
<span>Main Menu</span>
</li>
@ -94,6 +95,19 @@ const title = "Dashboard";
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>
</li>
<li class="menu-title mt-6 font-medium opacity-70">
<span>Account</span>
@ -213,6 +227,7 @@ const title = "Dashboard";
<EventsSection />
<ReimbursementSection />
<SettingsSection />
<Officer_EventManagement />
</div>
</div>
</main>