allows editing profiles
This commit is contained in:
parent
903fe32d9b
commit
3684071673
3 changed files with 280 additions and 192 deletions
|
@ -25,6 +25,14 @@ interface AuthElements {
|
||||||
refreshResumes: HTMLButtonElement;
|
refreshResumes: HTMLButtonElement;
|
||||||
resumeSearch: HTMLInputElement;
|
resumeSearch: HTMLInputElement;
|
||||||
searchResumes: HTMLButtonElement;
|
searchResumes: HTMLButtonElement;
|
||||||
|
profileEditor: HTMLDialogElement;
|
||||||
|
editorName: HTMLInputElement;
|
||||||
|
editorEmail: HTMLInputElement;
|
||||||
|
editorMemberId: HTMLInputElement;
|
||||||
|
editorPoints: HTMLInputElement;
|
||||||
|
editorResume: HTMLInputElement;
|
||||||
|
editorCurrentResume: HTMLParagraphElement;
|
||||||
|
saveProfileButton: HTMLButtonElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StoreAuth {
|
export class StoreAuth {
|
||||||
|
@ -128,6 +136,31 @@ export class StoreAuth {
|
||||||
"searchResumes",
|
"searchResumes",
|
||||||
) as HTMLButtonElement;
|
) as HTMLButtonElement;
|
||||||
|
|
||||||
|
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 editorPoints = document.getElementById(
|
||||||
|
"editorPoints",
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const editorResume = document.getElementById(
|
||||||
|
"editorResume",
|
||||||
|
) as HTMLInputElement;
|
||||||
|
const editorCurrentResume = document.getElementById(
|
||||||
|
"editorCurrentResume",
|
||||||
|
) as HTMLParagraphElement;
|
||||||
|
const saveProfileButton = document.getElementById(
|
||||||
|
"saveProfileButton",
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!loginButton ||
|
!loginButton ||
|
||||||
!logoutButton ||
|
!logoutButton ||
|
||||||
|
@ -153,7 +186,15 @@ export class StoreAuth {
|
||||||
!resumeList ||
|
!resumeList ||
|
||||||
!refreshResumes ||
|
!refreshResumes ||
|
||||||
!resumeSearch ||
|
!resumeSearch ||
|
||||||
!searchResumes
|
!searchResumes ||
|
||||||
|
!profileEditor ||
|
||||||
|
!editorName ||
|
||||||
|
!editorEmail ||
|
||||||
|
!editorMemberId ||
|
||||||
|
!editorPoints ||
|
||||||
|
!editorResume ||
|
||||||
|
!editorCurrentResume ||
|
||||||
|
!saveProfileButton
|
||||||
) {
|
) {
|
||||||
throw new Error("Required DOM elements not found");
|
throw new Error("Required DOM elements not found");
|
||||||
}
|
}
|
||||||
|
@ -184,6 +225,14 @@ export class StoreAuth {
|
||||||
refreshResumes,
|
refreshResumes,
|
||||||
resumeSearch,
|
resumeSearch,
|
||||||
searchResumes,
|
searchResumes,
|
||||||
|
profileEditor,
|
||||||
|
editorName,
|
||||||
|
editorEmail,
|
||||||
|
editorMemberId,
|
||||||
|
editorPoints,
|
||||||
|
editorResume,
|
||||||
|
editorCurrentResume,
|
||||||
|
saveProfileButton,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,8 +423,13 @@ export class StoreAuth {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFileNameFromUrl(url: string): string {
|
private getFileNameFromUrl(url: string): string {
|
||||||
const parts = url.split("/");
|
try {
|
||||||
return parts[parts.length - 1];
|
const urlObj = new URL(url);
|
||||||
|
const pathParts = urlObj.pathname.split("/");
|
||||||
|
return decodeURIComponent(pathParts[pathParts.length - 1]);
|
||||||
|
} catch (e) {
|
||||||
|
return url.split("/").pop() || "Unknown File";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleMemberIdButton() {
|
private async handleMemberIdButton() {
|
||||||
|
@ -436,6 +490,15 @@ export class StoreAuth {
|
||||||
throw new Error("User ID not found");
|
throw new Error("User ID not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get current user data first
|
||||||
|
const currentUser = await this.pb.collection("users").getOne(user.id);
|
||||||
|
|
||||||
|
// Keep existing data
|
||||||
|
formData.append("name", currentUser.name || "");
|
||||||
|
formData.append("email", currentUser.email || "");
|
||||||
|
formData.append("member_id", currentUser.member_id || "");
|
||||||
|
formData.append("points", currentUser.points?.toString() || "0");
|
||||||
|
|
||||||
await this.pb.collection("users").update(user.id, formData);
|
await this.pb.collection("users").update(user.id, formData);
|
||||||
|
|
||||||
uploadStatus.textContent = "Resume uploaded successfully!";
|
uploadStatus.textContent = "Resume uploaded successfully!";
|
||||||
|
@ -515,7 +578,7 @@ export class StoreAuth {
|
||||||
|
|
||||||
private async fetchUserResumes(searchQuery: string = "") {
|
private async fetchUserResumes(searchQuery: string = "") {
|
||||||
try {
|
try {
|
||||||
let filter = 'resume != ""';
|
let filter = ""; // Remove the resume filter to show all users
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
const terms = searchQuery
|
const terms = searchQuery
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
|
@ -528,16 +591,20 @@ export class StoreAuth {
|
||||||
`(name ?~ "${term}" || email ?~ "${term}" || member_id ?~ "${term}")`,
|
`(name ?~ "${term}" || email ?~ "${term}" || member_id ?~ "${term}")`,
|
||||||
)
|
)
|
||||||
.join(" && ");
|
.join(" && ");
|
||||||
filter += ` && (${searchConditions})`;
|
filter = searchConditions; // Only apply search conditions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const records = await this.pb.collection("users").getList(1, 50, {
|
const records = await this.pb.collection("users").getList(1, 50, {
|
||||||
filter,
|
filter,
|
||||||
sort: "-updated",
|
sort: "-updated",
|
||||||
fields: "id,name,email,member_id,resume,updated,points",
|
fields:
|
||||||
|
"id,name,email,member_id,resume,points,collectionId,collectionName",
|
||||||
|
expand: "resume",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("Fetched records:", records.items); // Debug log
|
||||||
|
|
||||||
const { resumeList } = this.elements;
|
const { resumeList } = this.elements;
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
|
|
||||||
|
@ -545,16 +612,19 @@ export class StoreAuth {
|
||||||
const row = document.createElement("tr");
|
const row = document.createElement("tr");
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td colspan="6" class="text-center py-4">
|
<td colspan="6" class="text-center py-4">
|
||||||
${searchQuery ? "No users found matching your search." : "No resumes uploaded yet."}
|
${searchQuery ? "No users found matching your search." : "No users found."}
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
fragment.appendChild(row);
|
fragment.appendChild(row);
|
||||||
} else {
|
} else {
|
||||||
records.items.forEach((user) => {
|
records.items.forEach((user) => {
|
||||||
const row = document.createElement("tr");
|
const row = document.createElement("tr");
|
||||||
const resumeUrl = user.resume
|
const resumeUrl =
|
||||||
? this.pb.files.getURL(user, user.resume)
|
user.resume && user.resume !== ""
|
||||||
: null;
|
? this.pb.files.getURL(user, user.resume.toString())
|
||||||
|
: null;
|
||||||
|
|
||||||
|
console.log("User resume:", user.resume, "Resume URL:", resumeUrl); // Debug log
|
||||||
|
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<td class="block lg:table-cell">
|
<td class="block lg:table-cell">
|
||||||
|
@ -563,40 +633,7 @@ export class StoreAuth {
|
||||||
<div class="font-medium">${user.name || "N/A"}</div>
|
<div class="font-medium">${user.name || "N/A"}</div>
|
||||||
<div class="text-sm opacity-70">${user.email || "N/A"}</div>
|
<div class="text-sm opacity-70">${user.email || "N/A"}</div>
|
||||||
<div class="text-sm opacity-70">ID: ${user.member_id || "N/A"}</div>
|
<div class="text-sm opacity-70">ID: ${user.member_id || "N/A"}</div>
|
||||||
<div class="flex items-center justify-between">
|
<div class="text-sm opacity-70">Points: ${user.points || 0}</div>
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span class="text-sm opacity-70">Points:</span>
|
|
||||||
<div class="points-display-${user.id} flex items-center gap-2">
|
|
||||||
<span class="font-medium">${user.points || 0}</span>
|
|
||||||
<button class="btn btn-xs btn-ghost edit-points" data-user-id="${user.id}">
|
|
||||||
<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>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="points-edit-${user.id} hidden flex items-center gap-2">
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
class="input input-bordered input-xs w-[70px]"
|
|
||||||
value="${user.points || 0}"
|
|
||||||
min="0"
|
|
||||||
data-user-id="${user.id}"
|
|
||||||
/>
|
|
||||||
<div class="flex gap-1">
|
|
||||||
<button class="btn btn-xs btn-ghost confirm-points" data-user-id="${user.id}">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-success" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-xs btn-ghost cancel-points" data-user-id="${user.id}">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-error" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
${
|
${
|
||||||
resumeUrl
|
resumeUrl
|
||||||
|
@ -607,9 +644,12 @@ export class StoreAuth {
|
||||||
`
|
`
|
||||||
: '<span class="text-sm opacity-50">No resume</span>'
|
: '<span class="text-sm opacity-50">No resume</span>'
|
||||||
}
|
}
|
||||||
<span class="text-xs opacity-50">
|
<button class="btn btn-ghost btn-xs edit-profile" data-user-id="${user.id}">
|
||||||
${new Date(user.updated).toLocaleDateString()}
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
</span>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -618,37 +658,7 @@ export class StoreAuth {
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden lg:table-cell">${user.email || "N/A"}</td>
|
<td class="hidden lg:table-cell">${user.email || "N/A"}</td>
|
||||||
<td class="hidden lg:table-cell">${user.member_id || "N/A"}</td>
|
<td class="hidden lg:table-cell">${user.member_id || "N/A"}</td>
|
||||||
<td class="hidden lg:table-cell w-[140px]">
|
<td class="hidden lg:table-cell">${user.points || 0}</td>
|
||||||
<div class="points-display-${user.id} flex items-center justify-between">
|
|
||||||
<span class="font-medium min-w-[40px]">${user.points || 0}</span>
|
|
||||||
<button class="btn btn-xs btn-ghost edit-points" data-user-id="${user.id}">
|
|
||||||
<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>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="points-edit-${user.id} hidden flex items-center justify-between">
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
class="input input-bordered input-xs w-[70px]"
|
|
||||||
value="${user.points || 0}"
|
|
||||||
min="0"
|
|
||||||
data-user-id="${user.id}"
|
|
||||||
/>
|
|
||||||
<div class="flex gap-1">
|
|
||||||
<button class="btn btn-xs btn-ghost confirm-points" data-user-id="${user.id}">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-success" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-xs btn-ghost cancel-points" data-user-id="${user.id}">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-error" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="hidden lg:table-cell">
|
<td class="hidden lg:table-cell">
|
||||||
${
|
${
|
||||||
resumeUrl
|
resumeUrl
|
||||||
|
@ -660,7 +670,14 @@ export class StoreAuth {
|
||||||
: '<span class="text-sm opacity-50">No resume</span>'
|
: '<span class="text-sm opacity-50">No resume</span>'
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden lg:table-cell">${new Date(user.updated).toLocaleDateString()}</td>
|
<td class="hidden lg:table-cell">
|
||||||
|
<button class="btn btn-ghost btn-xs edit-profile" data-user-id="${user.id}">
|
||||||
|
<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 Profile
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
fragment.appendChild(row);
|
fragment.appendChild(row);
|
||||||
|
@ -670,8 +687,16 @@ export class StoreAuth {
|
||||||
resumeList.innerHTML = "";
|
resumeList.innerHTML = "";
|
||||||
resumeList.appendChild(fragment);
|
resumeList.appendChild(fragment);
|
||||||
|
|
||||||
// Setup event listeners for the points editing functionality
|
// Setup edit profile event listeners
|
||||||
this.setupPointsEventListeners();
|
const editButtons = resumeList.querySelectorAll(".edit-profile");
|
||||||
|
editButtons.forEach((button) => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
const userId = (button as HTMLButtonElement).dataset.userId;
|
||||||
|
if (userId) {
|
||||||
|
this.handleProfileEdit(userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to fetch user resumes:", err);
|
console.error("Failed to fetch user resumes:", err);
|
||||||
const { resumeList } = this.elements;
|
const { resumeList } = this.elements;
|
||||||
|
@ -685,131 +710,102 @@ export class StoreAuth {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateUserPoints(userId: string, points: number) {
|
private async handleProfileEdit(userId: string) {
|
||||||
try {
|
try {
|
||||||
await this.pb.collection("users").update(userId, {
|
const user = await this.pb.collection("users").getOne(userId);
|
||||||
points: points,
|
const {
|
||||||
});
|
profileEditor,
|
||||||
|
editorName,
|
||||||
|
editorEmail,
|
||||||
|
editorMemberId,
|
||||||
|
editorPoints,
|
||||||
|
editorCurrentResume,
|
||||||
|
saveProfileButton,
|
||||||
|
} = this.elements;
|
||||||
|
|
||||||
// Update the display after successful update
|
// Populate the form
|
||||||
const displayElement = document.querySelector(
|
editorName.value = user.name || "";
|
||||||
`.points-display-${userId}`,
|
editorEmail.value = user.email || "";
|
||||||
) as HTMLDivElement;
|
editorMemberId.value = user.member_id || "";
|
||||||
const editElement = document.querySelector(
|
editorPoints.value = user.points?.toString() || "0";
|
||||||
`.points-edit-${userId}`,
|
|
||||||
) as HTMLDivElement;
|
// Update resume display
|
||||||
if (displayElement && editElement) {
|
if (user.resume) {
|
||||||
const pointsSpan = displayElement.querySelector("span");
|
const resumeUrl = this.pb.files.getURL(user, user.resume.toString());
|
||||||
if (pointsSpan) {
|
const fileName = this.getFileNameFromUrl(resumeUrl);
|
||||||
pointsSpan.textContent = points.toString();
|
editorCurrentResume.textContent = `Current resume: ${fileName}`;
|
||||||
}
|
editorCurrentResume.classList.remove("opacity-70");
|
||||||
displayElement.classList.remove("hidden");
|
} else {
|
||||||
editElement.classList.add("hidden");
|
editorCurrentResume.textContent = "No resume uploaded";
|
||||||
|
editorCurrentResume.classList.add("opacity-70");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store the user ID for saving
|
||||||
|
saveProfileButton.dataset.userId = userId;
|
||||||
|
|
||||||
|
// Show the dialog
|
||||||
|
profileEditor.showModal();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to update points:", err);
|
console.error("Failed to load user for editing:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupPointsEventListeners() {
|
private async handleProfileSave() {
|
||||||
// Use event delegation for all points-related actions
|
const {
|
||||||
const { resumeList } = this.elements;
|
profileEditor,
|
||||||
|
editorName,
|
||||||
|
editorEmail,
|
||||||
|
editorMemberId,
|
||||||
|
editorPoints,
|
||||||
|
editorResume,
|
||||||
|
saveProfileButton,
|
||||||
|
} = this.elements;
|
||||||
|
const userId = saveProfileButton.dataset.userId;
|
||||||
|
|
||||||
resumeList.addEventListener("click", async (e) => {
|
if (!userId) {
|
||||||
console.log("Click event triggered");
|
console.error("No user ID found for saving");
|
||||||
const target = e.target as HTMLElement;
|
return;
|
||||||
const button = target.closest("button");
|
}
|
||||||
console.log("Button found:", button);
|
|
||||||
if (!button) return;
|
|
||||||
|
|
||||||
const userId = button.dataset.userId;
|
try {
|
||||||
console.log("User ID:", userId);
|
// First get the current user data to check existing resume
|
||||||
if (!userId) return;
|
const currentUser = await this.pb.collection("users").getOne(userId);
|
||||||
|
|
||||||
// Handle edit button click
|
const formData = new FormData();
|
||||||
if (button.classList.contains("edit-points")) {
|
formData.append("name", editorName.value);
|
||||||
console.log("Edit points button clicked");
|
formData.append("email", editorEmail.value);
|
||||||
const row = button.closest("tr");
|
formData.append("member_id", editorMemberId.value);
|
||||||
if (!row) return;
|
formData.append("points", editorPoints.value);
|
||||||
|
|
||||||
const displayElement = row.querySelector(
|
// Only append resume if a new file is selected
|
||||||
`.points-display-${userId}`,
|
if (editorResume.files && editorResume.files.length > 0) {
|
||||||
) as HTMLDivElement;
|
formData.append("resume", editorResume.files[0]);
|
||||||
const editElement = row.querySelector(
|
} else if (currentUser.resume) {
|
||||||
`.points-edit-${userId}`,
|
// If no new file but there's an existing resume, keep it
|
||||||
) as HTMLDivElement;
|
formData.append("resume", currentUser.resume);
|
||||||
|
|
||||||
console.log("Display element:", displayElement);
|
|
||||||
console.log("Edit element:", editElement);
|
|
||||||
|
|
||||||
if (displayElement && editElement) {
|
|
||||||
const currentPoints =
|
|
||||||
displayElement.querySelector("span")?.textContent;
|
|
||||||
console.log("Current points:", currentPoints);
|
|
||||||
const input = editElement.querySelector("input") as HTMLInputElement;
|
|
||||||
console.log("Input element:", input);
|
|
||||||
if (input && currentPoints) {
|
|
||||||
input.value = currentPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
displayElement.classList.add("hidden");
|
|
||||||
editElement.classList.remove("hidden");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle confirm button click
|
// Log the form data for debugging
|
||||||
if (button.classList.contains("confirm-points")) {
|
console.log("Form data being sent:", {
|
||||||
const row = button.closest("tr");
|
name: editorName.value,
|
||||||
if (!row) return;
|
email: editorEmail.value,
|
||||||
|
member_id: editorMemberId.value,
|
||||||
|
points: editorPoints.value,
|
||||||
|
hasNewResume: editorResume.files && editorResume.files.length > 0,
|
||||||
|
hasExistingResume: !!currentUser.resume,
|
||||||
|
});
|
||||||
|
|
||||||
const input = row.querySelector(
|
const updatedUser = await this.pb
|
||||||
`input[data-user-id="${userId}"]`,
|
.collection("users")
|
||||||
) as HTMLInputElement;
|
.update(userId, formData);
|
||||||
if (input) {
|
console.log("Update response:", updatedUser);
|
||||||
const points = parseInt(input.value) || 0;
|
|
||||||
await this.updateUserPoints(userId, points);
|
|
||||||
|
|
||||||
const displayElement = row.querySelector(
|
// Close the dialog and refresh the table
|
||||||
`.points-display-${userId}`,
|
profileEditor.close();
|
||||||
) as HTMLDivElement;
|
this.fetchUserResumes();
|
||||||
const editElement = row.querySelector(
|
} catch (err) {
|
||||||
`.points-edit-${userId}`,
|
console.error("Failed to save user profile:", err);
|
||||||
) as HTMLDivElement;
|
}
|
||||||
|
|
||||||
if (displayElement && editElement) {
|
|
||||||
displayElement.classList.remove("hidden");
|
|
||||||
editElement.classList.add("hidden");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle cancel button click
|
|
||||||
if (button.classList.contains("cancel-points")) {
|
|
||||||
const row = button.closest("tr");
|
|
||||||
if (!row) return;
|
|
||||||
|
|
||||||
const displayElement = row.querySelector(
|
|
||||||
`.points-display-${userId}`,
|
|
||||||
) as HTMLDivElement;
|
|
||||||
const editElement = row.querySelector(
|
|
||||||
`.points-edit-${userId}`,
|
|
||||||
) as HTMLDivElement;
|
|
||||||
const input = row.querySelector(
|
|
||||||
`input[data-user-id="${userId}"]`,
|
|
||||||
) as HTMLInputElement;
|
|
||||||
const currentPoints =
|
|
||||||
displayElement?.querySelector("span")?.textContent;
|
|
||||||
|
|
||||||
if (input && currentPoints) {
|
|
||||||
input.value = currentPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (displayElement && editElement) {
|
|
||||||
displayElement.classList.remove("hidden");
|
|
||||||
editElement.classList.add("hidden");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
|
@ -883,5 +879,21 @@ export class StoreAuth {
|
||||||
console.log("Auth state changed. IsValid:", this.pb.authStore.isValid);
|
console.log("Auth state changed. IsValid:", this.pb.authStore.isValid);
|
||||||
this.updateUI();
|
this.updateUI();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Profile editor event listeners
|
||||||
|
const { profileEditor, saveProfileButton } = this.elements;
|
||||||
|
|
||||||
|
// Close dialog when clicking outside
|
||||||
|
profileEditor.addEventListener("click", (e) => {
|
||||||
|
if (e.target === profileEditor) {
|
||||||
|
profileEditor.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save profile button
|
||||||
|
saveProfileButton.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.handleProfileSave();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,6 +171,76 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Profile Editor Dialog -->
|
||||||
|
<dialog id="profileEditor" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h3 class="font-bold text-lg mb-4">Edit Profile</h3>
|
||||||
|
<form class="space-y-4" onsubmit="return false" novalidate>
|
||||||
|
<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"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</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" />
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Loyalty Points</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="editorPoints"
|
||||||
|
min="0"
|
||||||
|
class="input input-bordered"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text">Resume</span>
|
||||||
|
</label>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<p id="editorCurrentResume" class="text-sm opacity-70">
|
||||||
|
No resume uploaded
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
id="editorResume"
|
||||||
|
accept=".pdf,.doc,.docx"
|
||||||
|
class="file-input file-input-bordered file-input-sm w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-action">
|
||||||
|
<button type="button" id="saveProfileButton" class="btn btn-primary"
|
||||||
|
>Save Changes</button
|
||||||
|
>
|
||||||
|
<button type="button" class="btn" onclick="profileEditor.close()"
|
||||||
|
>Cancel</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button>close</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -178,4 +248,10 @@
|
||||||
#userInfo {
|
#userInfo {
|
||||||
transition: opacity 0.3s ease-in-out;
|
transition: opacity 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
.modal {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
.modal::backdrop {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -103,7 +103,7 @@ const title = "IEEE Store";
|
||||||
<th>Member ID</th>
|
<th>Member ID</th>
|
||||||
<th>Points</th>
|
<th>Points</th>
|
||||||
<th>Resume</th>
|
<th>Resume</th>
|
||||||
<th>Last Updated</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="resumeList" class="divide-y">
|
<tbody id="resumeList" class="divide-y">
|
||||||
|
|
Loading…
Reference in a new issue