checks to see if event has food and time checked in

This commit is contained in:
chark1es 2025-02-11 00:40:12 -08:00
parent c8ec34a911
commit 8387cff9d5
2 changed files with 200 additions and 35 deletions

View file

@ -29,6 +29,45 @@ import { Icon } from "astro-icon/components";
</div>
</div>
<!-- Food Selection Modal -->
<dialog id="foodSelectionModal" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">Food Selection</h3>
<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-alt text-error">*</span>
</label>
<input
type="text"
id="foodInput"
name="foodInput"
class="input input-bordered"
placeholder="Enter your food choice or 'none'"
required
/>
<label class="label">
<span class="label-text-alt text-info"
>Enter 'none' if you don't want any food</span
>
</label>
</div>
<div class="modal-action">
<button type="submit" class="btn btn-primary">Submit</button>
<button
type="button"
class="btn"
onclick="foodSelectionModal.close()">Cancel</button
>
</div>
</form>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
<!-- Event Registration Card -->
<div class="card bg-base-100 shadow-xl border border-base-200">
<div class="card-body">
@ -160,24 +199,30 @@ import { Icon } from "astro-icon/components";
interface Event {
id: string;
event_id: string;
event_name: string;
event_code: string;
location: string;
files: string[];
points_to_reward: number;
attendees: string[];
attendees: AttendeeEntry[];
start_date: string;
end_date: string;
has_food: boolean;
description: string;
files: string[];
}
async function handleEventCheckIn(eventCode: string) {
interface AttendeeEntry {
user_id: string;
time_checked_in: string;
food: string;
}
let currentCheckInEvent: Event | null = null;
async function handleEventCheckIn(eventCode: string): Promise<void> {
try {
const get = Get.getInstance();
const auth = Authentication.getInstance();
const update = Update.getInstance();
const logger = SendLog.getInstance();
const currentUser = auth.getCurrentUser();
if (!currentUser) {
@ -185,18 +230,16 @@ import { Icon } from "astro-icon/components";
}
// Find the event with the given code
const events = await get.getFirst<Event>(
const event = await get.getFirst<Event>(
"events",
`event_code = "${eventCode}"`,
);
if (!events) {
if (!event) {
throw new Error("Invalid event code");
}
const event = events;
// Check if user is already checked in
if (event.attendees.includes(currentUser.id)) {
if (event.attendees.some((entry) => entry.user_id === currentUser.id)) {
throw new Error("You have already checked in to this event");
}
@ -206,8 +249,82 @@ import { Icon } from "astro-icon/components";
throw new Error("This event has already ended");
}
// Add user to attendees
const updatedAttendees = [...event.attendees, currentUser.id];
// If event has food, show food selection modal
if (event.has_food) {
currentCheckInEvent = event;
const modal = document.getElementById(
"foodSelectionModal",
) as HTMLDialogElement;
modal.showModal();
} else {
// If no food, complete check-in directly
await completeCheckIn(event, null);
}
} catch (error: any) {
createToast(error?.message || "Failed to check in to event", "error");
}
}
// Add food selection form handler
const foodSelectionForm = document.getElementById(
"foodSelectionForm",
) as HTMLFormElement;
if (foodSelectionForm) {
foodSelectionForm.addEventListener("submit", async (e) => {
e.preventDefault();
const modal = document.getElementById(
"foodSelectionModal",
) as HTMLDialogElement;
const foodInput = document.getElementById(
"foodInput",
) as HTMLInputElement;
try {
if (currentCheckInEvent) {
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");
}
});
}
async function completeCheckIn(
event: Event,
foodSelection: string | null,
): Promise<void> {
try {
const auth = Authentication.getInstance();
const update = Update.getInstance();
const logger = SendLog.getInstance();
const currentUser = auth.getCurrentUser();
if (!currentUser) {
throw new Error("You must be logged in to check in to events");
}
// Create attendee entry with check-in details
const attendeeEntry: AttendeeEntry = {
user_id: currentUser.id,
time_checked_in: new Date().toISOString(),
food: foodSelection || "none",
};
// Get existing attendees or initialize empty array
const existingAttendees = event.attendees || [];
// Check if user is already checked in
if (existingAttendees.some((entry) => entry.user_id === currentUser.id)) {
throw new Error("You have already checked in to this event");
}
// Add new attendee entry to the array
const updatedAttendees = [...existingAttendees, attendeeEntry];
// Update attendees array with the new entry
await update.updateField(
"events",
event.id,
@ -215,6 +332,15 @@ import { Icon } from "astro-icon/components";
updatedAttendees,
);
// If food selection was made, log it
if (foodSelection) {
await logger.send(
"update",
"event check-in",
`Food selection for ${event.event_name}: ${foodSelection}`,
);
}
// Award points to user if available
if (event.points_to_reward > 0) {
const userPoints = currentUser.points || 0;
@ -243,7 +369,6 @@ import { Icon } from "astro-icon/components";
"success",
);
} catch (error: any) {
// Show error message
createToast(error?.message || "Failed to check in to event", "error");
}
}

View file

@ -25,6 +25,14 @@ interface Event {
start_date: string;
end_date: string;
published: boolean;
has_food: boolean;
attendees: AttendeeEntry[];
}
interface AttendeeEntry {
user_id: string;
time_checked_in: string;
food: string;
}
interface ListResponse<T> {
@ -369,6 +377,24 @@ declare global {
</label>
</div>
<!-- Has Food -->
<div class="form-control">
<label class="label cursor-pointer justify-start gap-4">
<input
type="checkbox"
id="editEventHasFood"
name="editEventHasFood"
class="toggle"
/>
<span class="label-text">Has Food</span>
</label>
<label class="label">
<span class="label-text-alt text-info"
>Check this if food will be provided at the event</span
>
</label>
</div>
<div class="modal-action">
<button type="submit" class="btn btn-primary">Save Changes</button>
<button type="button" class="btn" onclick="editEventModal.close()"
@ -548,6 +574,11 @@ declare global {
codeInput.value = localEvent?.event_code || "";
locationInput.value = localEvent?.location || "";
pointsInput.value = localEvent?.points_to_reward?.toString() || "0";
publishedInput.checked = localEvent?.published || false;
const hasFoodInput = document.getElementById(
"editEventHasFood",
) as HTMLInputElement;
hasFoodInput.checked = localEvent?.has_food || false;
// Format dates properly for datetime-local input
try {
@ -576,8 +607,6 @@ declare global {
console.error("Error formatting dates:", e);
}
publishedInput.checked = localEvent?.published || false;
// Reset temp files
tempFiles = [];
const newFilesDiv = document.getElementById("newFiles") as HTMLDivElement;
@ -772,6 +801,7 @@ declare global {
start_date: startDate.toISOString(),
end_date: endDate.toISOString(),
published: formData.get("editEventPublished") === "on",
has_food: formData.get("editEventHasFood") === "on",
};
// For new events, add empty attendees list
@ -782,14 +812,14 @@ declare global {
// Update event details
auth.setUpdating(true);
try {
const pb = auth.getPocketBase();
let result;
if (eventId) {
// Update existing event
// Update existing event using Update class
result = await update.updateFields("events", eventId, eventData);
} else {
// Create new event
// Create new event using PocketBase
const pb = auth.getPocketBase();
result = await pb.collection("events").create(eventData);
}
@ -1031,10 +1061,12 @@ declare global {
// Fetch user details for each attendee
const pb = auth.getPocketBase();
const attendeePromises = localEvent.attendees.map(
(userId: string) => pb.collection("users").getOne(userId),
);
const attendees = await Promise.all(attendeePromises);
const attendeePromises = localEvent.attendees.map(function (
attendee: any,
) {
return pb.collection("users").getOne(attendee.user_id);
});
const users = await Promise.all(attendeePromises);
// Display attendees in a table
attendeesContent.innerHTML = `
@ -1046,25 +1078,33 @@ declare global {
<th>Email</th>
<th>Major</th>
<th>Year</th>
<th>Check-in Time</th>
<th>Food Selection</th>
</tr>
</thead>
<tbody>
${attendees
.map(
(user) => `
<tr>
<td>${user.name || "N/A"}</td>
<td>${user.email || "N/A"}</td>
<td>${user.major || "N/A"}</td>
<td>${user.year || "N/A"}</td>
</tr>
`,
)
${localEvent.attendees
.map(function (attendee: any, index: number): string {
const user = users[index];
const checkInTime = new Date(
attendee.time_checked_in,
).toLocaleString();
return `
<tr>
<td>${user.name || "N/A"}</td>
<td>${user.email || "N/A"}</td>
<td>${user.major || "N/A"}</td>
<td>${user.year || "N/A"}</td>
<td>${checkInTime}</td>
<td>${attendee.food || "N/A"}</td>
</tr>
`;
})
.join("")}
</tbody>
</table>
<div class="mt-4 text-sm opacity-70 text-right">
Total Attendees: ${attendees.length}
Total Attendees: ${localEvent.attendees.length}
</div>
</div>
`;