added officer event_management boilerplate
This commit is contained in:
parent
2a1614edbc
commit
0607b67156
3 changed files with 425 additions and 5 deletions
404
src/components/dashboard/Officer_EventManagement.astro
Normal file
404
src/components/dashboard/Officer_EventManagement.astro
Normal 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>
|
|
@ -1,4 +1,5 @@
|
||||||
officerRoles:
|
officer:
|
||||||
- "IEEE Officer"
|
roles:
|
||||||
- "IEEE Executive"
|
- "IEEE Officer"
|
||||||
- "IEEE Administrator"
|
- "IEEE Executive"
|
||||||
|
- "IEEE Administrator"
|
||||||
|
|
|
@ -5,7 +5,7 @@ import ProfileSection from "../components/dashboard/ProfileSection.astro";
|
||||||
import EventsSection from "../components/dashboard/EventsSection.astro";
|
import EventsSection from "../components/dashboard/EventsSection.astro";
|
||||||
import ReimbursementSection from "../components/dashboard/ReimbursementSection.astro";
|
import ReimbursementSection from "../components/dashboard/ReimbursementSection.astro";
|
||||||
import SettingsSection from "../components/dashboard/SettingsSection.astro";
|
import SettingsSection from "../components/dashboard/SettingsSection.astro";
|
||||||
|
import Officer_EventManagement from "../components/dashboard/Officer_EventManagement.astro";
|
||||||
const title = "Dashboard";
|
const title = "Dashboard";
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@ const title = "Dashboard";
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="flex-1 overflow-y-auto scrollbar-hide py-6">
|
<nav class="flex-1 overflow-y-auto scrollbar-hide py-6">
|
||||||
<ul class="menu gap-2 px-4 text-base-content/80">
|
<ul class="menu gap-2 px-4 text-base-content/80">
|
||||||
|
<!-- All Members -->
|
||||||
<li class="menu-title font-medium opacity-70">
|
<li class="menu-title font-medium opacity-70">
|
||||||
<span>Main Menu</span>
|
<span>Main Menu</span>
|
||||||
</li>
|
</li>
|
||||||
|
@ -94,6 +95,19 @@ const title = "Dashboard";
|
||||||
Reimbursement
|
Reimbursement
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</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">
|
<li class="menu-title mt-6 font-medium opacity-70">
|
||||||
<span>Account</span>
|
<span>Account</span>
|
||||||
|
@ -213,6 +227,7 @@ const title = "Dashboard";
|
||||||
<EventsSection />
|
<EventsSection />
|
||||||
<ReimbursementSection />
|
<ReimbursementSection />
|
||||||
<SettingsSection />
|
<SettingsSection />
|
||||||
|
<Officer_EventManagement />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
Loading…
Reference in a new issue