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:
|
||||
roles:
|
||||
- "IEEE Officer"
|
||||
- "IEEE Executive"
|
||||
- "IEEE Administrator"
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue