checks to see if event has food and time checked in
This commit is contained in:
parent
c8ec34a911
commit
8387cff9d5
2 changed files with 200 additions and 35 deletions
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
`;
|
||||
|
|
Loading…
Reference in a new issue