// Portfolio Management JavaScript 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(); } }); const urlParams = new URLSearchParams(window.location.search); if (urlParams.get("action") === "create") { showCreateProject(); } }); 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) { console.error("Failed to load projects:", error); } } function renderProjects(projects) { const tbody = document.getElementById("projectsTableBody"); if (projects.length === 0) { tbody.innerHTML = `

No projects found

`; return; } tbody.innerHTML = projects .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 ` ${escapeHtml(String(p.id))} ${escapeHtml(p.title)} ${escapeHtml((p.description || "").substring(0, 50))}... ${p.category || "-"} ${statusIcon} ${statusText} ${formatDate(p.createdat)} `; }) .join(""); } function filterProjects() { const searchTerm = document.getElementById("searchInput").value.toLowerCase(); const filtered = projectsData.filter((p) => p.title.toLowerCase().includes(searchTerm) ); renderProjects(filtered); } function showCreateProject() { document.getElementById("modalTitle").textContent = "Add Portfolio Project"; 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(); } async function editProject(id) { try { const response = await fetch(`/api/admin/portfolio/projects/${id}`, { credentials: "include", }); const data = await response.json(); if (data.success) { const project = data.project; document.getElementById("modalTitle").textContent = "Edit Portfolio Project"; document.getElementById("projectId").value = project.id; document.getElementById("projectTitle").value = project.title; // 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) { console.error("Failed to load project:", error); showError("Failed to load project details"); } } 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: 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 (Title and Description)"); return; } try { const url = id ? `/api/admin/portfolio/projects/${id}` : "/api/admin/portfolio/projects"; const method = id ? "PUT" : "POST"; const response = await fetch(url, { method: method, headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(formData), }); const data = await response.json(); if (data.success) { showSuccess( id ? "Project updated successfully! 🎉" : "Project created successfully! 🎉" ); projectModal.hide(); loadProjects(); } else { showError(data.message || "Failed to save project"); } } catch (error) { console.error("Failed to save project:", error); showError("Failed to save project"); } } async function deleteProject(id, name) { if (!confirm(`Are you sure you want to delete "${name}"?`)) return; try { const response = await fetch(`/api/admin/portfolio/projects/${id}`, { method: "DELETE", credentials: "include", }); const data = await response.json(); if (data.success) { showSuccess("Project deleted successfully"); loadProjects(); } else { showError(data.message || "Failed to delete project"); } } catch (error) { console.error("Failed to delete project:", error); showError("Failed to delete project"); } } function escapeHtml(text) { const map = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", }; return text.replace(/[&<>"']/g, (m) => map[m]); } function formatDate(dateString) { return new Date(dateString).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", }); } // Render portfolio images gallery function renderPortfolioImages() { const gallery = document.getElementById("portfolioImagesGallery"); if (!gallery) return; if (portfolioImages.length === 0) { gallery.innerHTML = `
No images added yet. Click above to add images.
`; return; } gallery.innerHTML = portfolioImages .map( (img, index) => `
${img.filename}
` ) .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) { showToast(message, "success"); } function showError(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 = ''; } else if (type === "error") { icon = ''; } else if (type === "info") { icon = ''; } else if (type === "warning") { icon = ''; } toast.innerHTML = `
${icon}
${escapeHtml(message)}
`; 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); }