separated the two sections for better viewing

This commit is contained in:
chark1es 2025-02-11 01:50:22 -08:00
parent 9189b03a87
commit b697f0e644

View file

@ -15,7 +15,9 @@ import { Icon } from "astro-icon/components";
<h3 class="card-title text-lg mb-4">Event Check-in</h3>
<div class="form-control w-full">
<label class="label">
<span class="label-text">Enter event code to check in</span>
<span class="label-text"
>Enter event code to check in</span
>
</label>
<div class="flex gap-2">
<input
@ -23,7 +25,9 @@ import { Icon } from "astro-icon/components";
placeholder="Enter code"
class="input input-bordered flex-1"
/>
<button class="btn btn-primary min-w-[90px]">Check In</button>
<button class="btn btn-primary min-w-[90px]"
>Check In</button
>
</div>
</div>
</div>
@ -36,7 +40,9 @@ import { Icon } from "astro-icon/components";
<form id="foodSelectionForm" class="space-y-4">
<div class="form-control">
<label class="label">
<span class="label-text">What food would you like?</span>
<span class="label-text"
>What food would you like?</span
>
<span class="label-text-alt text-error">*</span>
</label>
<input
@ -54,7 +60,9 @@ import { Icon } from "astro-icon/components";
</label>
</div>
<div class="modal-action">
<button type="submit" class="btn btn-primary">Submit</button>
<button type="submit" class="btn btn-primary"
>Submit</button
>
<button
type="button"
class="btn"
@ -74,12 +82,15 @@ import { Icon } from "astro-icon/components";
<h3 class="card-title text-lg mb-4">Event Registration</h3>
<div class="form-control w-full">
<label class="label">
<span class="label-text">Select an event to register</span>
<span class="label-text"
>Select an event to register</span
>
</label>
<div class="flex gap-2">
<select class="select select-bordered flex-1">
<option disabled selected>Pick an event</option>
<option>Technical Workshop - Web Development</option>
<option>Technical Workshop - Web Development</option
>
<option>Professional Development Workshop</option>
<option>Social Event - Game Night</option>
</select>
@ -90,16 +101,32 @@ import { Icon } from "astro-icon/components";
</div>
</div>
<!-- Upcoming Events -->
<div
class="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform"
class="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-6"
>
<div class="card-body">
<h3 class="card-title mb-4">Upcoming Events</h3>
<div
id="eventsContainer"
id="upcomingEventsContainer"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
>
<!-- Events will be dynamically inserted here -->
<!-- Upcoming events will be dynamically inserted here -->
</div>
</div>
</div>
<!-- Past Events -->
<div
class="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform"
>
<div class="card-body">
<h3 class="card-title mb-4">Past Events</h3>
<div
id="pastEventsContainer"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
>
<!-- Past events will be dynamically inserted here -->
</div>
</div>
</div>
@ -114,16 +141,19 @@ import { Icon } from "astro-icon/components";
// Toast management system
const createToast = (
message: string,
type: "success" | "error" | "warning" = "success",
type: "success" | "error" | "warning" = "success"
) => {
let toastContainer = document.querySelector(".toast-container");
if (!toastContainer) {
toastContainer = document.createElement("div");
toastContainer.className = "toast-container fixed bottom-4 right-4 z-50";
toastContainer.className =
"toast-container fixed bottom-4 right-4 z-50";
document.body.appendChild(toastContainer);
}
const existingToasts = document.querySelectorAll(".toast-container .toast");
const existingToasts = document.querySelectorAll(
".toast-container .toast"
);
if (existingToasts.length >= 2) {
const oldestToast = existingToasts[0];
oldestToast.classList.add("toast-exit");
@ -133,7 +163,9 @@ import { Icon } from "astro-icon/components";
// Update positions of existing toasts
existingToasts.forEach((t) => {
const toast = t as HTMLElement;
const currentIndex = parseInt(toast.getAttribute("data-index") || "0");
const currentIndex = parseInt(
toast.getAttribute("data-index") || "0"
);
toast.setAttribute("data-index", (currentIndex + 1).toString());
});
@ -232,14 +264,18 @@ import { Icon } from "astro-icon/components";
// Find the event with the given code
const event = await get.getFirst<Event>(
"events",
`event_code = "${eventCode}"`,
`event_code = "${eventCode}"`
);
if (!event) {
throw new Error("Invalid event code");
}
// Check if user is already checked in
if (event.attendees.some((entry) => entry.user_id === currentUser.id)) {
if (
event.attendees.some(
(entry) => entry.user_id === currentUser.id
)
) {
throw new Error("You have already checked in to this event");
}
@ -253,7 +289,7 @@ import { Icon } from "astro-icon/components";
if (event.has_food) {
currentCheckInEvent = event;
const modal = document.getElementById(
"foodSelectionModal",
"foodSelectionModal"
) as HTMLDialogElement;
modal.showModal();
} else {
@ -261,40 +297,49 @@ import { Icon } from "astro-icon/components";
await completeCheckIn(event, null);
}
} catch (error: any) {
createToast(error?.message || "Failed to check in to event", "error");
createToast(
error?.message || "Failed to check in to event",
"error"
);
}
}
// Add food selection form handler
const foodSelectionForm = document.getElementById(
"foodSelectionForm",
"foodSelectionForm"
) as HTMLFormElement;
if (foodSelectionForm) {
foodSelectionForm.addEventListener("submit", async (e) => {
e.preventDefault();
const modal = document.getElementById(
"foodSelectionModal",
"foodSelectionModal"
) as HTMLDialogElement;
const foodInput = document.getElementById(
"foodInput",
"foodInput"
) as HTMLInputElement;
try {
if (currentCheckInEvent) {
await completeCheckIn(currentCheckInEvent, foodInput.value.trim());
await completeCheckIn(
currentCheckInEvent,
foodInput.value.trim()
);
modal.close();
foodInput.value = ""; // Reset input
currentCheckInEvent = null;
}
} catch (error: any) {
createToast(error?.message || "Failed to check in to event", "error");
createToast(
error?.message || "Failed to check in to event",
"error"
);
}
});
}
async function completeCheckIn(
event: Event,
foodSelection: string | null,
foodSelection: string | null
): Promise<void> {
try {
const auth = Authentication.getInstance();
@ -317,7 +362,11 @@ import { Icon } from "astro-icon/components";
const existingAttendees = event.attendees || [];
// Check if user is already checked in
if (existingAttendees.some((entry) => entry.user_id === currentUser.id)) {
if (
existingAttendees.some(
(entry) => entry.user_id === currentUser.id
)
) {
throw new Error("You have already checked in to this event");
}
@ -329,7 +378,7 @@ import { Icon } from "astro-icon/components";
"events",
event.id,
"attendees",
updatedAttendees,
updatedAttendees
);
// If food selection was made, log it
@ -337,7 +386,7 @@ import { Icon } from "astro-icon/components";
await logger.send(
"update",
"event check-in",
`Food selection for ${event.event_name}: ${foodSelection}`,
`Food selection for ${event.event_name}: ${foodSelection}`
);
}
@ -348,14 +397,14 @@ import { Icon } from "astro-icon/components";
"users",
currentUser.id,
"points",
userPoints + event.points_to_reward,
userPoints + event.points_to_reward
);
// Log the points award
await logger.send(
"update",
"event check-in",
`Awarded ${event.points_to_reward} points for checking in to ${event.event_name}`,
`Awarded ${event.points_to_reward} points for checking in to ${event.event_name}`
);
}
@ -366,17 +415,20 @@ import { Icon } from "astro-icon/components";
? ` (+${event.points_to_reward} points!)`
: ""
}`,
"success",
"success"
);
// Log the check-in
await logger.send(
"check_in",
"events",
`User ${currentUser.name} (${currentUser.graduation_year}) checked in to event ${event.event_name}`,
`User ${currentUser.name} (${currentUser.graduation_year}) checked in to event ${event.event_name}`
);
} catch (error: any) {
createToast(error?.message || "Failed to check in to event", "error");
createToast(
error?.message || "Failed to check in to event",
"error"
);
}
}
@ -422,13 +474,19 @@ import { Icon } from "astro-icon/components";
async function loadEvents() {
try {
// Show skeletons first
const eventsContainer = document.getElementById("eventsContainer");
if (!eventsContainer) return;
const upcomingEventsContainer = document.getElementById(
"upcomingEventsContainer"
);
const pastEventsContainer = document.getElementById(
"pastEventsContainer"
);
if (!upcomingEventsContainer || !pastEventsContainer) return;
// Add 6 skeleton cards initially
for (let i = 0; i < 6; i++) {
// Add 3 skeleton cards to each container initially
const createSkeletonCard = () => {
const skeletonCard = document.createElement("div");
skeletonCard.className = "card bg-base-200 shadow-lg animate-pulse";
skeletonCard.className =
"card bg-base-200 shadow-lg animate-pulse";
skeletonCard.innerHTML = `
<div class="card-body p-5">
<div class="flex flex-col h-full">
@ -455,20 +513,86 @@ import { Icon } from "astro-icon/components";
</div>
</div>
`;
eventsContainer.appendChild(skeletonCard);
return skeletonCard;
};
for (let i = 0; i < 3; i++) {
upcomingEventsContainer.appendChild(createSkeletonCard());
pastEventsContainer.appendChild(createSkeletonCard());
}
const get = Get.getInstance();
const events = await get.getAll<Event>(
"events",
undefined,
"-start_date",
"-start_date"
); // Sort by start date descending
// Clear skeletons
eventsContainer.innerHTML = "";
upcomingEventsContainer.innerHTML = "";
pastEventsContainer.innerHTML = "";
events.forEach((event) => {
// Split events into upcoming and past based on start and end dates
const now = new Date();
const { upcoming, past } = events.reduce(
(acc, event) => {
// Convert UTC dates to local time
const startDate = new Date(event.start_date);
const endDate = new Date(event.end_date);
// Set both dates and now to midnight for date-only comparison
const startLocal = new Date(
startDate.getFullYear(),
startDate.getMonth(),
startDate.getDate(),
startDate.getHours(),
startDate.getMinutes()
);
const endLocal = new Date(
endDate.getFullYear(),
endDate.getMonth(),
endDate.getDate(),
endDate.getHours(),
endDate.getMinutes()
);
const nowLocal = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate(),
now.getHours(),
now.getMinutes()
);
if (startLocal > nowLocal) {
// If start date is in future, it's upcoming
acc.upcoming.push(event);
} else if (endLocal < nowLocal) {
// If end date is in past, it's past
acc.past.push(event);
} else {
// If start date is past but end date is future, it's in progress (show in upcoming)
acc.upcoming.push(event);
}
return acc;
},
{ upcoming: [] as Event[], past: [] as Event[] }
);
// Sort upcoming events by start date (closest first)
upcoming.sort(
(a, b) =>
new Date(a.start_date).getTime() -
new Date(b.start_date).getTime()
);
// Sort past events by end date (most recent first)
past.sort(
(a, b) =>
new Date(b.end_date).getTime() -
new Date(a.end_date).getTime()
);
upcoming.forEach((event) => {
const startDate = new Date(event.start_date);
const endDate = new Date(event.end_date);
@ -493,7 +617,7 @@ import { Icon } from "astro-icon/components";
weekday: "short",
month: "short",
day: "numeric",
},
}
)}
</div>
<div class="text-xs mt-0.5 opacity-75">
@ -502,7 +626,7 @@ import { Icon } from "astro-icon/components";
{
hour: "numeric",
minute: "2-digit",
},
}
)}
</div>
</div>
@ -519,7 +643,7 @@ import { Icon } from "astro-icon/components";
</div>
${(() => {
const endDate = Get.isUTCDateString(
event.end_date,
event.end_date
)
? new Date(event.end_date)
: new Date();
@ -539,7 +663,81 @@ import { Icon } from "astro-icon/components";
</div>
</div>
`;
eventsContainer.appendChild(card);
upcomingEventsContainer.appendChild(card);
});
past.forEach((event) => {
const startDate = new Date(event.start_date);
const endDate = new Date(event.end_date);
const card = document.createElement("div");
card.className =
"card bg-base-200 shadow-lg hover:shadow-xl transition-all duration-300 relative overflow-hidden";
card.innerHTML = `
<div class="card-body p-5">
<div class="flex flex-col h-full">
<div class="flex items-start justify-between gap-3 mb-2">
<div class="flex-1">
<h3 class="card-title text-lg font-semibold mb-1 line-clamp-2">${event.event_name}</h3>
<div class="flex items-center gap-2 text-sm text-base-content/70">
<div class="badge badge-primary badge-sm">${event.points_to_reward} pts</div>
</div>
</div>
<div class="text-right shrink-0 text-base-content/80">
<div class="text-sm font-medium">
${startDate.toLocaleDateString(
"en-US",
{
weekday: "short",
month: "short",
day: "numeric",
}
)}
</div>
<div class="text-xs mt-0.5 opacity-75">
${startDate.toLocaleTimeString(
"en-US",
{
hour: "numeric",
minute: "2-digit",
}
)}
</div>
</div>
</div>
<div class="text-sm text-base-content/70 mb-3 line-clamp-2">
${event.description || "No description available"}
</div>
<div class="flex items-center justify-between">
<div class="flex items-start gap-2.5 text-base-content/80">
<Icon name="mdi:map-marker" class="w-4 h-4 text-primary shrink-0 mt-0.5" />
<span class="text-sm leading-tight truncate max-w-[200px]">${event.location}</span>
</div>
${(() => {
const endDate = Get.isUTCDateString(
event.end_date
)
? new Date(event.end_date)
: new Date();
const now = new Date();
return endDate < now &&
event.files &&
event.files.length > 0
? `
<button onclick="window.openDetailsModal(window['${eventDataId}'])" class="btn btn-ghost btn-xs gap-1">
<Icon name="heroicons:folder-open" class="w-4 h-4" />
Files (${event.files.length})
</button>
`
: "";
})()}
</div>
</div>
</div>
`;
pastEventsContainer.appendChild(card);
});
} catch (error) {
console.error("Failed to load events:", error);