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>
|
||||||
</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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
`;
|
`;
|
||||||
|
|
Loading…
Reference in a new issue