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