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>
</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 --> <!-- Event Registration Card -->
<div class="card bg-base-100 shadow-xl border border-base-200"> <div class="card bg-base-100 shadow-xl border border-base-200">
<div class="card-body"> <div class="card-body">
@ -160,24 +199,30 @@ import { Icon } from "astro-icon/components";
interface Event { interface Event {
id: string; id: string;
event_id: string;
event_name: string; event_name: string;
event_code: string; event_code: string;
location: string; location: string;
files: string[];
points_to_reward: number; points_to_reward: number;
attendees: string[]; attendees: AttendeeEntry[];
start_date: string; start_date: string;
end_date: string; end_date: string;
has_food: boolean;
description: string; 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 { try {
const get = Get.getInstance(); const get = Get.getInstance();
const auth = Authentication.getInstance(); const auth = Authentication.getInstance();
const update = Update.getInstance();
const logger = SendLog.getInstance();
const currentUser = auth.getCurrentUser(); const currentUser = auth.getCurrentUser();
if (!currentUser) { if (!currentUser) {
@ -185,18 +230,16 @@ import { Icon } from "astro-icon/components";
} }
// Find the event with the given code // Find the event with the given code
const events = await get.getFirst<Event>( const event = await get.getFirst<Event>(
"events", "events",
`event_code = "${eventCode}"`, `event_code = "${eventCode}"`,
); );
if (!events) { if (!event) {
throw new Error("Invalid event code"); throw new Error("Invalid event code");
} }
const event = events;
// Check if user is already checked in // 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"); 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"); throw new Error("This event has already ended");
} }
// Add user to attendees // If event has food, show food selection modal
const updatedAttendees = [...event.attendees, currentUser.id]; 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( await update.updateField(
"events", "events",
event.id, event.id,
@ -215,6 +332,15 @@ import { Icon } from "astro-icon/components";
updatedAttendees, 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 // Award points to user if available
if (event.points_to_reward > 0) { if (event.points_to_reward > 0) {
const userPoints = currentUser.points || 0; const userPoints = currentUser.points || 0;
@ -243,7 +369,6 @@ import { Icon } from "astro-icon/components";
"success", "success",
); );
} catch (error: any) { } catch (error: any) {
// Show error message
createToast(error?.message || "Failed to check in to event", "error"); createToast(error?.message || "Failed to check in to event", "error");
} }
} }

View file

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