709 lines
36 KiB
HTML
Executable File
709 lines
36 KiB
HTML
Executable File
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="robots" content="noindex, nofollow, noarchive">
|
|
<title>Portfolio Management - MSPE Admin</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha384-t1nt8BQoYMLFN5p42tRAtuAAFQaCQODekUVeKKZrEnEyp4H2R0RHFz0KWpmj7i8g" crossorigin="anonymous">
|
|
<link rel="stylesheet" href="/admin/css/admin.css">
|
|
</head>
|
|
<body>
|
|
<div class="admin-wrapper">
|
|
<!-- Sidebar -->
|
|
<aside class="sidebar" id="sidebar">
|
|
<div class="sidebar-header">
|
|
<a href="/admin/dashboard.html" class="sidebar-logo">
|
|
<i class="fas fa-cube"></i>
|
|
<span>MSPE</span>
|
|
</a>
|
|
<button class="sidebar-toggle" id="sidebar-toggle">
|
|
<i class="fas fa-bars"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<nav class="sidebar-nav">
|
|
<div class="nav-section">
|
|
<span class="nav-section-title">Main</span>
|
|
<ul class="nav-menu">
|
|
<li class="nav-item">
|
|
<a href="/admin/dashboard.html" class="nav-link">
|
|
<i class="fas fa-th-large"></i>
|
|
<span>Dashboard</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="nav-section">
|
|
<span class="nav-section-title">Content</span>
|
|
<ul class="nav-menu">
|
|
<li class="nav-item">
|
|
<a href="/admin/pages.html" class="nav-link">
|
|
<i class="fas fa-file-alt"></i>
|
|
<span>Pages</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/services.html" class="nav-link">
|
|
<i class="fas fa-concierge-bell"></i>
|
|
<span>Services</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item active">
|
|
<a href="/admin/portfolio.html" class="nav-link">
|
|
<i class="fas fa-briefcase"></i>
|
|
<span>Portfolio</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/news.html" class="nav-link">
|
|
<i class="fas fa-newspaper"></i>
|
|
<span>News & Events</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/testimonials.html" class="nav-link">
|
|
<i class="fas fa-quote-right"></i>
|
|
<span>Testimonials</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/team.html" class="nav-link">
|
|
<i class="fas fa-users"></i>
|
|
<span>Team</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="nav-section">
|
|
<span class="nav-section-title">Media</span>
|
|
<ul class="nav-menu">
|
|
<li class="nav-item">
|
|
<a href="/admin/media.html" class="nav-link">
|
|
<i class="fas fa-images"></i>
|
|
<span>Media Library</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="nav-section">
|
|
<span class="nav-section-title">Inquiries</span>
|
|
<ul class="nav-menu">
|
|
<li class="nav-item">
|
|
<a href="/admin/messages.html" class="nav-link">
|
|
<i class="fas fa-envelope"></i>
|
|
<span>Messages</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/subscribers.html" class="nav-link">
|
|
<i class="fas fa-bell"></i>
|
|
<span>Subscribers</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/bookings.html" class="nav-link">
|
|
<i class="fas fa-calendar-check"></i>
|
|
<span>Bookings</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="nav-section">
|
|
<span class="nav-section-title">Settings</span>
|
|
<ul class="nav-menu">
|
|
<li class="nav-item">
|
|
<a href="/admin/settings.html" class="nav-link">
|
|
<i class="fas fa-cog"></i>
|
|
<span>Site Settings</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/users.html" class="nav-link">
|
|
<i class="fas fa-user-shield"></i>
|
|
<span>Admin Users</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="sidebar-footer">
|
|
<a href="/index.html" class="view-site-btn" target="_blank">
|
|
<i class="fas fa-external-link-alt"></i>
|
|
<span>View Website</span>
|
|
</a>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Main Content -->
|
|
<main class="main-content">
|
|
<!-- Header -->
|
|
<header class="admin-header">
|
|
<div class="header-left">
|
|
<button class="mobile-toggle" id="mobile-toggle">
|
|
<i class="fas fa-bars"></i>
|
|
</button>
|
|
<h1 class="page-title">Portfolio Management</h1>
|
|
</div>
|
|
|
|
<div class="header-right">
|
|
<div class="header-user">
|
|
<div class="user-avatar">
|
|
<img src="https://ui-avatars.com/api/?name=Admin&background=0ea5e9&color=fff" alt="Admin">
|
|
</div>
|
|
<div class="user-info">
|
|
<span class="user-name">Admin</span>
|
|
<span class="user-role">Administrator</span>
|
|
</div>
|
|
<div class="user-dropdown">
|
|
<button class="dropdown-toggle">
|
|
<i class="fas fa-chevron-down"></i>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a href="/admin/settings.html"><i class="fas fa-cog"></i> Settings</a></li>
|
|
<li class="divider"></li>
|
|
<li><a href="#" id="logout-btn"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Content -->
|
|
<div class="content-wrapper">
|
|
<!-- List View -->
|
|
<div id="list-view">
|
|
<div class="content-header">
|
|
<div class="content-filters">
|
|
<div class="filter-group">
|
|
<select id="category-filter">
|
|
<option value="">All Categories</option>
|
|
<option value="it-support">IT Support</option>
|
|
<option value="cybersecurity">Cybersecurity</option>
|
|
<option value="cloud">Cloud Services</option>
|
|
<option value="consulting">Consulting</option>
|
|
</select>
|
|
</div>
|
|
<div class="filter-group">
|
|
<select id="status-filter">
|
|
<option value="">All Status</option>
|
|
<option value="published">Published</option>
|
|
<option value="draft">Draft</option>
|
|
<option value="featured">Featured</option>
|
|
</select>
|
|
</div>
|
|
<div class="search-box">
|
|
<i class="fas fa-search"></i>
|
|
<input type="text" id="search-input" placeholder="Search projects...">
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-primary" id="add-new-btn">
|
|
<i class="fas fa-plus"></i> Add Project
|
|
</button>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="portfolio-grid" id="portfolio-grid">
|
|
<div style="grid-column:1/-1;text-align:center;padding:3rem;color:var(--gray-400);">
|
|
<i class="fas fa-spinner fa-spin fa-2x"></i>
|
|
<p style="margin-top:1rem;">Loading projects...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Editor View -->
|
|
<div id="editor-view" style="display: none;">
|
|
<div class="content-header">
|
|
<button class="btn btn-outline" id="back-to-list">
|
|
<i class="fas fa-arrow-left"></i> Back to List
|
|
</button>
|
|
<div class="editor-actions">
|
|
<button class="btn btn-outline" id="save-draft-btn">Save Draft</button>
|
|
<button class="btn btn-primary" id="publish-btn">Publish</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="editor-grid">
|
|
<div class="editor-main">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="form-group">
|
|
<label for="project-title">Project Title</label>
|
|
<input type="text" id="project-title" class="form-control" placeholder="Enter project title">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="project-description">Short Description</label>
|
|
<textarea id="project-description" class="form-control" rows="3" placeholder="Brief project description..."></textarea>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="project-content">Full Description</label>
|
|
<textarea id="project-content" class="form-control editor-textarea" rows="10" placeholder="Detailed project description..."></textarea>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="project-client">Client Name</label>
|
|
<input type="text" id="project-client" class="form-control" placeholder="Client company name">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="project-url">Project URL</label>
|
|
<input type="url" id="project-url" class="form-control" placeholder="https://...">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Technologies Used</label>
|
|
<input type="text" id="project-technologies" class="form-control" placeholder="Azure, AWS, Docker, Kubernetes...">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Project Results</label>
|
|
<div class="results-grid">
|
|
<div class="result-item">
|
|
<input type="text" id="result-metric-0" placeholder="Metric (e.g., 99.9%)" class="form-control">
|
|
<input type="text" id="result-label-0" placeholder="Label (e.g., Uptime)" class="form-control">
|
|
</div>
|
|
<div class="result-item">
|
|
<input type="text" id="result-metric-1" placeholder="Metric (e.g., 50%)" class="form-control">
|
|
<input type="text" id="result-label-1" placeholder="Label (e.g., Cost Reduction)" class="form-control">
|
|
</div>
|
|
<div class="result-item">
|
|
<input type="text" id="result-metric-2" placeholder="Metric" class="form-control">
|
|
<input type="text" id="result-label-2" placeholder="Label" class="form-control">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="editor-sidebar">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>Project Settings</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="form-group">
|
|
<label for="project-category">Category</label>
|
|
<select id="project-category" class="form-control">
|
|
<option value="">Select category</option>
|
|
<option value="it-support">IT Support</option>
|
|
<option value="cybersecurity">Cybersecurity</option>
|
|
<option value="cloud">Cloud Services</option>
|
|
<option value="consulting">Consulting</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="project-status">Status</label>
|
|
<select id="project-status" class="form-control">
|
|
<option value="draft">Draft</option>
|
|
<option value="published">Published</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="checkbox-wrapper">
|
|
<input type="checkbox" id="project-featured">
|
|
<span>Featured Project</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="project-date">Completion Date</label>
|
|
<input type="date" id="project-date" class="form-control">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>Featured Image</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="image-upload-area" id="image-upload">
|
|
<i class="fas fa-cloud-upload-alt"></i>
|
|
<p>Click or drag to upload</p>
|
|
<span>PNG, JPG up to 5MB</span>
|
|
</div>
|
|
<input type="file" id="image-input" accept="image/*" hidden>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>Gallery Images</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="gallery-upload-area" id="gallery-upload">
|
|
<i class="fas fa-images"></i>
|
|
<p>Add gallery images</p>
|
|
</div>
|
|
<input type="file" id="gallery-input" accept="image/*" multiple hidden>
|
|
<div class="gallery-preview" id="gallery-preview"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<script src="/admin/js/admin.js"></script>
|
|
<script>
|
|
let allProjects = [];
|
|
let galleryFiles = []; // new File objects queued for upload
|
|
let existingGallery = []; // gallery URLs already saved in DB
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadProjects();
|
|
setupEventListeners();
|
|
|
|
// Check URL params
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const editId = urlParams.get('edit');
|
|
const action = urlParams.get('action');
|
|
|
|
if (editId) {
|
|
editProject(editId);
|
|
} else if (action === 'add') {
|
|
showEditor();
|
|
}
|
|
});
|
|
|
|
function setupEventListeners() {
|
|
document.getElementById('add-new-btn').addEventListener('click', showEditor);
|
|
document.getElementById('back-to-list').addEventListener('click', showList);
|
|
document.getElementById('publish-btn').addEventListener('click', () => saveProject('published'));
|
|
document.getElementById('save-draft-btn').addEventListener('click', () => saveProject('draft'));
|
|
|
|
// Filters
|
|
document.getElementById('category-filter').addEventListener('change', filterProjects);
|
|
document.getElementById('status-filter').addEventListener('change', filterProjects);
|
|
document.getElementById('search-input').addEventListener('input', MSPE.debounce(filterProjects, 300));
|
|
|
|
// Image upload
|
|
const imageUpload = document.getElementById('image-upload');
|
|
const imageInput = document.getElementById('image-input');
|
|
|
|
imageUpload.addEventListener('click', () => imageInput.click());
|
|
imageInput.addEventListener('change', handleImageSelect);
|
|
|
|
// Gallery upload
|
|
const galleryUpload = document.getElementById('gallery-upload');
|
|
const galleryInput = document.getElementById('gallery-input');
|
|
galleryUpload.addEventListener('click', () => galleryInput.click());
|
|
galleryInput.addEventListener('change', handleGallerySelect);
|
|
}
|
|
|
|
async function loadProjects() {
|
|
try {
|
|
const response = await MSPE.API.get('portfolio.php');
|
|
if (response && response.success) {
|
|
allProjects = response.data;
|
|
renderProjects(allProjects);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load projects:', error);
|
|
document.getElementById('portfolio-grid').innerHTML = `
|
|
<div class="empty-state">
|
|
<i class="fas fa-exclamation-circle"></i>
|
|
<p>Failed to load projects</p>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
function renderProjects(projects) {
|
|
const container = document.getElementById('portfolio-grid');
|
|
|
|
if (projects.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="empty-state">
|
|
<i class="fas fa-briefcase"></i>
|
|
<p>No projects found</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = projects.map(project => `
|
|
<div class="portfolio-admin-card" data-id="${project.id}">
|
|
<div class="portfolio-admin-image">
|
|
<img src="${project.image || 'https://via.placeholder.com/400x300'}" alt="${project.title}">
|
|
<span class="portfolio-status ${project.status}">${project.status}</span>
|
|
${project.featured ? '<span class="portfolio-featured"><i class="fas fa-star"></i></span>' : ''}
|
|
</div>
|
|
<div class="portfolio-admin-content">
|
|
<h3>${project.title}</h3>
|
|
<p class="portfolio-category">${project.category}</p>
|
|
<p class="portfolio-client">Client: ${project.client || 'N/A'}</p>
|
|
<div class="portfolio-admin-actions">
|
|
<button class="btn btn-sm btn-outline" onclick="editProject('${project.id}')">
|
|
<i class="fas fa-edit"></i> Edit
|
|
</button>
|
|
<button class="btn btn-sm btn-outline btn-danger" onclick="deleteProject('${project.id}')">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function filterProjects() {
|
|
const category = document.getElementById('category-filter').value;
|
|
const status = document.getElementById('status-filter').value;
|
|
const search = document.getElementById('search-input').value.toLowerCase();
|
|
|
|
let filtered = allProjects;
|
|
|
|
if (category) filtered = filtered.filter(p => p.category === category);
|
|
|
|
if (status) {
|
|
if (status === 'featured') {
|
|
filtered = filtered.filter(p => p.featured);
|
|
} else {
|
|
filtered = filtered.filter(p => p.status === status);
|
|
}
|
|
}
|
|
|
|
if (search) {
|
|
filtered = filtered.filter(p =>
|
|
p.title.toLowerCase().includes(search) ||
|
|
(p.client && p.client.toLowerCase().includes(search))
|
|
);
|
|
}
|
|
|
|
renderProjects(filtered);
|
|
}
|
|
|
|
function showEditor() {
|
|
document.getElementById('list-view').style.display = 'none';
|
|
document.getElementById('editor-view').style.display = 'block';
|
|
resetForm();
|
|
}
|
|
|
|
function showList() {
|
|
document.getElementById('editor-view').style.display = 'none';
|
|
document.getElementById('list-view').style.display = 'block';
|
|
|
|
const url = new URL(window.location);
|
|
url.searchParams.delete('edit');
|
|
url.searchParams.delete('action');
|
|
window.history.pushState({}, '', url);
|
|
}
|
|
|
|
function resetForm() {
|
|
// Inputs
|
|
['project-title', 'project-description', 'project-content', 'project-client', 'project-url', 'project-technologies', 'project-date'].forEach(id => {
|
|
document.getElementById(id).value = '';
|
|
});
|
|
document.getElementById('project-category').value = '';
|
|
document.getElementById('project-status').value = 'draft';
|
|
document.getElementById('project-featured').checked = false;
|
|
|
|
// Image reset
|
|
const uploadArea = document.getElementById('image-upload');
|
|
uploadArea.innerHTML = `
|
|
<i class="fas fa-cloud-upload-alt"></i>
|
|
<p>Click or drag to upload</p>
|
|
<span>PNG, JPG up to 5MB</span>
|
|
`;
|
|
uploadArea.style.backgroundImage = '';
|
|
document.getElementById('image-input').value = '';
|
|
|
|
// Clear results grid
|
|
[0, 1, 2].forEach(i => {
|
|
document.getElementById(`result-metric-${i}`).value = '';
|
|
document.getElementById(`result-label-${i}`).value = '';
|
|
});
|
|
|
|
// Clear gallery
|
|
galleryFiles = [];
|
|
existingGallery = [];
|
|
document.getElementById('gallery-preview').innerHTML = '';
|
|
document.getElementById('gallery-input').value = '';
|
|
|
|
// Remove ID if any
|
|
delete document.getElementById('editor-view').dataset.id;
|
|
}
|
|
|
|
async function editProject(id) {
|
|
try {
|
|
const response = await MSPE.API.get(`portfolio.php?id=${id}`);
|
|
if (response && response.success) {
|
|
const project = response.data;
|
|
|
|
showEditor();
|
|
document.getElementById('editor-view').dataset.id = project.id;
|
|
|
|
document.getElementById('project-title').value = project.title;
|
|
document.getElementById('project-description').value = project.description || '';
|
|
document.getElementById('project-content').value = project.content || '';
|
|
document.getElementById('project-client').value = project.client || '';
|
|
document.getElementById('project-url').value = project.url || '';
|
|
document.getElementById('project-technologies').value = project.technologies || '';
|
|
document.getElementById('project-date').value = project.completion_date || '';
|
|
document.getElementById('project-category').value = project.category;
|
|
document.getElementById('project-status').value = project.status;
|
|
document.getElementById('project-featured').checked = project.featured == 1; // Loose comparison for string '1'
|
|
|
|
if (project.image) {
|
|
const uploadArea = document.getElementById('image-upload');
|
|
uploadArea.innerHTML = '';
|
|
uploadArea.style.backgroundImage = `url(${project.image})`;
|
|
uploadArea.style.backgroundSize = 'cover';
|
|
uploadArea.style.backgroundPosition = 'center';
|
|
}
|
|
|
|
// Restore results grid
|
|
const rawResults = project.results;
|
|
const results = Array.isArray(rawResults) ? rawResults :
|
|
(rawResults ? JSON.parse(rawResults) : []);
|
|
[0, 1, 2].forEach(i => {
|
|
const r = results[i] || {};
|
|
document.getElementById(`result-metric-${i}`).value = r.metric || '';
|
|
document.getElementById(`result-label-${i}`).value = r.label || '';
|
|
});
|
|
|
|
// Restore gallery
|
|
existingGallery = Array.isArray(project.gallery) ? [...project.gallery] : [];
|
|
galleryFiles = [];
|
|
const galleryPreview = document.getElementById('gallery-preview');
|
|
galleryPreview.innerHTML = existingGallery.map(url =>
|
|
`<div class="gallery-preview-item" data-url="${url}">
|
|
<img src="/${url}" alt="Gallery image">
|
|
<button type="button" class="gallery-remove-btn" onclick="removeGalleryItem(this)"><i class="fas fa-times"></i></button>
|
|
</div>`
|
|
).join('');
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load project:', error);
|
|
MSPE.showNotification('Failed to load project details', 'error');
|
|
}
|
|
}
|
|
|
|
function handleImageSelect(e) {
|
|
if (this.files && this.files[0]) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
const uploadArea = document.getElementById('image-upload');
|
|
uploadArea.innerHTML = '';
|
|
uploadArea.style.backgroundImage = `url(${e.target.result})`;
|
|
uploadArea.style.backgroundSize = 'cover';
|
|
uploadArea.style.backgroundPosition = 'center';
|
|
};
|
|
reader.readAsDataURL(this.files[0]);
|
|
}
|
|
}
|
|
|
|
function handleGallerySelect() {
|
|
const files = Array.from(this.files);
|
|
if (!files.length) return;
|
|
const preview = document.getElementById('gallery-preview');
|
|
files.forEach(file => {
|
|
const idx = galleryFiles.length;
|
|
galleryFiles.push(file);
|
|
const reader = new FileReader();
|
|
reader.onload = function(ev) {
|
|
const item = document.createElement('div');
|
|
item.className = 'gallery-preview-item';
|
|
item.dataset.galleryIdx = idx;
|
|
item.innerHTML = `<img src="${ev.target.result}" alt=""><button type="button" class="gallery-remove-btn" onclick="removeGalleryItem(this)"><i class="fas fa-times"></i></button>`;
|
|
preview.appendChild(item);
|
|
};
|
|
reader.readAsDataURL(file);
|
|
});
|
|
this.value = ''; // reset so same files can be re-selected
|
|
}
|
|
|
|
function removeGalleryItem(btn) {
|
|
const item = btn.closest('.gallery-preview-item');
|
|
const idx = parseInt(item.dataset.galleryIdx);
|
|
if (!isNaN(idx) && galleryFiles[idx]) galleryFiles[idx] = null;
|
|
const url = item.dataset.url;
|
|
if (url) existingGallery = existingGallery.filter(u => u !== url);
|
|
item.remove();
|
|
}
|
|
|
|
async function saveProject(status) {
|
|
const id = document.getElementById('editor-view').dataset.id;
|
|
const formData = new FormData();
|
|
|
|
if (id) formData.append('id', id);
|
|
|
|
formData.append('title', document.getElementById('project-title').value);
|
|
formData.append('description', document.getElementById('project-description').value);
|
|
formData.append('content', document.getElementById('project-content').value);
|
|
formData.append('client', document.getElementById('project-client').value);
|
|
formData.append('url', document.getElementById('project-url').value);
|
|
formData.append('technologies', document.getElementById('project-technologies').value);
|
|
formData.append('completion_date', document.getElementById('project-date').value);
|
|
formData.append('category', document.getElementById('project-category').value);
|
|
formData.append('status', status); // Use passed status
|
|
formData.append('featured', document.getElementById('project-featured').checked ? 1 : 0);
|
|
|
|
const imageInput = document.getElementById('image-input');
|
|
if (imageInput.files[0]) {
|
|
formData.append('image', imageInput.files[0]);
|
|
}
|
|
|
|
// Serialize results grid
|
|
const results = [0, 1, 2].map(i => ({
|
|
metric: document.getElementById(`result-metric-${i}`)?.value?.trim() || '',
|
|
label: document.getElementById(`result-label-${i}`)?.value?.trim() || ''
|
|
})).filter(r => r.metric || r.label);
|
|
formData.append('results', JSON.stringify(results));
|
|
|
|
// Gallery — new uploads + existing to keep
|
|
galleryFiles.forEach(file => { if (file) formData.append('gallery_images[]', file); });
|
|
formData.append('gallery_keep', JSON.stringify(existingGallery));
|
|
|
|
try {
|
|
const result = await MSPE.API.upload('portfolio.php', formData);
|
|
|
|
if (result && result.success) {
|
|
MSPE.showNotification('Project saved successfully', 'success');
|
|
loadProjects();
|
|
showList();
|
|
} else {
|
|
MSPE.showNotification(result?.message || 'Failed to save project', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Save error:', error);
|
|
MSPE.showNotification('An error occurred while saving', 'error');
|
|
}
|
|
}
|
|
|
|
async function deleteProject(id) {
|
|
if (!await MSPE.confirmDialog('Are you sure you want to delete this project?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await MSPE.API.delete(`portfolio.php?id=${id}`);
|
|
if (response && response.success) {
|
|
MSPE.showNotification('Project deleted successfully', 'success');
|
|
loadProjects();
|
|
}
|
|
} catch (error) {
|
|
console.error('Delete error:', error);
|
|
MSPE.showNotification('Failed to delete project', 'error');
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|