updateweb

This commit is contained in:
Local Server
2025-12-24 00:13:23 -06:00
parent e4b3de4a46
commit 017c6376fc
88 changed files with 17866 additions and 1191 deletions

View File

@@ -2,9 +2,23 @@
let projectsData = [];
let projectModal;
let quillEditor;
let portfolioImages = [];
let currentMediaPicker = null;
let isModalExpanded = false;
document.addEventListener("DOMContentLoaded", function () {
projectModal = new bootstrap.Modal(document.getElementById("projectModal"));
// Fix aria-hidden accessibility issue
const projectModalElement = document.getElementById("projectModal");
projectModalElement.addEventListener("hide.bs.modal", function () {
document.querySelector(".btn.btn-primary")?.focus();
});
// Initialize Quill editor
initializeQuillEditor();
checkAuth().then((authenticated) => {
if (authenticated) {
loadProjects();
@@ -17,14 +31,100 @@ document.addEventListener("DOMContentLoaded", function () {
}
});
function resetModalSize() {
const modalDialog = document.querySelector("#projectModal .modal-dialog");
const expandIcon = document.getElementById("expandIcon");
const expandText = document.querySelector("#btnExpandModal span");
const editor = document.getElementById("projectDescriptionEditor");
if (modalDialog && expandIcon && expandText && editor) {
modalDialog.classList.remove("modal-fullscreen");
modalDialog.classList.add("modal-xl");
expandIcon.className = "bi bi-arrows-fullscreen";
expandText.textContent = "Expand";
editor.style.height = "300px";
const container = editor.querySelector(".ql-container");
if (container) {
container.style.height = "calc(300px - 42px)";
}
isModalExpanded = false;
}
}
function toggleModalSize() {
const modalDialog = document.querySelector("#projectModal .modal-dialog");
const expandIcon = document.getElementById("expandIcon");
const expandText = document.querySelector("#btnExpandModal span");
const editor = document.getElementById("projectDescriptionEditor");
if (!modalDialog || !expandIcon || !expandText || !editor) {
console.error("Modal elements not found");
return;
}
if (isModalExpanded) {
// Collapse to normal size
modalDialog.classList.remove("modal-fullscreen");
modalDialog.classList.add("modal-xl");
expandIcon.className = "bi bi-arrows-fullscreen";
expandText.textContent = "Expand";
editor.style.height = "300px";
const container = editor.querySelector(".ql-container");
if (container) {
container.style.height = "calc(300px - 42px)";
}
isModalExpanded = false;
} else {
// Expand to fullscreen
modalDialog.classList.remove("modal-xl");
modalDialog.classList.add("modal-fullscreen");
expandIcon.className = "bi bi-fullscreen-exit";
expandText.textContent = "Collapse";
editor.style.height = "60vh";
const container = editor.querySelector(".ql-container");
if (container) {
container.style.height = "calc(60vh - 42px)";
}
isModalExpanded = true;
}
}
// Initialize Quill Editor
function initializeQuillEditor() {
quillEditor = new Quill("#projectDescriptionEditor", {
theme: "snow",
placeholder: "Describe your portfolio project here...",
modules: {
toolbar: [
[{ header: [1, 2, 3, false] }],
["bold", "italic", "underline", "strike"],
[{ list: "ordered" }, { list: "bullet" }],
[{ color: [] }, { background: [] }],
["link", "image"],
["clean"],
],
},
});
}
async function loadProjects() {
try {
const response = await fetch("/api/admin/portfolio/projects", {
credentials: "include",
cache: "no-cache", // Force fresh data
});
const data = await response.json();
if (data.success) {
projectsData = data.projects;
console.log(
"📊 Loaded projects:",
projectsData.map((p) => ({
id: p.id,
title: p.title,
isactive: p.isactive,
isactiveType: typeof p.isactive,
}))
);
renderProjects(projectsData);
}
} catch (error) {
@@ -47,28 +147,45 @@ function renderProjects(projects) {
}
tbody.innerHTML = projects
.map(
(p) => `
.map((p) => {
// Explicitly check and log the status
console.log(
`Project ${p.id}: isactive =`,
p.isactive,
`(type: ${typeof p.isactive})`
);
const isActive =
p.isactive === true || p.isactive === "true" || p.isactive === 1;
console.log(` -> Evaluated as: ${isActive ? "ACTIVE" : "INACTIVE"}`);
const statusClass = isActive
? "bg-success text-white"
: "bg-danger text-white";
const statusText = isActive ? "Active" : "Inactive";
const statusIcon = isActive ? "✓" : "✗";
return `
<tr>
<td>${p.id}</td>
<td>${escapeHtml(String(p.id))}</td>
<td><strong>${escapeHtml(p.title)}</strong></td>
<td>${escapeHtml((p.description || "").substring(0, 50))}...</td>
<td>${p.category || "-"}</td>
<td><span class="badge ${p.isactive ? "badge-success" : "badge-danger"}">
${p.isactive ? "Active" : "Inactive"}</span></td>
<td><span class="badge ${statusClass}">
${statusIcon} ${statusText}</span></td>
<td>${formatDate(p.createdat)}</td>
<td>
<button class="btn btn-sm btn-info" onclick="editProject(${p.id})">
<button class="btn btn-sm btn-info" onclick="editProject('${escapeHtml(
String(p.id)
)}')">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-sm btn-danger" onclick="deleteProject(${
p.id
}, '${escapeHtml(p.title)}')">
<button class="btn btn-sm btn-danger" onclick="deleteProject('${escapeHtml(
String(p.id)
)}', '${escapeHtml(p.title).replace(/'/g, "&#39;")}')">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>`
)
</tr>`;
})
.join("");
}
@@ -85,6 +202,17 @@ function showCreateProject() {
document.getElementById("projectForm").reset();
document.getElementById("projectId").value = "";
document.getElementById("projectActive").checked = true;
// Clear Quill editor
if (quillEditor) {
quillEditor.setContents([]);
}
// Clear images
portfolioImages = [];
renderPortfolioImages();
resetModalSize();
projectModal.show();
}
@@ -100,10 +228,27 @@ async function editProject(id) {
"Edit Portfolio Project";
document.getElementById("projectId").value = project.id;
document.getElementById("projectTitle").value = project.title;
document.getElementById("projectDescription").value =
project.description || "";
// Set Quill editor content
if (quillEditor && project.description) {
quillEditor.root.innerHTML = project.description;
}
document.getElementById("projectCategory").value = project.category || "";
document.getElementById("projectActive").checked = project.isactive;
// Load images if available (imageurl field or parse from description)
portfolioImages = [];
if (project.imageurl) {
// If single image URL exists
portfolioImages.push({
url: project.imageurl,
filename: project.imageurl.split("/").pop(),
});
}
renderPortfolioImages();
resetModalSize();
projectModal.show();
}
} catch (error) {
@@ -114,15 +259,21 @@ async function editProject(id) {
async function saveProject() {
const id = document.getElementById("projectId").value;
// Get description from Quill editor
const description = quillEditor.root.innerHTML;
const formData = {
title: document.getElementById("projectTitle").value,
description: document.getElementById("projectDescription").value,
description: description,
category: document.getElementById("projectCategory").value,
isactive: document.getElementById("projectActive").checked,
imageurl: portfolioImages.length > 0 ? portfolioImages[0].url : null,
images: portfolioImages.map((img) => img.url),
};
if (!formData.title || !formData.description) {
showError("Please fill in all required fields");
showError("Please fill in all required fields (Title and Description)");
return;
}
@@ -141,7 +292,9 @@ async function saveProject() {
const data = await response.json();
if (data.success) {
showSuccess(
id ? "Project updated successfully" : "Project created successfully"
id
? "Project updated successfully! 🎉"
: "Project created successfully! 🎉"
);
projectModal.hide();
loadProjects();
@@ -174,18 +327,6 @@ async function deleteProject(id, name) {
}
}
async function logout() {
try {
const response = await fetch("/api/admin/logout", {
method: "POST",
credentials: "include",
});
if (response.ok) window.location.href = "/admin/login.html";
} catch (error) {
console.error("Logout failed:", error);
}
}
function escapeHtml(text) {
const map = {
"&": "&amp;",
@@ -205,9 +346,213 @@ function formatDate(dateString) {
});
}
// Render portfolio images gallery
function renderPortfolioImages() {
const gallery = document.getElementById("portfolioImagesGallery");
if (!gallery) return;
if (portfolioImages.length === 0) {
gallery.innerHTML = `
<div class="text-muted small">
No images added yet. Click above to add images.
</div>
`;
return;
}
gallery.innerHTML = portfolioImages
.map(
(img, index) => `
<div class="position-relative" style="width: 100px; height: 100px;">
<img
src="${img.url}"
alt="${img.filename}"
class="img-thumbnail w-100 h-100 object-fit-cover"
title="${img.filename}"
/>
<button
type="button"
class="btn btn-sm btn-danger position-absolute top-0 end-0 m-1 p-1"
onclick="removePortfolioImage(${index})"
style="line-height: 1; width: 24px; height: 24px; font-size: 12px;"
>
<i class="bi bi-x"></i>
</button>
</div>
`
)
.join("");
}
// Remove portfolio image
function removePortfolioImage(index) {
portfolioImages.splice(index, 1);
renderPortfolioImages();
}
// Media Library Integration
function openMediaLibrary(purpose) {
currentMediaPicker = { purpose };
// Create backdrop
const backdrop = document.createElement("div");
backdrop.id = "mediaLibraryBackdrop";
backdrop.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
`;
// Create modal
const modal = document.createElement("div");
modal.style.cssText = `
width: 90%;
height: 90%;
background: white;
border-radius: 12px;
overflow: hidden;
position: relative;
`;
// Create close button
const closeBtn = document.createElement("button");
closeBtn.innerHTML = "×";
closeBtn.style.cssText = `
position: absolute;
top: 10px;
right: 10px;
z-index: 10000;
background: #dc3545;
color: white;
border: none;
width: 40px;
height: 40px;
border-radius: 50%;
cursor: pointer;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
`;
closeBtn.onclick = closeMediaLibrary;
// Create iframe
const iframe = document.createElement("iframe");
iframe.id = "mediaLibraryFrame";
iframe.src = "/admin/media-library.html?selectMode=true";
iframe.style.cssText = `
width: 100%;
height: 100%;
border: none;
`;
modal.appendChild(closeBtn);
modal.appendChild(iframe);
backdrop.appendChild(modal);
document.body.appendChild(backdrop);
// Close on backdrop click
backdrop.onclick = function (e) {
if (e.target === backdrop) {
closeMediaLibrary();
}
};
}
function closeMediaLibrary() {
const backdrop = document.getElementById("mediaLibraryBackdrop");
if (backdrop) {
backdrop.remove();
}
currentMediaPicker = null;
}
function handleMediaSelection(media) {
if (!currentMediaPicker) return;
if (currentMediaPicker.purpose === "portfolioImages") {
// Handle multiple images
const mediaArray = Array.isArray(media) ? media : [media];
// Add all selected images to portfolio images array
mediaArray.forEach((item) => {
// Check if image already exists
if (!portfolioImages.find((img) => img.url === item.url)) {
portfolioImages.push({
url: item.url,
filename: item.filename || item.url.split("/").pop(),
});
}
});
renderPortfolioImages();
showSuccess(`${mediaArray.length} image(s) added to portfolio gallery`);
}
closeMediaLibrary();
}
// Toast Notification System
function showSuccess(message) {
alert(message);
showToast(message, "success");
}
function showError(message) {
alert("Error: " + message);
showToast(message, "error");
}
function showToast(message, type = "info") {
// Create toast container if it doesn't exist
let container = document.getElementById("toastContainer");
if (!container) {
container = document.createElement("div");
container.id = "toastContainer";
container.className = "toast-container";
document.body.appendChild(container);
}
// Create toast element
const toast = document.createElement("div");
toast.className = `toast toast-${type} toast-show`;
// Set icon based on type
let icon = "";
if (type === "success") {
icon = '<i class="bi bi-check-circle-fill"></i>';
} else if (type === "error") {
icon = '<i class="bi bi-exclamation-circle-fill"></i>';
} else if (type === "info") {
icon = '<i class="bi bi-info-circle-fill"></i>';
} else if (type === "warning") {
icon = '<i class="bi bi-exclamation-triangle-fill"></i>';
}
toast.innerHTML = `
<div class="toast-icon">${icon}</div>
<div class="toast-message">${escapeHtml(message)}</div>
<button class="toast-close" onclick="this.parentElement.remove()">
<i class="bi bi-x"></i>
</button>
`;
container.appendChild(toast);
// Auto remove after 4 seconds
setTimeout(() => {
toast.classList.remove("toast-show");
toast.classList.add("toast-hide");
setTimeout(() => {
if (toast.parentElement) {
toast.remove();
}
}, 300);
}, 4000);
}