Files
uldp-degree-mangement-system/views/admin/aboutUs/index.ejs

967 lines
53 KiB
Plaintext

<div class="container">
<div class="d-flex justify-content-between align-items-center mt-4 mb-4">
<div>
<h1 class="h3 mb-0" style="color: var(--primary-dark);">
<%= title %>
</h1>
<p class="text-muted mb-0">Edit content displayed on About Us page</p>
</div>
<div>
<a href="<%= frontendUrl %>/about-us/" class="btn btn-outline-primary" target="_blank">
<i class="fas fa-external-link-alt me-2"></i>View About Us Page
</a>
</div>
</div>
<div class="row">
<div class="col-12">
<form method="POST" class="content-with-fixed-buttons" id="aboutUsForm"
action="/admin/about-us/update">
<!-- Hidden inputs for JSON data -->
<input type="hidden" name="heroJson" id="heroJson">
<input type="hidden" name="introJson" id="introJson">
<input type="hidden" name="missionJson" id="missionJson">
<input type="hidden" name="featuresJson" id="featuresJson">
<input type="hidden" name="newsJson" id="newsJson">
<input type="hidden" name="activeTab" id="activeTabInput" value="<%= locals.activeTab || 'hero' %>">
<!-- Navigation Tabs -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white border-bottom">
<!-- Tab Menu -->
<ul class="nav nav-tabs card-header-tabs" id="aboutUsTabs" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link <%= (locals.activeTab === 'hero' || !locals.activeTab) ? 'active' : '' %>"
id="hero-tab" data-bs-toggle="tab" href="#hero" role="tab"
aria-selected="true"><i class="fas fa-image me-2"></i>Hero</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link <%= locals.activeTab === 'intro' ? 'active' : '' %>" id="intro-tab"
data-bs-toggle="tab" href="#intro" role="tab"
aria-selected="false"><i class="fas fa-info-circle me-2"></i>Intro</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link <%= locals.activeTab === 'mission' ? 'active' : '' %>" id="mission-tab"
data-bs-toggle="tab" href="#mission" role="tab"
aria-selected="false"><i class="fas fa-bullseye me-2"></i>Mission</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link <%= locals.activeTab === 'features' ? 'active' : '' %>" id="features-tab"
data-bs-toggle="tab" href="#features" role="tab"
aria-selected="false"><i class="fas fa-star me-2"></i>Features</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link <%= locals.activeTab === 'news' ? 'active' : '' %>" id="news-tab"
data-bs-toggle="tab" href="#news" role="tab"
aria-selected="false"><i class="fas fa-newspaper me-2"></i>News</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<!-- Hero Tab -->
<div class="tab-pane fade <%= (locals.activeTab === 'hero' || !locals.activeTab) ? 'show active' : '' %>" id="hero"
role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="fas fa-image me-2"></i>Hero Section</h6>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Title</label>
<input type="text" class="form-control" id="heroTitle" name="heroTitle"
value="<%= data.hero?.title || '' %>">
</div>
<div class="col-md-6">
<label class="form-label">Breadcrumb (comma separated)</label>
<input type="text" class="form-control" id="heroBreadcrumb"
value="<%= (data.hero?.breadcrumb || []).join(', ') %>">
</div>
<div class="col-md-12">
<label class="form-label">Background Image</label>
<div class="input-group">
<input type="text" class="form-control" id="heroBackgroundImage"
name="heroBackgroundImage" value="<%= data.hero?.backgroundImage || '' %>">
<button class="btn btn-outline-primary btn-upload-image" type="button"
data-target-input="heroBackgroundImage" data-image-type="about">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
<% if (data.hero?.backgroundImage) { %>
<img src="<%= data.hero.backgroundImage %>"
class="img-thumbnail uploaded-preview mt-2"
style="max-height: 200px;">
<% } %>
</div>
</div>
</div>
</div>
</div>
<!-- Intro Tab -->
<div class="tab-pane fade <%= locals.activeTab === 'intro' ? 'show active' : '' %>" id="intro"
role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="fas fa-info-circle me-2"></i>Introduction</h6>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Subheading</label>
<input type="text" class="form-control" id="introSubheading" name="introSubheading"
value="<%= data.intro?.subheading || '' %>">
</div>
<div class="col-md-6">
<label class="form-label">Heading</label>
<input type="text" class="form-control" id="introHeading" name="introHeading"
value="<%= data.intro?.heading || '' %>">
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="introDescription" name="introDescription"
rows="4"><%= data.intro?.description || '' %></textarea>
</div>
<div class="col-md-12">
<label class="form-label">Main Image</label>
<div class="input-group">
<input type="text" class="form-control" id="introImage" name="introImage"
value="<%= data.intro?.image || '' %>">
<button class="btn btn-outline-primary btn-upload-image" type="button"
data-target-input="introImage" data-image-type="about">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
<% if (data.intro?.image) { %>
<img src="<%= data.intro.image %>"
class="img-thumbnail uploaded-preview mt-2"
style="max-height: 200px;">
<% } %>
</div>
</div>
</div>
</div>
</div>
<!-- Mission Tab -->
<div class="tab-pane fade <%= locals.activeTab === 'mission' ? 'show active' : '' %>" id="mission" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="fas fa-bullseye me-2"></i>Mission Section</h6>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Subheading</label>
<input type="text" class="form-control" id="missionSubheading" value="<%= data.mission?.subheading || '' %>">
</div>
<div class="col-md-6">
<label class="form-label">Heading</label>
<input type="text" class="form-control" id="missionHeading" value="<%= data.mission?.heading || '' %>">
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="missionDescription" rows="3"><%= data.mission?.description || '' %></textarea>
</div>
</div>
<div class="row g-3 mt-2">
<div class="col-md-6">
<label class="form-label">CTA Button Label</label>
<input type="text" class="form-control" id="missionCtaLabel" value="<%= data.mission?.ctaButton?.label || '' %>">
</div>
<div class="col-md-6">
<label class="form-label">CTA Button Link</label>
<input type="text" class="form-control" id="missionCtaHref" value="<%= data.mission?.ctaButton?.href || '' %>">
</div>
</div>
<h6 class="mt-4 mb-3">Images</h6>
<div class="row g-3">
<% ['main', 'secondary', 'bgShape', 'planeShape', 'topShape', 'globeShape'].forEach(imgKey => { %>
<div class="col-md-4">
<label class="form-label"><%= imgKey.charAt(0).toUpperCase() + imgKey.slice(1) %></label>
<div class="input-group">
<input type="text" class="form-control" id="missionImg_<%= imgKey %>" value="<%= data.mission?.images?.[imgKey] || '' %>">
<button class="btn btn-outline-primary btn-upload-image btn-sm" type="button" data-target-input="missionImg_<%= imgKey %>" data-image-type="about">
<i class="fas fa-upload"></i>
</button>
</div>
</div>
<% }) %>
</div>
<div class="row mt-4">
<div class="col-md-6">
<div class="d-flex justify-content-between align-items-center mb-2">
<label class="form-label mb-0">Items (Icons & Labels)</label>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="addMissionItem()">
<i class="fas fa-plus me-1"></i>Add
</button>
</div>
<div id="missionItemsContainer"></div>
</div>
<div class="col-md-6">
<div class="d-flex justify-content-between align-items-center mb-2">
<label class="form-label mb-0">Features (List)</label>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="addMissionFeature()">
<i class="fas fa-plus me-1"></i>Add
</button>
</div>
<div id="missionFeaturesContainer"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Features Tab -->
<div class="tab-pane fade <%= locals.activeTab === 'features' ? 'show active' : '' %>" id="features" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="fas fa-star me-2"></i>Features Section</h6>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Subheading</label>
<input type="text" class="form-control" id="featuresSubheading" value="<%= data.features?.subheading || '' %>">
</div>
<div class="col-md-6">
<label class="form-label">Heading</label>
<input type="text" class="form-control" id="featuresHeading" value="<%= data.features?.heading || '' %>">
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="featuresDescription" rows="3"><%= data.features?.description || '' %></textarea>
</div>
<div class="col-md-6">
<label class="form-label">Background Image</label>
<div class="input-group">
<input type="text" class="form-control" id="featuresBgImage" value="<%= data.features?.backgroundImage || '' %>">
<button class="btn btn-outline-primary btn-upload-image" type="button" data-target-input="featuresBgImage" data-image-type="about">
<i class="fas fa-upload"></i>
</button>
</div>
</div>
<div class="col-md-6">
<label class="form-label">Side Image</label>
<div class="input-group">
<input type="text" class="form-control" id="featuresImage" value="<%= data.features?.image || '' %>">
<button class="btn btn-outline-primary btn-upload-image" type="button" data-target-input="featuresImage" data-image-type="about">
<i class="fas fa-upload"></i>
</button>
</div>
</div>
<div class="col-md-6">
<label class="form-label">CTA Button Label</label>
<input type="text" class="form-control" id="featuresCtaLabel" value="<%= data.features?.ctaButton?.label || '' %>">
</div>
<div class="col-md-6">
<label class="form-label">CTA Button Link</label>
<input type="text" class="form-control" id="featuresCtaHref" value="<%= data.features?.ctaButton?.href || '' %>">
</div>
</div>
<div class="mt-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Feature Items</h6>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="addFeatureItem()">
<i class="fas fa-plus me-1"></i>Add Item
</button>
</div>
<div id="featureItemsContainer"></div>
</div>
</div>
</div>
</div>
<!-- News Tab -->
<div class="tab-pane fade <%= locals.activeTab === 'news' ? 'show active' : '' %>" id="news" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h6 class="mb-0"><i class="fas fa-newspaper me-2"></i>News Section (Blog Preview)</h6>
<span class="badge bg-info text-dark">System will automatically fetch the 3 latest posts if no specific blog is selected.</span>
</div>
<div class="card-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label fw-medium">Subheading</label>
<input type="text" class="form-control" id="newsSubheading" value="<%= data.news?.subheading || '' %>" placeholder="e.g., Visa Tips & Guides">
</div>
<div class="col-md-6">
<label class="form-label fw-medium">Heading</label>
<input type="text" class="form-control" id="newsHeading" value="<%= data.news?.heading || '' %>" placeholder="e.g., Latest Insights & Updates">
</div>
<div class="col-md-6">
<label class="form-label fw-medium">CTA Button Label</label>
<input type="text" class="form-control" id="newsCtaLabel" value="<%= data.news?.ctaButton?.label || '' %>" placeholder="e.g., View All Articles">
</div>
<div class="col-md-6">
<label class="form-label fw-medium">CTA Button Link</label>
<input type="text" class="form-control" id="newsCtaHref" value="<%= data.news?.ctaButton?.href || '' %>" placeholder="/blog">
</div>
</div>
<div class="col-md-12 mt-4">
<label class="form-label fw-bold"><i class="fas fa-check-square me-2"></i>Select Featured Blogs (Direct from Blog Module)</label>
<p class="text-muted small mb-3">Select blog posts to display on About page. If none are selected, the system will use the 3 latest posts.</p>
<div class="row g-3 blog-selector-container" style="max-height: 400px; overflow-y: auto; border: 1px solid #eee; padding: 15px; border-radius: 8px;">
<% if (allBlogs && allBlogs.length > 0) { %>
<% allBlogs.forEach(blog => {
const isSelected = data.news?.selectedBlogIds && data.news.selectedBlogIds.some(id => id.toString() === blog._id.toString());
%>
<div class="col-md-4">
<div class="card h-100 blog-select-card <%= isSelected ? 'border-primary bg-light' : '' %>" onclick="toggleAboutBlogSelection(this, '<%= blog._id %>')" style="cursor: pointer; transition: all 0.2s;">
<div class="position-absolute top-0 end-0 m-2">
<div class="form-check">
<input class="form-check-input about-blog-checkbox" type="checkbox" value="<%= blog._id %>" <%= isSelected ? 'checked' : '' %> onclick="event.stopPropagation(); handleAboutCheckboxChange(this)">
</div>
</div>
<img src="<%= blog.featuredImage ? (blog.featuredImage.startsWith('http') ? blog.featuredImage : backendUrl + blog.featuredImage) : '/assets/img/placeholder.jpg' %>" class="card-img-top" style="height: 210px; object-fit: cover;">
<div class="card-body p-2">
<h6 class="card-title small fw-bold mb-1" title="<%= blog.title %>" style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; height: 2.6em; line-height: 1.3em;">
<%= blog.title %>
</h6>
<p class="card-text tiny text-muted mb-0">
<%= blog.publishedAt ? new Date(blog.publishedAt).toLocaleDateString('vi-VN') : '' %>
</p>
</div>
</div>
</div>
<% }) %>
<% } else { %>
<div class="col-12 text-center py-4">
<p class="text-muted">No published blogs found. Please create some blogs first.</p>
<a href="/admin/blog/create" class="btn btn-sm btn-outline-primary">Create Blog</a>
</div>
<% } %>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Fixed Bottom Buttons inside Card Footer -->
<div class="card-footer bg-light d-flex justify-content-end py-3 gap-2">
<button type="button" class="btn btn-outline-secondary px-4" onclick="resetForm()">
<i class="fas fa-undo me-2"></i>Reset
</button>
<button type="submit" class="btn btn-outline-primary px-4" id="submitBtn">
<i class="fas fa-save me-2"></i>Save Changes
</button>
</div>
</form>
</div>
</div>
</div>
<script>
let originalFormData = null;
document.addEventListener('DOMContentLoaded', function () {
originalFormData = <%- JSON.stringify(data) %>;
updateAllJsonInputs(originalFormData);
initializeFormHandlers();
});
function initializeFormHandlers() {
const form = document.getElementById('aboutUsForm');
form.addEventListener('submit', async function (e) {
e.preventDefault();
const submitBtn = document.getElementById('submitBtn');
const originalHtml = submitBtn.innerHTML;
try {
// Collect all data into a single object
updateJsonData();
// No more "Saving..." text or disabling button to keep UI "instant"
// submitBtn.disabled = true;
// submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Saving...';
// We'll send the individual section JSONs or construct one big one
const aboutData = {
hero: JSON.parse(document.getElementById('heroJson').value),
intro: JSON.parse(document.getElementById('introJson').value),
mission: JSON.parse(document.getElementById('missionJson').value),
features: JSON.parse(document.getElementById('featuresJson').value),
news: JSON.parse(document.getElementById('newsJson').value)
};
const response = await fetch('/api/about', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ aboutJson: JSON.stringify(aboutData) })
});
if (!response.ok) throw new Error('Update failed');
const result = await response.json();
if (result.success) {
showToast('Success', 'About Us updated successfully', 'success');
// Update the local state with returned data from server
// This ensures the UI is in sync with what was actually saved
if (result.data) {
originalFormData = result.data;
updateAllJsonInputs(originalFormData);
}
} else {
throw new Error(result.error || 'Failed to update');
}
} catch (error) {
console.error('Error:', error);
showToast('Error', error.message, 'error');
}
});
document.body.addEventListener('click', function (e) {
const uploadBtn = e.target.closest('.btn-upload-image');
if (uploadBtn) {
const targetInput = uploadBtn.dataset.targetInput;
const imageType = uploadBtn.dataset.imageType;
openImageUploader(targetInput, imageType);
}
});
// Tab change listener to keep track of active tab
const tabs = document.querySelectorAll('a[data-bs-toggle="tab"]');
tabs.forEach(tab => {
tab.addEventListener('shown.bs.tab', function (e) {
const targetId = e.target.getAttribute('href').replace('#', '');
const activeTabInput = document.getElementById('activeTabInput');
if (activeTabInput) {
activeTabInput.value = targetId;
}
});
});
}
function openImageUploader(targetInput, imageType) {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.style.display = 'none';
document.body.appendChild(fileInput);
function getPreviewDims(name) {
if (/hero/i.test(name)) return { h: '250px', w: '100%' };
if (/intro/i.test(name) || /mission/i.test(name) || /features/i.test(name)) return { h: '150px', w: '100%' };
return { h: '120px', w: '100%' };
}
fileInput.onchange = async function (e) {
const file = e.target.files[0];
if (!file) return;
try {
const formData = new FormData();
formData.append('image', file);
// Disable upload button during upload
const uploadBtn = document.querySelector(`[data-target-input="${targetInput}"]`);
const originalBtnHtml = uploadBtn ? uploadBtn.innerHTML : 'Upload';
if (uploadBtn) {
uploadBtn.disabled = true;
}
const response = await fetch(`/admin/upload/image?imageType=${imageType}`, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('Upload failed');
}
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Upload failed');
}
// Update input value
const input = document.getElementById(targetInput) || document.querySelector(`[name="${targetInput}"]`);
if (!input) {
throw new Error('Target input not found');
}
// Use absolute URL for preview when necessary
const previewUrl = (result.path && (result.path.startsWith('http://') || result.path.startsWith('https://'))) ? result.path : (window.location.origin + result.path);
input.value = result.path;
// Try to find an existing image preview inside the closest card or input group (prefer .uploaded-preview)
let card = input.closest('.card');
let previewImg = card ? card.querySelector('.uploaded-preview') : null;
if (!previewImg) {
// Look for a preview right after the input group
const parent = input.parentElement || input.closest('.input-group') || input.closest('div');
previewImg = parent ? parent.querySelector('.uploaded-preview') : null;
}
const dims = getPreviewDims(targetInput);
if (previewImg) {
previewImg.src = previewUrl;
previewImg.style.height = dims.h;
previewImg.style.width = dims.w;
} else {
// Create a preview image and attach it after the input group
const img = document.createElement('img');
img.src = previewUrl;
img.className = 'img-thumbnail uploaded-preview mt-2';
img.style.height = dims.h;
img.style.width = dims.w;
img.style.objectFit = 'cover';
img.alt = 'Image preview';
const parent = input.parentElement || input.closest('.input-group') || input.closest('div');
if (parent) parent.appendChild(img);
}
// Removed toast for silent upload
// Restore button state
if (uploadBtn) {
uploadBtn.disabled = false;
}
} catch (error) {
console.error('Upload error:', error);
showToast('Error', 'Failed to upload image: ' + error.message, 'error');
// Restore button state
const uploadBtn = document.querySelector(`[data-target-input="${targetInput}"]`);
if (uploadBtn) {
uploadBtn.disabled = false;
}
} finally {
document.body.removeChild(fileInput);
}
};
fileInput.click();
}
function resetForm() {
if (confirm('Are you sure you want to reset all changes?')) {
updateAllJsonInputs(originalFormData);
showToast('Reset', 'Form restored to last saved state', 'info');
}
}
function showError(message) {
const alertHtml = `
<div class="alert alert-danger alert-dismissible fade show" role="alert">
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
`;
document.querySelector('.container').insertAdjacentHTML('afterbegin', alertHtml);
}
// Show toast message
function showToast(title, message, type = 'info') {
const toast = document.createElement('div');
toast.className = `toast align-items-center text-white bg-${type === 'error' ? 'danger' : type} border-0`;
toast.setAttribute('role', 'alert');
toast.setAttribute('aria-live', 'assertive');
toast.setAttribute('aria-atomic', 'true');
toast.innerHTML = `
<div class="d-flex">
<div class="toast-body">
<strong>${title}:</strong> ${message}
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
`;
// Add toast to container
let container = document.querySelector('.toast-container');
if (!container) {
container = document.createElement('div');
container.className = 'toast-container position-fixed top-0 end-0 p-3';
document.body.appendChild(container);
}
container.appendChild(toast);
// Show toast
const bsToast = new bootstrap.Toast(toast, {
animation: true,
autohide: true,
delay: 3000
});
bsToast.show();
// Remove toast after hide
toast.addEventListener('hidden.bs.toast', () => {
toast.remove();
});
}
function updateAllJsonInputs(data) {
if (!data) return;
// 1. Hero
const hero = data.hero || {};
document.getElementById('heroJson').value = JSON.stringify(hero);
document.getElementById('heroTitle').value = hero.title || '';
document.getElementById('heroBreadcrumb').value = (hero.breadcrumb || []).join(', ');
document.getElementById('heroBackgroundImage').value = hero.backgroundImage || '';
updateImagePreview('heroBackgroundImage', hero.backgroundImage);
// 2. Intro
const intro = data.intro || {};
document.getElementById('introJson').value = JSON.stringify(intro);
document.getElementById('introSubheading').value = intro.subheading || '';
document.getElementById('introHeading').value = intro.heading || '';
document.getElementById('introDescription').value = intro.description || '';
document.getElementById('introImage').value = intro.image || '';
updateImagePreview('introImage', intro.image);
// 3. Mission
const mission = data.mission || {};
document.getElementById('missionJson').value = JSON.stringify(mission);
document.getElementById('missionSubheading').value = mission.subheading || '';
document.getElementById('missionHeading').value = mission.heading || '';
document.getElementById('missionDescription').value = mission.description || '';
document.getElementById('missionCtaLabel').value = mission.ctaButton?.label || '';
document.getElementById('missionCtaHref').value = mission.ctaButton?.href || '';
['main', 'secondary', 'bgShape', 'planeShape', 'topShape', 'globeShape'].forEach(k => {
const el = document.getElementById('missionImg_' + k);
const val = mission.images?.[k] || '';
if (el) {
el.value = val;
updateImagePreview('missionImg_' + k, val);
}
});
populateMissionItems(mission.items || []);
populateMissionFeatures(mission.features || []);
// 4. Features
const features = data.features || {};
document.getElementById('featuresJson').value = JSON.stringify(features);
document.getElementById('featuresSubheading').value = features.subheading || '';
document.getElementById('featuresHeading').value = features.heading || '';
document.getElementById('featuresDescription').value = features.description || '';
document.getElementById('featuresBgImage').value = features.backgroundImage || '';
document.getElementById('featuresImage').value = features.image || '';
document.getElementById('featuresCtaLabel').value = features.ctaButton?.label || '';
document.getElementById('featuresCtaHref').value = features.ctaButton?.href || '';
updateImagePreview('featuresBgImage', features.backgroundImage);
updateImagePreview('featuresImage', features.image);
populateFeatureItems(features.items || []);
// 5. News
const news = data.news || {};
document.getElementById('newsJson').value = JSON.stringify(news);
document.getElementById('newsSubheading').value = news.subheading || '';
document.getElementById('newsHeading').value = news.heading || '';
document.getElementById('newsCtaLabel').value = news.ctaButton?.label || '';
document.getElementById('newsCtaHref').value = news.ctaButton?.href || '';
// Update blog selection checkboxes
document.querySelectorAll('.about-blog-checkbox').forEach(cb => {
const isSelected = news.selectedBlogIds && news.selectedBlogIds.some(id => id.toString() === cb.value);
cb.checked = isSelected;
const card = cb.closest('.blog-select-card');
if (card) {
handleAboutCheckboxUpdate(card, isSelected);
}
});
}
function updateImagePreview(inputId, imagePath) {
if (!imagePath) return;
const input = document.getElementById(inputId);
if (!input) return;
let card = input.closest('.card');
let previewImg = card ? card.querySelector('.uploaded-preview') : null;
if (!previewImg) {
const parent = input.closest('.input-group') || input.parentElement;
previewImg = parent ? parent.querySelector('.uploaded-preview') : null;
}
if (previewImg) {
previewImg.src = imagePath;
}
}
// --- Helper dynamic populations ---
function addMissionItem() {
const container = document.getElementById('missionItemsContainer');
const idx = container.children.length;
const html = `
<div class="card mb-2 mission-item">
<div class="card-body p-2">
<div class="row g-2">
<div class="col-md-4">
<div class="input-group input-group-sm">
<input type="text" class="form-control" name="missionItemIcon_${idx}" placeholder="Icon path">
<button class="btn btn-outline-primary btn-upload-image" type="button" data-target-input="missionItemIcon_${idx}" data-image-type="about">
<i class="fas fa-upload"></i>
</button>
</div>
</div>
<div class="col-md-4">
<input type="text" class="form-control form-control-sm" name="missionItemLabel_${idx}" placeholder="Label">
</div>
<div class="col-md-4">
<input type="text" class="form-control form-control-sm" name="missionItemDesc_${idx}" placeholder="Description">
</div>
</div>
<button type="button" class="btn btn-link text-danger btn-sm p-0 mt-1" onclick="this.closest('.mission-item').remove()">Remove</button>
</div>
</div>`;
container.insertAdjacentHTML('beforeend', html);
}
function populateMissionItems(items) {
const container = document.getElementById('missionItemsContainer');
container.innerHTML = '';
items.forEach((item, i) => {
addMissionItem();
const last = container.lastElementChild;
last.querySelector(`[name="missionItemIcon_${i}"]`).value = item.icon || '';
last.querySelector(`[name="missionItemLabel_${i}"]`).value = item.label || '';
last.querySelector(`[name="missionItemDesc_${i}"]`).value = item.description || '';
});
}
function addMissionFeature() {
const container = document.getElementById('missionFeaturesContainer');
const html = `
<div class="input-group input-group-sm mb-1">
<input type="text" class="form-control mission-feature-input" placeholder="Feature text">
<button class="btn btn-outline-danger" type="button" onclick="this.parentElement.remove()">x</button>
</div>`;
container.insertAdjacentHTML('beforeend', html);
}
function populateMissionFeatures(features) {
const container = document.getElementById('missionFeaturesContainer');
container.innerHTML = '';
features.forEach(f => {
addMissionFeature();
container.lastElementChild.querySelector('input').value = f || '';
});
}
function addFeatureItem() {
const container = document.getElementById('featureItemsContainer');
const idx = container.children.length;
const html = `
<div class="card mb-2 feature-item-row">
<div class="card-body p-2">
<div class="row g-2">
<div class="col-md-4">
<div class="input-group input-group-sm">
<input type="text" class="form-control" name="featureItemIcon_${idx}" placeholder="Icon path">
<button class="btn btn-outline-primary btn-upload-image" type="button" data-target-input="featureItemIcon_${idx}" data-image-type="about">
<i class="fas fa-upload"></i>
</button>
</div>
</div>
<div class="col-md-4">
<input type="text" class="form-control form-control-sm" name="featureItemTitle_${idx}" placeholder="Title">
</div>
<div class="col-md-4">
<input type="text" class="form-control form-control-sm" name="featureItemDesc_${idx}" placeholder="Description">
</div>
</div>
<button type="button" class="btn btn-link text-danger btn-sm p-0 mt-1" onclick="this.closest('.feature-item-row').remove()">Remove</button>
</div>
</div>`;
container.insertAdjacentHTML('beforeend', html);
}
function populateFeatureItems(items) {
const container = document.getElementById('featureItemsContainer');
container.innerHTML = '';
items.forEach((item, i) => {
addFeatureItem();
const last = container.lastElementChild;
last.querySelector(`[name="featureItemIcon_${i}"]`).value = item.icon || '';
last.querySelector(`[name="featureItemTitle_${i}"]`).value = item.title || '';
last.querySelector(`[name="featureItemDesc_${i}"]`).value = item.description || '';
});
}
// Blog selection functions for About News section
function toggleAboutBlogSelection(card, blogId) {
const checkbox = card.querySelector('.about-blog-checkbox');
const isChecking = !checkbox.checked;
if (isChecking) {
const checkedCount = document.querySelectorAll('.about-blog-checkbox:checked').length;
if (checkedCount >= 3) {
alert('You can only select up to 3 blogs.');
return;
}
}
checkbox.checked = isChecking;
handleAboutCheckboxUpdate(card, checkbox.checked);
}
function handleAboutCheckboxChange(checkbox) {
if (checkbox.checked) {
const checkedCount = document.querySelectorAll('.about-blog-checkbox:checked').length;
if (checkedCount > 3) {
checkbox.checked = false;
alert('You can only select up to 3 blogs.');
return;
}
}
const card = checkbox.closest('.blog-select-card');
handleAboutCheckboxUpdate(card, checkbox.checked);
}
function handleAboutCheckboxUpdate(card, isChecked) {
if (isChecked) {
card.classList.add('border-primary', 'bg-light');
} else {
card.classList.remove('border-primary', 'bg-light');
}
}
function addService() {
const container = document.getElementById('servicesContainer');
const index = container.children.length;
const html = `
<div class="card mb-3 service-item">
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Title</label>
<input type="text" class="form-control" name="serviceTitle_${index}">
</div>
<div class="col-md-8">
<label class="form-label">Description</label>
<textarea class="form-control" name="serviceDescription_${index}" rows="2"></textarea>
</div>
</div>
<button type="button" class="btn btn-outline-danger btn-sm mt-3" onclick="removeService(this)">
<i class="fas fa-trash me-2"></i>Remove Service
</button>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
}
function updateJsonData() {
try {
// Hero
const heroData = {
title: document.getElementById('heroTitle').value.trim(),
breadcrumb: document.getElementById('heroBreadcrumb').value.split(',').map(s => s.trim()).filter(s => s !== ''),
backgroundImage: document.getElementById('heroBackgroundImage').value.trim()
};
document.getElementById('heroJson').value = JSON.stringify(heroData);
// Intro
const introData = {
subheading: document.getElementById('introSubheading').value.trim(),
heading: document.getElementById('introHeading').value.trim(),
description: document.getElementById('introDescription').value.trim(),
image: document.getElementById('introImage').value.trim()
};
document.getElementById('introJson').value = JSON.stringify(introData);
// Mission
const missionData = {
subheading: document.getElementById('missionSubheading').value.trim(),
heading: document.getElementById('missionHeading').value.trim(),
description: document.getElementById('missionDescription').value.trim(),
images: {
main: document.getElementById('missionImg_main').value.trim(),
secondary: document.getElementById('missionImg_secondary').value.trim(),
bgShape: document.getElementById('missionImg_bgShape').value.trim(),
planeShape: document.getElementById('missionImg_planeShape').value.trim(),
topShape: document.getElementById('missionImg_topShape').value.trim(),
globeShape: document.getElementById('missionImg_globeShape').value.trim()
},
items: Array.from(document.querySelectorAll('.mission-item')).map(item => ({
icon: item.querySelector('[name^="missionItemIcon_"]').value.trim(),
label: item.querySelector('[name^="missionItemLabel_"]').value.trim(),
description: item.querySelector('[name^="missionItemDesc_"]').value.trim()
})).filter(i => i.label !== ''),
features: Array.from(document.querySelectorAll('.mission-feature-input')).map(input => input.value.trim()).filter(v => v !== ''),
ctaButton: {
label: document.getElementById('missionCtaLabel').value.trim(),
href: document.getElementById('missionCtaHref').value.trim()
}
};
document.getElementById('missionJson').value = JSON.stringify(missionData);
// Features
const featuresData = {
backgroundImage: document.getElementById('featuresBgImage').value.trim(),
subheading: document.getElementById('featuresSubheading').value.trim(),
heading: document.getElementById('featuresHeading').value.trim(),
description: document.getElementById('featuresDescription').value.trim(),
image: document.getElementById('featuresImage').value.trim(),
items: Array.from(document.querySelectorAll('.feature-item-row')).map(item => ({
icon: item.querySelector('[name^="featureItemIcon_"]').value.trim(),
title: item.querySelector('[name^="featureItemTitle_"]').value.trim(),
description: item.querySelector('[name^="featureItemDesc_"]').value.trim()
})).filter(i => i.title !== ''),
ctaButton: {
label: document.getElementById('featuresCtaLabel').value.trim(),
href: document.getElementById('featuresCtaHref').value.trim()
}
};
document.getElementById('featuresJson').value = JSON.stringify(featuresData);
// News
const selectedIds = [];
document.querySelectorAll('.about-blog-checkbox:checked').forEach(cb => {
selectedIds.push(cb.value);
});
const newsData = {
subheading: document.getElementById('newsSubheading').value.trim(),
heading: document.getElementById('newsHeading').value.trim(),
ctaButton: {
label: document.getElementById('newsCtaLabel').value.trim(),
href: document.getElementById('newsCtaHref').value.trim()
},
selectedBlogIds: selectedIds,
items: [] // Server will populate this from selectedBlogIds
};
document.getElementById('newsJson').value = JSON.stringify(newsData);
} catch (error) {
console.error('Error updating JSON data:', error);
throw new Error('Failed to process form data');
}
}
</script>
<style>
.tiny {
font-size: 0.75rem;
}
.blog-select-card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
</style>