fixed manual refresh
This commit is contained in:
parent
4ea541fc64
commit
172c56e023
2 changed files with 379 additions and 53 deletions
|
@ -232,21 +232,40 @@ const { title, columns } = config.ui.tables.events;
|
|||
<td class="text-center">${formatDate(event.start_date)}</td>
|
||||
<td class="text-center">${formatDate(event.end_date)}</td>
|
||||
<td class="text-center">${event.points_to_reward || "0"}</td>
|
||||
<td class="text-center">${event.location || "N/A"}</td>
|
||||
<td class="text-center">
|
||||
<div class="flex justify-center gap-2">
|
||||
<button class="btn btn-ghost btn-xs" onclick="document.dispatchEvent(new CustomEvent('viewAttendees', { detail: '${event.id}' }))">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" />
|
||||
</svg>
|
||||
Attendees
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-xs" onclick="document.getElementById('eventEditor').showModal(); document.dispatchEvent(new CustomEvent('editEvent', { detail: ${JSON.stringify(event)} }))">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
${
|
||||
event.files && event.files.length > 0
|
||||
? event.files
|
||||
.map(
|
||||
(file: string) => `
|
||||
<button class="btn btn-ghost btn-xs" data-file-url="${pb.files.getUrl(event, file)}" data-file-name="${file}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
View
|
||||
</button>
|
||||
`
|
||||
)
|
||||
.join("")
|
||||
: "No files"
|
||||
}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-ghost btn-xs view-attendees" data-event-id="${event.id}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" />
|
||||
</svg>
|
||||
Attendees
|
||||
</button>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-ghost btn-xs edit-event" data-event="${encodeURIComponent(JSON.stringify(event))}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
|
@ -260,9 +279,9 @@ const { title, columns } = config.ui.tables.events;
|
|||
// Show loading state
|
||||
eventsList.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="7" class="text-center py-4">
|
||||
<td colspan="10" class="text-center py-8">
|
||||
<span class="loading loading-spinner loading-md"></span>
|
||||
Loading events...
|
||||
<div class="mt-2">Loading events...</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
|
@ -281,7 +300,7 @@ const { title, columns } = config.ui.tables.events;
|
|||
if (events.items.length === 0) {
|
||||
eventsList.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="7" class="text-center py-4">
|
||||
<td colspan="10" class="text-center py-8">
|
||||
No events found
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -294,7 +313,7 @@ const { title, columns } = config.ui.tables.events;
|
|||
console.error("Failed to load events:", error);
|
||||
eventsList.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="7" class="text-center py-4 text-error">
|
||||
<td colspan="10" class="text-center py-8 text-error">
|
||||
Failed to load events. Please try again.
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -319,18 +338,6 @@ const { title, columns } = config.ui.tables.events;
|
|||
refreshButton.addEventListener("click", () => {
|
||||
if (searchInput) searchInput.value = "";
|
||||
loadEvents();
|
||||
|
||||
// Turn off officer view toggle
|
||||
const officerViewToggle =
|
||||
document.getElementById("officerViewToggle");
|
||||
const officerViewCheckbox = officerViewToggle?.querySelector(
|
||||
'input[type="checkbox"]'
|
||||
) as HTMLInputElement;
|
||||
if (officerViewCheckbox) {
|
||||
officerViewCheckbox.checked = false;
|
||||
// Trigger the change event to update the views
|
||||
officerViewCheckbox.dispatchEvent(new Event("change"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -407,4 +414,48 @@ const { title, columns } = config.ui.tables.events;
|
|||
document.addEventListener("showFilePreview", ((e: CustomEvent) => {
|
||||
showFilePreview(e.detail.url, e.detail.fileName);
|
||||
}) as EventListener);
|
||||
|
||||
// Add event listeners for dynamic elements
|
||||
document.addEventListener("click", (e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
// Handle file view buttons
|
||||
const fileButton = target.closest("button[data-file-url]");
|
||||
if (fileButton) {
|
||||
const url = fileButton.getAttribute("data-file-url");
|
||||
const fileName = fileButton.getAttribute("data-file-name");
|
||||
if (url && fileName) {
|
||||
showFilePreview(url, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle view attendees button
|
||||
const attendeesButton = target.closest(".view-attendees");
|
||||
if (attendeesButton) {
|
||||
const eventId = attendeesButton.getAttribute("data-event-id");
|
||||
if (eventId) {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("viewAttendees", { detail: eventId })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle edit event button
|
||||
const editButton = target.closest(".edit-event");
|
||||
if (editButton) {
|
||||
const eventData = editButton.getAttribute("data-event");
|
||||
if (eventData) {
|
||||
const event = JSON.parse(decodeURIComponent(eventData));
|
||||
const eventEditor = document.getElementById(
|
||||
"eventEditor"
|
||||
) as HTMLDialogElement;
|
||||
if (eventEditor) {
|
||||
eventEditor.showModal();
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("editEvent", { detail: event })
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
---
|
||||
// Import the majors list
|
||||
import allMajors from "../../data/allUCSDMajors.txt?raw";
|
||||
const majorsList: string[] = allMajors
|
||||
.split("\n")
|
||||
.filter((major: string) => major.trim())
|
||||
.sort((a, b) => a.localeCompare(b)); // Sort alphabetically
|
||||
---
|
||||
|
||||
<div
|
||||
class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 lg:gap-0 mb-4"
|
||||
>
|
||||
|
@ -57,6 +66,8 @@
|
|||
<th class="text-center">Name</th>
|
||||
<th class="text-center">Email</th>
|
||||
<th class="text-center">Member ID</th>
|
||||
<th class="text-center">Major</th>
|
||||
<th class="text-center">Grad Year</th>
|
||||
<th class="text-center">Points</th>
|
||||
<th class="text-center">Resume</th>
|
||||
<th class="text-center">Actions</th>
|
||||
|
@ -68,11 +79,139 @@
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Add Profile Editor Dialog -->
|
||||
<dialog id="profileEditor" class="modal">
|
||||
<div class="modal-box">
|
||||
<h3 class="font-bold text-lg mb-4">Edit User Profile</h3>
|
||||
<form method="dialog" class="space-y-4">
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Name</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editorName"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Email</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="editorEmail"
|
||||
class="input input-bordered"
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">IEEE Member ID</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="editorMemberId"
|
||||
class="input input-bordered"
|
||||
placeholder="00000000"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Major</span>
|
||||
</label>
|
||||
<select id="editorMajor" class="select select-bordered w-full">
|
||||
<option value="">Select major</option>
|
||||
{
|
||||
majorsList.map((major) => (
|
||||
<option value={major.trim()}>{major.trim()}</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Graduation Year</span>
|
||||
</label>
|
||||
<select
|
||||
id="editorGradYear"
|
||||
class="select select-bordered w-full"
|
||||
>
|
||||
<option value="">Select graduation year</option>
|
||||
{
|
||||
Array.from({ length: 6 }, (_, i) => {
|
||||
const year = new Date().getFullYear() + i;
|
||||
return <option value={year}>{year}</option>;
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Points</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="editorPoints"
|
||||
class="input input-bordered"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text">Events Attended</span>
|
||||
</label>
|
||||
<textarea
|
||||
id="editorEvents"
|
||||
class="textarea textarea-bordered"
|
||||
readonly></textarea>
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
<button
|
||||
class="btn"
|
||||
onclick="document.getElementById('profileEditor').close()"
|
||||
>Cancel</button
|
||||
>
|
||||
<button id="saveProfileButton" class="btn btn-primary"
|
||||
>Save Changes</button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script is:inline define:vars={{ majorsList }}>
|
||||
// Make majorsList available globally
|
||||
window.majorsList = majorsList;
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import PocketBase from "pocketbase";
|
||||
import type { RecordModel } from "pocketbase";
|
||||
import yaml from "js-yaml";
|
||||
import configYaml from "../../data/storeConfig.yaml?raw";
|
||||
|
||||
// Define interface for user data structure
|
||||
interface UserData {
|
||||
id: string;
|
||||
avatar: string;
|
||||
collectionId: string;
|
||||
collectionName: string;
|
||||
created: string;
|
||||
email: string;
|
||||
emailVisibility: boolean;
|
||||
events_attended: string[];
|
||||
graduation_year: number;
|
||||
last_login: string;
|
||||
major: string;
|
||||
member_id: string;
|
||||
member_type: string;
|
||||
name: string;
|
||||
points: number;
|
||||
resume: string[];
|
||||
updated: string;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
// Parse YAML configuration
|
||||
interface Config {
|
||||
api: {
|
||||
|
@ -95,21 +234,104 @@
|
|||
return new Date(dateStr).toLocaleString();
|
||||
}
|
||||
|
||||
// Make handleEditUser globally available
|
||||
window.handleEditUser = function (user: UserData) {
|
||||
console.log("Edit user data:", user);
|
||||
const profileEditor = document.getElementById(
|
||||
"profileEditor"
|
||||
) as HTMLDialogElement;
|
||||
const editorName = document.getElementById(
|
||||
"editorName"
|
||||
) as HTMLInputElement;
|
||||
const editorEmail = document.getElementById(
|
||||
"editorEmail"
|
||||
) as HTMLInputElement;
|
||||
const editorMemberId = document.getElementById(
|
||||
"editorMemberId"
|
||||
) as HTMLInputElement;
|
||||
const editorMajor = document.getElementById(
|
||||
"editorMajor"
|
||||
) as HTMLSelectElement;
|
||||
const editorGradYear = document.getElementById(
|
||||
"editorGradYear"
|
||||
) as HTMLSelectElement;
|
||||
const editorPoints = document.getElementById(
|
||||
"editorPoints"
|
||||
) as HTMLInputElement;
|
||||
const editorEvents = document.getElementById(
|
||||
"editorEvents"
|
||||
) as HTMLTextAreaElement;
|
||||
const saveProfileButton = document.getElementById(
|
||||
"saveProfileButton"
|
||||
) as HTMLButtonElement;
|
||||
|
||||
// Check if all required elements exist
|
||||
if (
|
||||
!profileEditor ||
|
||||
!editorName ||
|
||||
!editorEmail ||
|
||||
!editorMemberId ||
|
||||
!editorMajor ||
|
||||
!editorGradYear ||
|
||||
!editorPoints ||
|
||||
!editorEvents ||
|
||||
!saveProfileButton
|
||||
) {
|
||||
console.error("Missing form elements:", {
|
||||
profileEditor: !!profileEditor,
|
||||
editorName: !!editorName,
|
||||
editorEmail: !!editorEmail,
|
||||
editorMemberId: !!editorMemberId,
|
||||
editorMajor: !!editorMajor,
|
||||
editorGradYear: !!editorGradYear,
|
||||
editorPoints: !!editorPoints,
|
||||
editorEvents: !!editorEvents,
|
||||
saveProfileButton: !!saveProfileButton,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Populate the form with user data
|
||||
editorName.value = user.name || "";
|
||||
editorEmail.value = user.email || "";
|
||||
editorMemberId.value = user.member_id || "";
|
||||
editorMajor.value = user.major?.trim() || ""; // Trim to remove any newlines
|
||||
editorGradYear.value = user.graduation_year?.toString() || "";
|
||||
editorPoints.value = user.points?.toString() || "0";
|
||||
editorEvents.value = Array.isArray(user.events_attended)
|
||||
? user.events_attended.join(", ")
|
||||
: "No events attended";
|
||||
saveProfileButton.dataset.userId = user.id;
|
||||
|
||||
// Show the dialog
|
||||
profileEditor.showModal();
|
||||
} catch (err) {
|
||||
console.error("Error populating form:", err);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to render user row
|
||||
function renderUserRow(user: any) {
|
||||
function renderUserRow(user: RecordModel) {
|
||||
const userData = user as unknown as UserData;
|
||||
const escapedUser = JSON.stringify(userData)
|
||||
.replace(/'/g, "\\'")
|
||||
.replace(/"/g, """);
|
||||
return `
|
||||
<tr class="hover:bg-base-200">
|
||||
<td class="text-center">
|
||||
<div class="font-medium">${user.name || "N/A"}</div>
|
||||
<div class="font-medium">${userData.name || "N/A"}</div>
|
||||
</td>
|
||||
<td class="text-center">${user.email || "N/A"}</td>
|
||||
<td class="text-center">${user.member_id || "N/A"}</td>
|
||||
<td class="text-center">${user.points || "0"}</td>
|
||||
<td class="text-center">${userData.email || "N/A"}</td>
|
||||
<td class="text-center">${userData.member_id || "N/A"}</td>
|
||||
<td class="text-center">${userData.major?.trim() || "N/A"}</td>
|
||||
<td class="text-center">${userData.graduation_year || "N/A"}</td>
|
||||
<td class="text-center">${userData.points || "0"}</td>
|
||||
<td class="text-center">
|
||||
${
|
||||
user.resume
|
||||
userData.resume && userData.resume.length > 0
|
||||
? `
|
||||
<button class="btn btn-ghost btn-xs" onclick="window.open('${pb.files.getUrl(user, user.resume)}', '_blank')">
|
||||
<button class="btn btn-ghost btn-xs" onclick="window.open('${pb.files.getUrl(userData, userData.resume[0])}', '_blank')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
@ -120,7 +342,7 @@
|
|||
}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-ghost btn-xs" onclick="document.getElementById('profileEditor').showModal(); document.dispatchEvent(new CustomEvent('editUser', { detail: ${JSON.stringify(user)} }))">
|
||||
<button class="btn btn-ghost btn-xs" onclick="handleEditUser(${escapedUser})">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
||||
</svg>
|
||||
|
@ -131,6 +353,53 @@
|
|||
`;
|
||||
}
|
||||
|
||||
// Function to handle saving profile changes
|
||||
async function handleProfileSave() {
|
||||
const profileEditor = document.getElementById(
|
||||
"profileEditor"
|
||||
) as HTMLDialogElement;
|
||||
const editorName = document.getElementById(
|
||||
"editorName"
|
||||
) as HTMLInputElement;
|
||||
const editorMemberId = document.getElementById(
|
||||
"editorMemberId"
|
||||
) as HTMLInputElement;
|
||||
const editorMajor = document.getElementById(
|
||||
"editorMajor"
|
||||
) as HTMLSelectElement;
|
||||
const editorGradYear = document.getElementById(
|
||||
"editorGradYear"
|
||||
) as HTMLSelectElement;
|
||||
const editorPoints = document.getElementById(
|
||||
"editorPoints"
|
||||
) as HTMLInputElement;
|
||||
const saveProfileButton = document.getElementById(
|
||||
"saveProfileButton"
|
||||
) as HTMLButtonElement;
|
||||
const userId = saveProfileButton.dataset.userId;
|
||||
|
||||
if (!userId) {
|
||||
console.error("No user ID found for saving");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("name", editorName.value);
|
||||
formData.append("member_id", editorMemberId.value);
|
||||
formData.append("major", editorMajor.value);
|
||||
formData.append("graduation_year", editorGradYear.value);
|
||||
formData.append("points", editorPoints.value);
|
||||
|
||||
await pb.collection("users").update(userId, formData);
|
||||
profileEditor.close();
|
||||
// Refresh the user list
|
||||
loadUsers();
|
||||
} catch (err) {
|
||||
console.error("Failed to save user profile:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to load users
|
||||
async function loadUsers(searchQuery = "") {
|
||||
if (!resumeList) return;
|
||||
|
@ -139,9 +408,9 @@
|
|||
// Show loading state
|
||||
resumeList.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-4">
|
||||
<td colspan="8" class="text-center py-8">
|
||||
<span class="loading loading-spinner loading-md"></span>
|
||||
Loading users...
|
||||
<div class="mt-2">Loading users...</div>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
|
@ -160,7 +429,7 @@
|
|||
if (users.items.length === 0) {
|
||||
resumeList.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-4">
|
||||
<td colspan="8" class="text-center py-8">
|
||||
No users found
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -173,7 +442,7 @@
|
|||
console.error("Failed to load users:", error);
|
||||
resumeList.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-4 text-error">
|
||||
<td colspan="8" class="text-center py-8 text-error">
|
||||
Failed to load users. Please try again.
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -198,21 +467,27 @@
|
|||
refreshButton.addEventListener("click", () => {
|
||||
if (searchInput) searchInput.value = "";
|
||||
loadUsers();
|
||||
});
|
||||
}
|
||||
|
||||
// Turn off officer view toggle
|
||||
const officerViewToggle =
|
||||
document.getElementById("officerViewToggle");
|
||||
const officerViewCheckbox = officerViewToggle?.querySelector(
|
||||
'input[type="checkbox"]'
|
||||
) as HTMLInputElement;
|
||||
if (officerViewCheckbox) {
|
||||
officerViewCheckbox.checked = false;
|
||||
// Trigger the change event to update the views
|
||||
officerViewCheckbox.dispatchEvent(new Event("change"));
|
||||
}
|
||||
// Add event listener for save button
|
||||
const saveProfileButton = document.getElementById("saveProfileButton");
|
||||
if (saveProfileButton) {
|
||||
saveProfileButton.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
handleProfileSave();
|
||||
});
|
||||
}
|
||||
|
||||
// Initial load
|
||||
loadUsers();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
declare global {
|
||||
interface Window {
|
||||
handleEditUser: (user: any) => void;
|
||||
majorsList: string[];
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in a new issue