fix multiple files not showing

This commit is contained in:
chark1es 2025-02-11 02:41:59 -08:00
parent ac36431107
commit b0cb20d0f4
2 changed files with 1448 additions and 1284 deletions

View file

@ -56,7 +56,13 @@ let upcomingEvents: Event[] = [];
// Fetch events // Fetch events
try { try {
if (auth.isAuthenticated()) { if (auth.isAuthenticated()) {
eventResponse = await get.getList<Event>("events", 1, 5, "", "-start_date"); eventResponse = await get.getList<Event>(
"events",
1,
5,
"",
"-start_date"
);
upcomingEvents = eventResponse.items; upcomingEvents = eventResponse.items;
} }
} catch (error) { } catch (error) {
@ -108,7 +114,9 @@ declare global {
class="stats shadow-lg bg-base-100 rounded-2xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform" class="stats shadow-lg bg-base-100 rounded-2xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform"
> >
<div class="stat"> <div class="stat">
<div class="stat-title font-medium opacity-80">Total Events</div> <div class="stat-title font-medium opacity-80">
Total Events
</div>
<div class="stat-value text-primary" id="totalEvents">-</div> <div class="stat-value text-primary" id="totalEvents">-</div>
<div class="stat-desc flex items-center gap-2 mt-1"> <div class="stat-desc flex items-center gap-2 mt-1">
<div class="badge badge-primary badge-sm">All Time</div> <div class="badge badge-primary badge-sm">All Time</div>
@ -120,9 +128,14 @@ declare global {
> >
<div class="stat"> <div class="stat">
<div class="stat-title font-medium opacity-80">Showing</div> <div class="stat-title font-medium opacity-80">Showing</div>
<div class="stat-value text-secondary" id="showingEvents">-</div> <div class="stat-value text-secondary" id="showingEvents">
-
</div>
<div class="stat-desc flex items-center gap-2 mt-1"> <div class="stat-desc flex items-center gap-2 mt-1">
<div class="badge badge-secondary badge-sm" id="totalEventsLabel"> <div
class="badge badge-secondary badge-sm"
id="totalEventsLabel"
>
of - Events of - Events
</div> </div>
</div> </div>
@ -135,7 +148,10 @@ declare global {
<div class="stat-title font-medium opacity-80">Pages</div> <div class="stat-title font-medium opacity-80">Pages</div>
<div class="stat-value text-accent" id="currentPage">-</div> <div class="stat-value text-accent" id="currentPage">-</div>
<div class="stat-desc flex items-center gap-2 mt-1"> <div class="stat-desc flex items-center gap-2 mt-1">
<div class="badge badge-accent badge-sm" id="totalPagesLabel"> <div
class="badge badge-accent badge-sm"
id="totalPagesLabel"
>
of - of -
</div> </div>
</div> </div>
@ -160,7 +176,9 @@ declare global {
<div class="flex flex-col md:flex-row gap-4 mb-4"> <div class="flex flex-col md:flex-row gap-4 mb-4">
<div class="form-control flex-1"> <div class="form-control flex-1">
<div class="join w-full"> <div class="join w-full">
<div class="join-item bg-base-200 flex items-center px-3"> <div
class="join-item bg-base-200 flex items-center px-3"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 opacity-70" class="h-5 w-5 opacity-70"
@ -183,10 +201,15 @@ declare global {
</div> </div>
<div class="form-control w-full md:w-auto"> <div class="form-control w-full md:w-auto">
<div class="join"> <div class="join">
<div class="join-item bg-base-200 flex items-center px-3"> <div
class="join-item bg-base-200 flex items-center px-3"
>
Per Page Per Page
</div> </div>
<select id="perPageSelect" class="select select-bordered join-item"> <select
id="perPageSelect"
class="select select-bordered join-item"
>
<option value="5">5</option> <option value="5">5</option>
<option value="10">10</option> <option value="10">10</option>
<option value="25">25</option> <option value="25">25</option>
@ -210,13 +233,21 @@ declare global {
<!-- Pagination --> <!-- Pagination -->
<div class="flex justify-center mt-6" id="paginationContainer"> <div class="flex justify-center mt-6" id="paginationContainer">
<div class="join"> <div class="join">
<button class="join-item btn btn-sm" id="firstPageBtn">«</button> <button class="join-item btn btn-sm" id="firstPageBtn"
<button class="join-item btn btn-sm" id="prevPageBtn"></button> >«</button
>
<button class="join-item btn btn-sm" id="prevPageBtn"
></button
>
<button class="join-item btn btn-sm" <button class="join-item btn btn-sm"
>Page <span id="currentPageNumber">1</span></button >Page <span id="currentPageNumber">1</span></button
> >
<button class="join-item btn btn-sm" id="nextPageBtn"></button> <button class="join-item btn btn-sm" id="nextPageBtn"
<button class="join-item btn btn-sm" id="lastPageBtn">»</button> ></button
>
<button class="join-item btn btn-sm" id="lastPageBtn"
>»</button
>
</div> </div>
</div> </div>
</div> </div>
@ -372,8 +403,8 @@ declare global {
</label> </label>
<label class="label"> <label class="label">
<span class="label-text-alt text-info" <span class="label-text-alt text-info"
>This has to be clicked if you want to make this event available to >This has to be clicked if you want to make this event
the public</span available to the public</span
> >
</label> </label>
</div> </div>
@ -397,9 +428,13 @@ declare global {
</div> </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"
<button type="button" class="btn" onclick="editEventModal.close()" >Save Changes</button
>Cancel</button >
<button
type="button"
class="btn"
onclick="editEventModal.close()">Cancel</button
> >
</div> </div>
</form> </form>
@ -448,7 +483,10 @@ declare global {
<div id="attendeesContent" class="space-y-4 hidden"> <div id="attendeesContent" class="space-y-4 hidden">
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Event Attendees</h3> <h3 class="text-lg font-semibold">Event Attendees</h3>
<button id="downloadAttendeesCSV" class="btn btn-primary btn-sm gap-2"> <button
id="downloadAttendeesCSV"
class="btn btn-primary btn-sm gap-2"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4" class="h-4 w-4"
@ -470,10 +508,14 @@ declare global {
<div id="filePreviewSection" class="hidden"> <div id="filePreviewSection" class="hidden">
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button class="btn btn-ghost btn-sm" onclick="backToFileList()"> <button
class="btn btn-ghost btn-sm"
onclick="backToFileList()"
>
← Back ← Back
</button> </button>
<h3 class="font-bold text-lg truncate" id="previewFileName"></h3> <h3 class="font-bold text-lg truncate" id="previewFileName">
</h3>
</div> </div>
</div> </div>
<div class="relative" id="previewContainer"> <div class="relative" id="previewContainer">
@ -538,10 +580,11 @@ declare global {
function resetModalState() { function resetModalState() {
const filesContent = document.getElementById("filesContent"); const filesContent = document.getElementById("filesContent");
const attendeesContent = document.getElementById("attendeesContent"); const attendeesContent = document.getElementById("attendeesContent");
const filePreviewSection = document.getElementById("filePreviewSection"); const filePreviewSection =
document.getElementById("filePreviewSection");
const modalTitle = document.getElementById("modalTitle"); const modalTitle = document.getElementById("modalTitle");
const tabs = document.querySelectorAll( const tabs = document.querySelectorAll(
".tab", ".tab"
) as NodeListOf<HTMLButtonElement>; ) as NodeListOf<HTMLButtonElement>;
// Reset tab states // Reset tab states
@ -568,7 +611,7 @@ declare global {
// Add event listener for modal close when the DOM is loaded // Add event listener for modal close when the DOM is loaded
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const eventDetailsModal = document.getElementById( const eventDetailsModal = document.getElementById(
"eventDetailsModal", "eventDetailsModal"
) as HTMLDialogElement; ) as HTMLDialogElement;
if (eventDetailsModal) { if (eventDetailsModal) {
eventDetailsModal.addEventListener("close", resetModalState); eventDetailsModal.addEventListener("close", resetModalState);
@ -577,10 +620,10 @@ declare global {
// Add event listeners for filters // Add event listeners for filters
const searchInput = document.getElementById( const searchInput = document.getElementById(
"searchInput", "searchInput"
) as HTMLInputElement; ) as HTMLInputElement;
const perPageSelect = document.getElementById( const perPageSelect = document.getElementById(
"perPageSelect", "perPageSelect"
) as HTMLSelectElement; ) as HTMLSelectElement;
if (searchInput) { if (searchInput) {
@ -609,42 +652,48 @@ declare global {
const localEvent = event ? Get.convertUTCToLocal(event) : null; const localEvent = event ? Get.convertUTCToLocal(event) : null;
const modal = document.getElementById( const modal = document.getElementById(
"editEventModal", "editEventModal"
) as HTMLDialogElement; ) as HTMLDialogElement;
const modalTitle = document.getElementById("editModalTitle"); const modalTitle = document.getElementById("editModalTitle");
const form = document.getElementById("editEventForm") as HTMLFormElement; const form = document.getElementById(
const idInput = document.getElementById("editEventId") as HTMLInputElement; "editEventForm"
) as HTMLFormElement;
const idInput = document.getElementById(
"editEventId"
) as HTMLInputElement;
const nameInput = document.getElementById( const nameInput = document.getElementById(
"editEventName", "editEventName"
) as HTMLInputElement; ) as HTMLInputElement;
const descInput = document.getElementById( const descInput = document.getElementById(
"editEventDescription", "editEventDescription"
) as HTMLTextAreaElement; ) as HTMLTextAreaElement;
const codeInput = document.getElementById( const codeInput = document.getElementById(
"editEventCode", "editEventCode"
) as HTMLInputElement; ) as HTMLInputElement;
const locationInput = document.getElementById( const locationInput = document.getElementById(
"editEventLocation", "editEventLocation"
) as HTMLInputElement; ) as HTMLInputElement;
const pointsInput = document.getElementById( const pointsInput = document.getElementById(
"editEventPoints", "editEventPoints"
) as HTMLInputElement; ) as HTMLInputElement;
const startDateInput = document.getElementById( const startDateInput = document.getElementById(
"editEventStartDate", "editEventStartDate"
) as HTMLInputElement; ) as HTMLInputElement;
const endDateInput = document.getElementById( const endDateInput = document.getElementById(
"editEventEndDate", "editEventEndDate"
) as HTMLInputElement; ) as HTMLInputElement;
const publishedInput = document.getElementById( const publishedInput = document.getElementById(
"editEventPublished", "editEventPublished"
) as HTMLInputElement; ) as HTMLInputElement;
const currentFilesDiv = document.getElementById( const currentFilesDiv = document.getElementById(
"currentFiles", "currentFiles"
) as HTMLDivElement; ) as HTMLDivElement;
// Update modal title based on whether we're editing or creating // Update modal title based on whether we're editing or creating
if (modalTitle) { if (modalTitle) {
modalTitle.textContent = localEvent ? "Edit Event" : "Create New Event"; modalTitle.textContent = localEvent
? "Edit Event"
: "Create New Event";
} }
// Set values // Set values
@ -656,21 +705,23 @@ declare global {
pointsInput.value = localEvent?.points_to_reward?.toString() || "0"; pointsInput.value = localEvent?.points_to_reward?.toString() || "0";
publishedInput.checked = localEvent?.published || false; publishedInput.checked = localEvent?.published || false;
const hasFoodInput = document.getElementById( const hasFoodInput = document.getElementById(
"editEventHasFood", "editEventHasFood"
) as HTMLInputElement; ) as HTMLInputElement;
hasFoodInput.checked = localEvent?.has_food || false; hasFoodInput.checked = localEvent?.has_food || false;
// Format dates properly for datetime-local input // Format dates properly for datetime-local input
try { try {
const now = new Date(); const now = new Date();
const startDate = localEvent ? new Date(localEvent.start_date) : now; const startDate = localEvent
? new Date(localEvent.start_date)
: now;
const endDate = localEvent const endDate = localEvent
? new Date(localEvent.end_date) ? new Date(localEvent.end_date)
: new Date(now.getTime() + 60 * 60 * 1000); // Default to 1 hour duration : new Date(now.getTime() + 60 * 60 * 1000); // Default to 1 hour duration
if (!isNaN(startDate.getTime())) { if (!isNaN(startDate.getTime())) {
startDateInput.value = new Date( startDateInput.value = new Date(
startDate.getTime() - startDate.getTimezoneOffset() * 60000, startDate.getTime() - startDate.getTimezoneOffset() * 60000
) )
.toISOString() .toISOString()
.slice(0, 16); .slice(0, 16);
@ -678,7 +729,7 @@ declare global {
if (!isNaN(endDate.getTime())) { if (!isNaN(endDate.getTime())) {
endDateInput.value = new Date( endDateInput.value = new Date(
endDate.getTime() - endDate.getTimezoneOffset() * 60000, endDate.getTime() - endDate.getTimezoneOffset() * 60000
) )
.toISOString() .toISOString()
.slice(0, 16); .slice(0, 16);
@ -689,7 +740,9 @@ declare global {
// Reset temp files // Reset temp files
tempFiles = []; tempFiles = [];
const newFilesDiv = document.getElementById("newFiles") as HTMLDivElement; const newFilesDiv = document.getElementById(
"newFiles"
) as HTMLDivElement;
newFilesDiv.innerHTML = ""; newFilesDiv.innerHTML = "";
// Display current files if any // Display current files if any
@ -711,7 +764,7 @@ declare global {
</button> </button>
</div> </div>
</div> </div>
`, `
) )
.join(""); .join("");
} else { } else {
@ -767,7 +820,7 @@ declare global {
await sendLog.send( await sendLog.send(
"delete", "delete",
"event_files", "event_files",
`Deleted file ${filename} from event ${eventId}`, `Deleted file ${filename} from event ${eventId}`
); );
await fetchEvents(); // Refresh the list await fetchEvents(); // Refresh the list
} catch (error) { } catch (error) {
@ -777,13 +830,13 @@ declare global {
// Handle file input change // Handle file input change
const fileInput = document.getElementById( const fileInput = document.getElementById(
"editEventFiles", "editEventFiles"
) as HTMLInputElement; ) as HTMLInputElement;
if (fileInput) { if (fileInput) {
fileInput.addEventListener("change", function () { fileInput.addEventListener("change", function () {
if (this.files && this.files.length > 0) { if (this.files && this.files.length > 0) {
const newFilesDiv = document.getElementById( const newFilesDiv = document.getElementById(
"newFiles", "newFiles"
) as HTMLDivElement; ) as HTMLDivElement;
// Add new files to temp storage // Add new files to temp storage
@ -805,7 +858,7 @@ declare global {
</button> </button>
</div> </div>
</div> </div>
`, `
) )
.join(""); .join("");
@ -818,7 +871,9 @@ declare global {
// Remove temp file // Remove temp file
window.removeTempFile = function (index: number) { window.removeTempFile = function (index: number) {
tempFiles.splice(index, 1); tempFiles.splice(index, 1);
const newFilesDiv = document.getElementById("newFiles") as HTMLDivElement; const newFilesDiv = document.getElementById(
"newFiles"
) as HTMLDivElement;
if (tempFiles.length === 0) { if (tempFiles.length === 0) {
newFilesDiv.innerHTML = ""; newFilesDiv.innerHTML = "";
@ -839,18 +894,20 @@ declare global {
</button> </button>
</div> </div>
</div> </div>
`, `
) )
.join(""); .join("");
}; };
// Update form submission to handle both create and edit // Update form submission to handle both create and edit
const editForm = document.getElementById("editEventForm") as HTMLFormElement; const editForm = document.getElementById(
"editEventForm"
) as HTMLFormElement;
if (editForm) { if (editForm) {
editForm.addEventListener("submit", async (e) => { editForm.addEventListener("submit", async (e) => {
e.preventDefault(); e.preventDefault();
const modal = document.getElementById( const modal = document.getElementById(
"editEventModal", "editEventModal"
) as HTMLDialogElement; ) as HTMLDialogElement;
try { try {
@ -860,7 +917,9 @@ declare global {
).value; ).value;
// Get the date strings from the form // Get the date strings from the form
const startDateStr = formData.get("editEventStartDate") as string; const startDateStr = formData.get(
"editEventStartDate"
) as string;
const endDateStr = formData.get("editEventEndDate") as string; const endDateStr = formData.get("editEventEndDate") as string;
// Convert local dates to UTC ISO strings // Convert local dates to UTC ISO strings
@ -868,7 +927,9 @@ declare global {
const endDate = new Date(endDateStr); const endDate = new Date(endDateStr);
// Get points value and ensure it's a number // Get points value and ensure it's a number
const pointsValue = parseInt(formData.get("editEventPoints") as string); const pointsValue = parseInt(
formData.get("editEventPoints") as string
);
const points = isNaN(pointsValue) ? 0 : pointsValue; const points = isNaN(pointsValue) ? 0 : pointsValue;
// Prepare base event data without attendees // Prepare base event data without attendees
@ -896,28 +957,44 @@ declare global {
if (eventId) { if (eventId) {
// Update existing event using Update class // Update existing event using Update class
result = await update.updateFields("events", eventId, eventData); result = await update.updateFields(
"events",
eventId,
eventData
);
// Append new files if any, using appendFiles to preserve existing ones
if (tempFiles.length > 0) {
await fileManager.appendFiles(
"events",
result.id,
"files",
tempFiles
);
}
} else { } else {
// Create new event using PocketBase // Create new event using PocketBase
const pb = auth.getPocketBase(); const pb = auth.getPocketBase();
result = await pb.collection("events").create(eventData); result = await pb
} .collection("events")
.create(eventData);
// Upload temp files if any // For new events, use uploadFiles since there are no existing files
if (tempFiles.length > 0) { if (tempFiles.length > 0) {
await fileManager.uploadFiles( await fileManager.uploadFiles(
"events", "events",
result.id, result.id,
"files", "files",
tempFiles, tempFiles
); );
} }
}
// Log the action // Log the action
await sendLog.send( await sendLog.send(
eventId ? "update" : "create", eventId ? "update" : "create",
"events", "events",
`${eventId ? "Updated" : "Created"} event ${eventData.event_name}`, `${eventId ? "Updated" : "Created"} event ${eventData.event_name}`
); );
modal.close(); modal.close();
@ -958,7 +1035,8 @@ declare global {
} }
function backToFileList() { function backToFileList() {
const filePreviewSection = document.getElementById("filePreviewSection"); const filePreviewSection =
document.getElementById("filePreviewSection");
const filesContent = document.getElementById("filesContent"); const filesContent = document.getElementById("filesContent");
const modalTitle = document.getElementById("modalTitle"); const modalTitle = document.getElementById("modalTitle");
@ -967,8 +1045,13 @@ declare global {
if (modalTitle) modalTitle.textContent = "Event Details"; if (modalTitle) modalTitle.textContent = "Event Details";
} }
function showFilePreview(file: { url: string; type: string; name: string }) { function showFilePreview(file: {
const filePreviewSection = document.getElementById("filePreviewSection"); url: string;
type: string;
name: string;
}) {
const filePreviewSection =
document.getElementById("filePreviewSection");
const filesContent = document.getElementById("filesContent"); const filesContent = document.getElementById("filesContent");
const previewContent = document.getElementById("previewContent"); const previewContent = document.getElementById("previewContent");
const previewFileName = document.getElementById("previewFileName"); const previewFileName = document.getElementById("previewFileName");
@ -1081,7 +1164,7 @@ declare global {
// Fetch all user details for the attendees // Fetch all user details for the attendees
const attendeePromises = event.attendees.map(function ( const attendeePromises = event.attendees.map(function (
attendee: AttendeeEntry, attendee: AttendeeEntry
) { ) {
return pb.collection("users").getOne(attendee.user_id); return pb.collection("users").getOne(attendee.user_id);
}); });
@ -1104,7 +1187,7 @@ declare global {
(attendee: AttendeeEntry, index: number) => { (attendee: AttendeeEntry, index: number) => {
const user = users[index]; const user = users[index];
const checkInTime = new Date( const checkInTime = new Date(
attendee.time_checked_in, attendee.time_checked_in
).toLocaleString(); ).toLocaleString();
// Escape and format fields to handle commas and quotes // Escape and format fields to handle commas and quotes
@ -1123,14 +1206,16 @@ declare global {
formatField(checkInTime), formatField(checkInTime),
formatField(attendee.food), formatField(attendee.food),
].join(","); ].join(",");
}, }
); );
// Combine header and rows // Combine header and rows
const csvContent = [csvHeader, ...csvRows].join("\n"); const csvContent = [csvHeader, ...csvRows].join("\n");
// Create and trigger download // Create and trigger download
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); const blob = new Blob([csvContent], {
type: "text/csv;charset=utf-8;",
});
const link = document.createElement("a"); const link = document.createElement("a");
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
link.setAttribute("href", url); link.setAttribute("href", url);
@ -1190,19 +1275,19 @@ declare global {
const localEvent = Get.convertUTCToLocal(event); const localEvent = Get.convertUTCToLocal(event);
const modal = document.getElementById( const modal = document.getElementById(
"eventDetailsModal", "eventDetailsModal"
) as HTMLDialogElement; ) as HTMLDialogElement;
const filesContent = document.getElementById( const filesContent = document.getElementById(
"filesContent", "filesContent"
) as HTMLDivElement; ) as HTMLDivElement;
const attendeesContent = document.getElementById( const attendeesContent = document.getElementById(
"attendeesContent", "attendeesContent"
) as HTMLDivElement; ) as HTMLDivElement;
const filePreviewSection = document.getElementById( const filePreviewSection = document.getElementById(
"filePreviewSection", "filePreviewSection"
) as HTMLDivElement; ) as HTMLDivElement;
const tabs = modal.querySelectorAll( const tabs = modal.querySelectorAll(
".tab", ".tab"
) as NodeListOf<HTMLButtonElement>; ) as NodeListOf<HTMLButtonElement>;
// Reset state // Reset state
@ -1237,7 +1322,10 @@ declare global {
try { try {
// Check if event has attendees // Check if event has attendees
if (!localEvent.attendees || localEvent.attendees.length === 0) { if (
!localEvent.attendees ||
localEvent.attendees.length === 0
) {
attendeesContent.innerHTML = ` attendeesContent.innerHTML = `
<div class="text-center py-8 text-base-content/70"> <div class="text-center py-8 text-base-content/70">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-4 opacity-50" viewBox="0 0 20 20" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-4 opacity-50" viewBox="0 0 20 20" fill="currentColor">
@ -1251,11 +1339,13 @@ 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(function ( const attendeePromises = localEvent.attendees.map(
attendee: AttendeeEntry, function (attendee: AttendeeEntry) {
) { return pb
return pb.collection("users").getOne(attendee.user_id); .collection("users")
}); .getOne(attendee.user_id);
}
);
const users = await Promise.all(attendeePromises); const users = await Promise.all(attendeePromises);
// Display attendees in a table // Display attendees in a table
@ -1285,11 +1375,11 @@ declare global {
${localEvent.attendees ${localEvent.attendees
.map(function ( .map(function (
attendee: AttendeeEntry, attendee: AttendeeEntry,
index: number, index: number
): string { ): string {
const user = users[index]; const user = users[index];
const checkInTime = new Date( const checkInTime = new Date(
attendee.time_checked_in, attendee.time_checked_in
).toLocaleString(); ).toLocaleString();
return ` return `
<tr> <tr>
@ -1313,10 +1403,11 @@ declare global {
// Add click handler for CSV download button // Add click handler for CSV download button
const downloadButton = document.getElementById( const downloadButton = document.getElementById(
"downloadAttendeesCSV", "downloadAttendeesCSV"
); );
if (downloadButton) { if (downloadButton) {
downloadButton.onclick = () => exportAttendeesToCSV(localEvent); downloadButton.onclick = () =>
exportAttendeesToCSV(localEvent);
} }
} catch (error) { } catch (error) {
console.error("Failed to fetch attendees:", error); console.error("Failed to fetch attendees:", error);
@ -1366,7 +1457,7 @@ declare global {
url: fileUrl, url: fileUrl,
type: fileType, type: fileType,
name: file, name: file,
}, }
)})'> )})'>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" /> <path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
@ -1412,7 +1503,7 @@ declare global {
window.deleteEvent = async function (eventId: string, eventName: string) { window.deleteEvent = async function (eventId: string, eventName: string) {
if ( if (
!confirm( !confirm(
`Are you sure you want to delete "${eventName}"? This action cannot be undone.`, `Are you sure you want to delete "${eventName}"? This action cannot be undone.`
) )
) { ) {
return; return;
@ -1422,7 +1513,11 @@ declare global {
auth.setUpdating(true); auth.setUpdating(true);
const pb = auth.getPocketBase(); const pb = auth.getPocketBase();
await pb.collection("events").delete(eventId); await pb.collection("events").delete(eventId);
await sendLog.send("delete", "events", `Deleted event ${eventName}`); await sendLog.send(
"delete",
"events",
`Deleted event ${eventName}`
);
await fetchEvents(); // Refresh the list await fetchEvents(); // Refresh the list
} catch (error) { } catch (error) {
console.error("Failed to delete event:", error); console.error("Failed to delete event:", error);
@ -1434,7 +1529,9 @@ declare global {
async function fetchEvents() { async function fetchEvents() {
const eventsList = document.getElementById("eventsList"); const eventsList = document.getElementById("eventsList");
const paginationContainer = document.getElementById("paginationContainer"); const paginationContainer = document.getElementById(
"paginationContainer"
);
if (!eventsList || !paginationContainer) return; if (!eventsList || !paginationContainer) return;
try { try {
@ -1467,21 +1564,25 @@ declare global {
perPage, perPage,
filter, filter,
"-start_date", "-start_date",
{ disableAutoCancellation: true }, { disableAutoCancellation: true }
); );
// Convert response items to local time // Convert response items to local time
const localEvents = response.items.map((event) => const localEvents = response.items.map((event) =>
Get.convertUTCToLocal(event), Get.convertUTCToLocal(event)
); );
// Update stats // Update stats
const totalEventsEl = document.getElementById("totalEvents"); const totalEventsEl = document.getElementById("totalEvents");
const showingEventsEl = document.getElementById("showingEvents"); const showingEventsEl =
const totalEventsLabelEl = document.getElementById("totalEventsLabel"); document.getElementById("showingEvents");
const totalEventsLabelEl =
document.getElementById("totalEventsLabel");
const currentPageEl = document.getElementById("currentPage"); const currentPageEl = document.getElementById("currentPage");
const totalPagesLabelEl = document.getElementById("totalPagesLabel"); const totalPagesLabelEl =
const currentPageNumber = document.getElementById("currentPageNumber"); document.getElementById("totalPagesLabel");
const currentPageNumber =
document.getElementById("currentPageNumber");
if (totalEventsEl) if (totalEventsEl)
totalEventsEl.textContent = response.totalItems.toString(); totalEventsEl.textContent = response.totalItems.toString();
@ -1489,7 +1590,8 @@ declare global {
showingEventsEl.textContent = localEvents.length.toString(); showingEventsEl.textContent = localEvents.length.toString();
if (totalEventsLabelEl) if (totalEventsLabelEl)
totalEventsLabelEl.textContent = `of ${response.totalItems} Events`; totalEventsLabelEl.textContent = `of ${response.totalItems} Events`;
if (currentPageEl) currentPageEl.textContent = response.page.toString(); if (currentPageEl)
currentPageEl.textContent = response.page.toString();
if (totalPagesLabelEl) if (totalPagesLabelEl)
totalPagesLabelEl.textContent = `of ${response.totalPages}`; totalPagesLabelEl.textContent = `of ${response.totalPages}`;
if (currentPageNumber) if (currentPageNumber)
@ -1497,16 +1599,16 @@ declare global {
// Update pagination buttons state // Update pagination buttons state
const firstPageBtn = document.getElementById( const firstPageBtn = document.getElementById(
"firstPageBtn", "firstPageBtn"
) as HTMLButtonElement; ) as HTMLButtonElement;
const prevPageBtn = document.getElementById( const prevPageBtn = document.getElementById(
"prevPageBtn", "prevPageBtn"
) as HTMLButtonElement; ) as HTMLButtonElement;
const nextPageBtn = document.getElementById( const nextPageBtn = document.getElementById(
"nextPageBtn", "nextPageBtn"
) as HTMLButtonElement; ) as HTMLButtonElement;
const lastPageBtn = document.getElementById( const lastPageBtn = document.getElementById(
"lastPageBtn", "lastPageBtn"
) as HTMLButtonElement; ) as HTMLButtonElement;
if (firstPageBtn) firstPageBtn.disabled = response.page <= 1; if (firstPageBtn) firstPageBtn.disabled = response.page <= 1;
@ -1555,8 +1657,12 @@ declare global {
console.error("Error formatting date:", e); console.error("Error formatting date:", e);
} }
const locationStr = event.location ? `${event.location}` : ""; const locationStr = event.location
const codeStr = event.event_code ? `${event.event_code}` : ""; ? `${event.location}`
: "";
const codeStr = event.event_code
? `${event.event_code}`
: "";
const detailsStr = [locationStr, codeStr] const detailsStr = [locationStr, codeStr]
.filter(Boolean) .filter(Boolean)
.join(" | code: "); .join(" | code: ");

View file

@ -89,6 +89,64 @@ export class FileManager {
} }
} }
/**
* Append multiple files to a record without overriding existing ones
* @param collectionName The name of the collection
* @param recordId The ID of the record to attach the files to
* @param field The field name for the files
* @param files Array of files to upload
* @returns The updated record
*/
public async appendFiles<T = any>(
collectionName: string,
recordId: string,
field: string,
files: File[]
): Promise<T> {
if (!this.auth.isAuthenticated()) {
throw new Error("User must be authenticated to upload files");
}
try {
this.auth.setUpdating(true);
const pb = this.auth.getPocketBase();
// First, get the current record to check existing files
const record = await pb.collection(collectionName).getOne<T>(recordId);
// Create FormData with existing files
const formData = new FormData();
// Get existing files from the record
const existingFiles = (record as any)[field] || [];
// For each existing file, we need to fetch it and add it to the FormData
for (const existingFile of existingFiles) {
try {
const response = await fetch(this.getFileUrl(collectionName, recordId, existingFile));
const blob = await response.blob();
const file = new File([blob], existingFile, { type: blob.type });
formData.append(field, file);
} catch (error) {
console.warn(`Failed to fetch existing file ${existingFile}:`, error);
}
}
// Append new files
files.forEach(file => {
formData.append(field, file);
});
const result = await pb.collection(collectionName).update<T>(recordId, formData);
return result;
} catch (err) {
console.error(`Failed to append files to ${collectionName}:`, err);
throw err;
} finally {
this.auth.setUpdating(false);
}
}
/** /**
* Get the URL for a file * Get the URL for a file
* @param collectionName The name of the collection * @param collectionName The name of the collection