Files
uldp-degree-mangement-system/views/admin/aboutUs/index.ejs
r2xrzh9q2z-lab d1b931d547 first commit
2026-02-02 11:07:09 +07:00

1276 lines
74 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="<%= data && data._id ? ('/admin/about-us/' + data._id + '/update') : '#' %>">
<!-- Hidden inputs for JSON data -->
<input type="hidden" name="hero" id="heroJson">
<input type="hidden" name="introduction" id="introductionJson">
<input type="hidden" name="statistics" id="statisticsJson">
<input type="hidden" name="accommodation" id="accommodationJson">
<input type="hidden" name="activities" id="activitiesJson">
<input type="hidden" name="newsletter" id="newsletterJson">
<input type="hidden" name="events" id="eventsJson">
<!-- Navigation Tabs -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white border-bottom">
<ul class="nav nav-tabs card-header-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#hero" role="tab">
<i class="fas fa-home me-2"></i>Hero
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#stats" role="tab">
<i class="fas fa-chart-bar me-2"></i>Stats
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#introduce" role="tab">
<i class="fas fa-info-circle me-2"></i>Introduce
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#activities" role="tab">
<i class="fas fa-running me-2"></i>Activities
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#newsletter" role="tab">
<i class="fas fa-envelope me-2"></i>Newsletter
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#features" role="tab">
<i class="fas fa-star me-2"></i>Features
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#events" role="tab">
<i class="fas fa-calendar-alt me-2"></i>Events
</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<!-- Hero Tab -->
<div class="tab-pane fade show active" id="hero" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-body">
<div class="row">
<div class="col-md-5">
<label class="form-label fw-medium">Background Image</label>
<div class="input-group mb-2">
<input type="text" class="form-control" id="heroBackgroundImage"
name="heroBackgroundImage" value="<%= data.hero?.backgroundImage || '' %>">
<button type="button"
class="btn btn-outline-primary btn-upload-image"
data-target-input="heroBackgroundImage" data-image-type="about">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
</div>
<div class="col-md-7">
<% if (data.hero?.backgroundImage) { %>
<img src="<%= data.hero.backgroundImage %>" class="img-thumbnail uploaded-preview"
style="height: 300px; width: 100%; object-fit: cover;"
alt="Background image preview">
<% } %>
</div>
</div>
<div class="row mt-3">
<div class="col-md-12">
<label class="form-label fw-medium">Title</label>
<input type="text" class="form-control" id="heroTitle" name="heroTitle"
value="<%= data.hero?.title || '' %>">
</div>
</div>
</div>
</div>
</div>
<!-- Stats Tab -->
<div class="tab-pane fade" id="stats" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="fw-medium mb-0">Statistics</h6>
<button type="button" class="btn btn-primary btn-sm" onclick="addStat()">
<i class="fas fa-plus"></i> Add Stat
</button>
</div>
<div id="statsContainer">
<% if (data.statistics && data.statistics.items && data.statistics.items.length > 0) { %>
<% data.statistics.items.forEach((stat, index)=> { %>
<div class="card mb-3 stat-item">
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Number</label>
<input type="text" class="form-control"
name="statNumber_<%= index %>"
value="<%= stat.number %>">
</div>
<div class="col-md-6">
<label class="form-label">Description</label>
<input type="text" class="form-control"
name="statDescription_<%= index %>"
value="<%= stat.description %>">
</div>
</div>
<button type="button"
class="btn btn-outline-danger btn-sm mt-3"
onclick="removeStat(this)">
<i class="fas fa-trash me-2"></i>Remove Stat
</button>
</div>
</div>
<% }); %>
<% } %>
</div>
</div>
</div>
</div>
<!-- Introduce Tab -->
<div class="tab-pane fade" id="introduce" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label fw-medium">Subtitle</label>
<input type="text" class="form-control" id="introSubtitle"
value="<%= data.introduction?.subtitle || '' %>">
</div>
<div class="col-md-6">
<label class="form-label fw-medium">Title</label>
<input type="text" class="form-control" id="introTitle"
value="<%= data.introduction?.title || '' %>">
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<label class="form-label fw-medium">Description</label>
<textarea id="introDescription" class="form-control"
rows="4"><%= data.introduction?.description || '' %></textarea>
</div>
</div>
<div class="row mt-3">
<div class="col-md-8">
<label class="form-label fw-medium">Main Image</label>
<div class="input-group">
<input type="text" class="form-control" id="introMainImage"
value="<%= data.introduction?.mainImage || '' %>">
<button type="button"
class="btn btn-outline-primary btn-upload-image"
data-target-input="introMainImage" data-image-type="about">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
</div>
<div class="col-md-4">
<% if (data.introduction?.mainImage) { %>
<img src="<%= data.introduction.mainImage %>" class="img-thumbnail uploaded-preview"
style="height: 120px; width: 100%; object-fit: cover;"
alt="Main image preview">
<% } %>
</div>
</div>
<hr class="my-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="fw-medium mb-0">Services</h6>
<button type="button" class="btn btn-primary btn-sm" onclick="addService()">
<i class="fas fa-plus"></i> Add Service
</button>
</div>
<div id="servicesContainer">
<% if (data.introduction?.services && data.introduction.services.length > 0) { %>
<% data.introduction.services.forEach((service, index)=> { %>
<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 %>"
value="<%= service.title %>">
</div>
<div class="col-md-8">
<label class="form-label">Description</label>
<textarea class="form-control"
name="serviceDescription_<%= index %>"
rows="2"><%= service.description %></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>
<% }) %>
<% } %>
</div>
</div>
</div>
</div>
<!-- Activities Tab -->
<div class="tab-pane fade" id="activities" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-body">
<h6 class="fw-medium mb-3">Activities Header</h6>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Subtitle</label>
<input type="text" class="form-control" id="activitiesSubtitle"
value="<%= data.activities?.subtitle || '' %>">
</div>
<div class="col-md-8">
<label class="form-label">Title</label>
<input type="text" class="form-control" id="activitiesTitle"
value="<%= data.activities?.title || '' %>">
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<label class="form-label">Description</label>
<textarea id="activitiesDescription" class="form-control"
rows="3"><%= data.activities?.description || '' %></textarea>
</div>
</div>
<hr class="my-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="fw-medium mb-0">Gallery Items</h6>
<button type="button" class="btn btn-primary btn-sm"
onclick="addGalleryItem()">
<i class="fas fa-plus"></i> Add Gallery Item
</button>
</div>
<div id="activitiesGalleryContainer">
<% if (data.activities?.gallery && data.activities.gallery.length > 0) { %>
<% data.activities.gallery.forEach((item, index)=> { %>
<div class="card mb-3 gallery-item">
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Image</label>
<div class="input-group">
<input type="text" class="form-control"
name="galleryImage_<%= index %>"
value="<%= item.image %>">
<button type="button"
class="btn btn-outline-primary btn-upload-image"
data-target-input="galleryImage_<%= index %>"
data-image-type="about">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
<% if (item.image) { %>
<img src="<%= item.image %>" class="img-thumbnail uploaded-preview mt-2"
style="height: 100px; width: 100%; object-fit: cover;"
alt="Gallery image preview">
<% } %>
</div>
<div class="col-md-4">
<label class="form-label">Title</label>
<input type="text" class="form-control"
name="galleryTitle_<%= index %>"
value="<%= item.title %>">
</div>
<div class="col-md-4">
<label class="form-label">Description</label>
<textarea class="form-control"
name="galleryDescription_<%= index %>"
rows="3"><%= item.description %></textarea>
</div>
</div>
<button type="button"
class="btn btn-outline-danger btn-sm mt-3"
onclick="removeGalleryItem(this)">
<i class="fas fa-trash me-2"></i>Remove Gallery Item
</button>
</div>
</div>
<% }); %>
<% } %>
</div>
</div>
</div>
</div>
<!-- Newsletter Tab -->
<div class="tab-pane fade" id="newsletter" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-body">
<h6 class="fw-medium mb-3">Newsletter Settings</h6>
<div class="row g-3">
<div class="col-md-8">
<label class="form-label">Image Path</label>
<div class="input-group">
<input class="form-control" id="newsletterImagePath"
value="<%= data.newsletter?.imagePath || '' %>">
<button type="button"
class="btn btn-outline-primary btn-upload-image"
data-target-input="newsletterImagePath" data-image-type="about">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
</div>
<div class="col-md-4">
<% if (data.newsletter?.imagePath) { %>
<img src="<%= data.newsletter.imagePath %>" class="img-thumbnail uploaded-preview"
style="height: 100px; width: 100%; object-fit: cover;"
alt="Newsletter image preview">
<% } %>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<label class="form-label">Title</label>
<input class="form-control" id="newsletterTitle"
value="<%= data.newsletter?.title || '' %>">
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<label class="form-label">Description</label>
<textarea id="newsletterDescription" class="form-control"
rows="3"><%= data.newsletter?.description || '' %></textarea>
</div>
</div>
<div class="row mt-3">
<div class="col-md-4">
<label class="form-label">Button Text</label>
<input class="form-control" id="newsletterButtonText"
value="<%= data.newsletter?.buttonText || '' %>">
</div>
</div>
</div>
</div>
</div>
<!-- Features Tab (Accommodation) -->
<div class="tab-pane fade" id="features" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-body">
<h6 class="fw-medium mb-3">Accommodation Section</h6>
<div class="row g-3 mb-4">
<div class="col-md-4">
<label class="form-label">Subtitle</label>
<input class="form-control" id="accommodationSubtitle"
value="<%= data.accommodation?.subtitle || '' %>">
</div>
<div class="col-md-8">
<label class="form-label">Title</label>
<input class="form-control" id="accommodationTitle"
value="<%= data.accommodation?.title || '' %>">
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="accommodationDescription" rows="3"><%= data.accommodation?.description || '' %></textarea>
</div>
</div>
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="fw-medium mb-0">Features</h6>
<button type="button" class="btn btn-primary btn-sm" onclick="addFeature()">
<i class="fas fa-plus"></i> Add Feature
</button>
</div>
<div id="featuresContainer">
<% if (data.accommodation?.features && data.accommodation.features.length > 0) { %>
<% data.accommodation.features.forEach((feature, index)=> { %>
<div class="card mb-3 feature-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="featureTitle_<%= index %>"
value="<%= feature.title %>">
</div>
<div class="col-md-4">
<label class="form-label">Description</label>
<textarea class="form-control"
name="featureDescription_<%= index %>"
rows="2"><%= feature.description %></textarea>
</div>
<div class="col-md-4">
<label class="form-label">Image</label>
<div class="input-group">
<input type="text" class="form-control" name="featureIcon_<%= index %>" value="<%= feature.icon || '' %>">
<button type="button" class="btn btn-outline-primary btn-upload-image" data-target-input="featureIcon_<%= index %>" data-image-type="about">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
<% if (feature.icon) { %>
<img src="<%= feature.icon %>" class="img-thumbnail uploaded-preview mt-2" style="height: 100px; width: 100%; object-fit: cover;" alt="Feature image preview">
<% } %>
</div>
</div>
<button type="button"
class="btn btn-outline-danger btn-sm mt-3"
onclick="removeFeature(this)">
<i class="fas fa-trash me-2"></i>Remove Feature
</button>
</div>
</div>
<% }); %>
<% } %>
</div>
</div>
</div>
</div>
<!-- Events Tab -->
<div class="tab-pane fade" id="events" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-body">
<div class="mb-3">
<h6 class="fw-medium mb-3">Events Section</h6>
<div class="row g-3">
<div class="col-md-12">
<label class="form-label">Title</label>
<input class="form-control" id="eventsTitle"
value="<%= data.events?.title || '' %>">
</div>
</div>
</div>
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="fw-medium mb-0">Events</h6>
<button type="button" class="btn btn-primary btn-sm" onclick="addEvent()">
<i class="fas fa-plus"></i> Add Event
</button>
</div>
<div id="eventsContainer">
<% if (data.events?.items && data.events.items.length > 0) { %>
<% data.events.items.forEach((event, index)=> { %>
<div class="card mb-3 event-item">
<div class="card-body">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label">Image</label>
<div class="input-group">
<input type="text" class="form-control"
name="eventImage_<%= index %>"
value="<%= event.imageUrl %>">
<button type="button"
class="btn btn-outline-primary btn-upload-image"
data-target-input="eventImage_<%= index %>"
data-image-type="about">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
<% if (event.imageUrl) { %>
<img src="<%= event.imageUrl %>"
class="img-thumbnail uploaded-preview mt-2"
style="height: 150px; width: 100%; object-fit: cover;"
alt="Event image preview">
<% } %>
</div>
<div class="col-md-9">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Date</label>
<input type="text" class="form-control"
name="eventDate_<%= index %>"
value="<%= event.date %>">
</div>
<div class="col-md-8">
<label class="form-label">Title</label>
<input type="text" class="form-control"
name="eventTitle_<%= index %>"
value="<%= event.title %>">
</div>
<div class="col-md-12">
<label
class="form-label">Description</label>
<textarea class="form-control"
name="eventDescription_<%= index %>"
rows="2"><%= event.description %></textarea>
</div>
<div class="col-md-12">
<label class="form-label">Age Group</label>
<input type="text" class="form-control"
name="eventAge_<%= index %>"
value="<%= event.age || '' %>"
placeholder="e.g., Age Group: 10-14">
</div>
</div>
</div>
</div>
<button type="button"
class="btn btn-outline-danger btn-sm mt-3"
onclick="removeEvent(this)">
<i class="fas fa-trash me-2"></i>Remove Event
</button>
</div>
</div>
<% }); %>
<% } %>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Fixed Bottom Buttons -->
<div class="fixed-bottom-buttons">
<button type="reset" class="btn btn-secondary" onclick="resetForm()">
<i class="fas fa-undo me-2"></i>Reset
</button>
<button type="submit" class="btn btn-primary" id="submitBtn" <%= data && data._id ? '' : 'disabled' %>>
<i class="fas fa-save me-2"></i>Save Changes
</button>
</div>
<% if (!data || !data._id) { %>
<div class="mt-3 alert alert-warning">
No About Us document found. To enable editing, please <a href="/admin/about-us/create">create an About Us record</a> first.
</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');
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Saving...';
try {
updateJsonData();
this.submit();
} catch (error) {
console.error('Error updating data:', error);
alert('Failed to process form data. Please try again.');
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-save me-2"></i>Save Changes';
}
});
document.querySelectorAll('.btn-upload-image').forEach(button => {
button.addEventListener('click', function () {
const targetInput = this.dataset.targetInput;
const imageType = this.dataset.imageType;
openImageUploader(targetInput, imageType);
});
});
}
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: '300px', w: '100%' };
if (/introMainImage/i.test(name)) return { h: '120px', w: '100%' };
if (/newsletter/i.test(name)) return { h: '100px', w: '100%' };
if (/^eventImage_/i.test(name)) return { h: '150px', w: '100%' };
if (/^galleryImage_/i.test(name)) return { h: '100px', w: '100%' };
return { h: '150px', 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 and show loading
const uploadBtn = document.querySelector(`[data-target-input="${targetInput}"]`);
const originalBtnHtml = uploadBtn ? uploadBtn.innerHTML : 'Upload';
if (uploadBtn) {
uploadBtn.disabled = true;
uploadBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Uploading...';
}
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);
}
// Show success message
showToast('Success', 'Image uploaded successfully', 'success');
// Restore button
if (uploadBtn) {
uploadBtn.disabled = false;
uploadBtn.innerHTML = originalBtnHtml;
}
} catch (error) {
console.error('Upload error:', error);
showToast('Error', 'Failed to upload image: ' + error.message, 'error');
// Restore button
const uploadBtn = document.querySelector(`[data-target-input="${targetInput}"]`);
if (uploadBtn) {
uploadBtn.disabled = false;
uploadBtn.innerHTML = uploadBtn.innerHTML.replace('Uploading...', 'Upload');
}
} finally {
document.body.removeChild(fileInput);
}
};
fileInput.click();
}
function resetForm() {
if (confirm('Are you sure you want to reset all changes?')) {
updateAllJsonInputs(originalFormData);
location.reload();
}
}
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) {
// Hero
document.getElementById('heroJson').value = JSON.stringify(data.hero || {});
// Introduction
document.getElementById('introductionJson').value = JSON.stringify(data.introduction || {});
// Statistics
document.getElementById('statisticsJson').value = JSON.stringify(data.statistics || {});
// Accommodation
document.getElementById('accommodationJson').value = JSON.stringify(data.accommodation || {});
// Activities
document.getElementById('activitiesJson').value = JSON.stringify(data.activities || {});
// Newsletter
document.getElementById('newsletterJson').value = JSON.stringify(data.newsletter || {});
// Events
document.getElementById('eventsJson').value = JSON.stringify(data.events || {});
// Populate dynamic content
populateServicesFromData(data.introduction?.services || []);
populateFeaturesFromData(data.accommodation?.features || []);
populateEventsFromData(data.events?.items || []);
populateGalleryFromData(data.activities?.gallery || []);
populateStatsFromData(data.statistics?.items || []);
}
function addStat() {
const container = document.getElementById('statsContainer');
const index = container.children.length;
const html = `
<div class="card mb-3 stat-item">
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Number</label>
<input type="text" class="form-control" name="statNumber_${index}">
</div>
<div class="col-md-6">
<label class="form-label">Description</label>
<input type="text" class="form-control" name="statDescription_${index}">
</div>
</div>
<button type="button" class="btn btn-outline-danger btn-sm mt-3" onclick="removeStat(this)">
<i class="fas fa-trash me-2"></i>Remove Stat
</button>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
}
function removeStat(button) {
button.closest('.stat-item').remove();
}
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 removeService(button) {
button.closest('.service-item').remove();
}
function addFeature() {
const container = document.getElementById('featuresContainer');
const index = container.children.length;
const html = `
<div class="card mb-3 feature-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="featureTitle_${index}">
</div>
<div class="col-md-4">
<label class="form-label">Description</label>
<textarea class="form-control" name="featureDescription_${index}" rows="2"></textarea>
</div>
<div class="col-md-4">
<label class="form-label">Image</label>
<div class="input-group">
<input type="text" class="form-control" name="featureIcon_${index}">
<button type="button" class="btn btn-outline-primary btn-upload-image" data-target-input="featureIcon_${index}" data-image-type="about">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-outline-danger btn-sm mt-3" onclick="removeFeature(this)">
<i class="fas fa-trash me-2"></i>Remove Feature
</button>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
// Add event listener for new upload button inside the new feature
const newButton = container.lastElementChild.querySelector('.btn-upload-image');
if (newButton) {
newButton.addEventListener('click', function () {
const targetInput = this.dataset.targetInput;
const imageType = this.dataset.imageType;
openImageUploader(targetInput, imageType);
});
}
}
function removeFeature(button) {
button.closest('.feature-item').remove();
}
function addEvent() {
const container = document.getElementById('eventsContainer');
const index = container.children.length;
const html = `
<div class="card mb-3 event-item">
<div class="card-body">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label">Image</label>
<div class="input-group">
<input type="text" class="form-control" name="eventImage_${index}">
<button type="button" class="btn btn-outline-primary btn-upload-image" data-target-input="eventImage_${index}" data-image-type="about">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
</div>
<div class="col-md-9">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Date</label>
<input type="text" class="form-control" name="eventDate_${index}">
</div>
<div class="col-md-8">
<label class="form-label">Title</label>
<input type="text" class="form-control" name="eventTitle_${index}">
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" name="eventDescription_${index}" rows="2"></textarea>
</div>
<div class="col-md-12">
<label class="form-label">Age Group</label>
<input type="text" class="form-control" name="eventAge_${index}" placeholder="e.g., Age Group: 10-14">
</div>
</div>
</div>
</div>
<button type="button" class="btn btn-outline-danger btn-sm mt-3" onclick="removeEvent(this)">
<i class="fas fa-trash me-2"></i>Remove Event
</button>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
// Add event listener for new upload button
const newButton = container.lastElementChild.querySelector('.btn-upload-image');
newButton.addEventListener('click', function () {
const targetInput = this.dataset.targetInput;
const imageType = this.dataset.imageType;
openImageUploader(targetInput, imageType);
});
}
function removeEvent(button) {
button.closest('.event-item').remove();
}
function updateJsonData() {
try {
// Hero
const heroData = {
title: (document.getElementById('heroTitle') || {}).value?.trim() || '',
backgroundImage: (document.getElementById('heroBackgroundImage') || {}).value?.trim() || ''
};
document.getElementById('heroJson').value = JSON.stringify(heroData);
// Introduction
const servicesData = Array.from(document.querySelectorAll('.service-item'))
.map((item) => {
const titleEl = item.querySelector('[name^="serviceTitle_"]') || item.querySelector('input');
const descEl = item.querySelector('[name^="serviceDescription_"]') || item.querySelector('textarea');
return {
title: (titleEl?.value || '').trim(),
description: (descEl?.value || '').trim()
};
})
.filter(s => s.title !== '' || s.description !== '');
const introductionData = {
subtitle: (document.getElementById('introSubtitle') || {}).value?.trim() || '',
title: (document.getElementById('introTitle') || {}).value?.trim() || '',
description: (document.getElementById('introDescription') || {}).value?.trim() || '',
mainImage: (document.getElementById('introMainImage') || {}).value?.trim() || '',
services: servicesData
};
document.getElementById('introductionJson').value = JSON.stringify(introductionData);
// Statistics
const statsItems = Array.from(document.querySelectorAll('.stat-item'))
.map((item) => {
const numEl = item.querySelector('[name^="statNumber_"]') || item.querySelectorAll('input')[0];
const descEl = item.querySelector('[name^="statDescription_"]') || item.querySelectorAll('input')[1];
return {
number: (numEl?.value || '').trim(),
description: (descEl?.value || '').trim()
};
})
.filter(stat => stat.number !== '' || stat.description !== '');
const statisticsData = { items: statsItems };
document.getElementById('statisticsJson').value = JSON.stringify(statisticsData);
// Accommodation
const featuresItems = Array.from(document.querySelectorAll('.feature-item'))
.map((item) => {
const titleEl = item.querySelector('[name^="featureTitle_"]') || item.querySelector('input');
const descEl = item.querySelector('[name^="featureDescription_"]') || item.querySelector('textarea');
const iconEl = item.querySelector('[name^="featureIcon_"]') || item.querySelector('input');
return {
title: (titleEl?.value || '').trim(),
description: (descEl?.value || '').trim(),
icon: (iconEl?.value || '').trim()
};
})
.filter(f => f.title !== '' || f.description !== '');
const accommodationData = {
subtitle: (document.getElementById('accommodationSubtitle') || {}).value?.trim() || '',
title: (document.getElementById('accommodationTitle') || {}).value?.trim() || '',
description: (document.getElementById('accommodationDescription') || {}).value?.trim() || '',
features: featuresItems
};
document.getElementById('accommodationJson').value = JSON.stringify(accommodationData);
// Activities
const galleryItems = Array.from(document.querySelectorAll('.gallery-item'))
.map((item) => {
const imageEl = item.querySelector('[name^="galleryImage_"]') || item.querySelector('input');
const titleEl = item.querySelector('[name^="galleryTitle_"]') || item.querySelectorAll('input')[1];
const descEl = item.querySelector('[name^="galleryDescription_"]') || item.querySelector('textarea');
return {
image: (imageEl?.value || '').trim(),
title: (titleEl?.value || '').trim(),
description: (descEl?.value || '').trim()
};
})
.filter(item => item.image !== '' || item.title !== '');
const activitiesData = {
subtitle: (document.getElementById('activitiesSubtitle') || {}).value?.trim() || '',
title: (document.getElementById('activitiesTitle') || {}).value?.trim() || '',
description: (document.getElementById('activitiesDescription') || {}).value?.trim() || '',
gallery: galleryItems
};
document.getElementById('activitiesJson').value = JSON.stringify(activitiesData);
// Newsletter
const newsletterData = {
imagePath: (document.getElementById('newsletterImagePath') || {}).value?.trim() || '',
title: (document.getElementById('newsletterTitle') || {}).value?.trim() || '',
description: (document.getElementById('newsletterDescription') || {}).value?.trim() || '',
buttonText: (document.getElementById('newsletterButtonText') || {}).value?.trim() || ''
};
document.getElementById('newsletterJson').value = JSON.stringify(newsletterData);
// Events
const eventsItems = Array.from(document.querySelectorAll('.event-item')).map((item) => {
const imageEl = item.querySelector('[name^="eventImage_"]') || item.querySelector('input');
const dateEl = item.querySelector('[name^="eventDate_"]') || item.querySelector('input[type="text"]');
const titleEl = item.querySelector('[name^="eventTitle_"]') || item.querySelector('input');
const descEl = item.querySelector('[name^="eventDescription_"]') || item.querySelector('textarea');
const ageEl = item.querySelector('[name^="eventAge_"]') || item.querySelectorAll('input')[3];
return {
imageUrl: (imageEl?.value || '').trim(),
date: (dateEl?.value || '').trim(),
title: (titleEl?.value || '').trim(),
description: (descEl?.value || '').trim(),
age: (ageEl?.value || '').trim()
};
}).filter(event => event.title !== '' || event.description !== '');
const eventsData = {
title: (document.getElementById('eventsTitle') || {}).value?.trim() || '',
items: eventsItems
};
document.getElementById('eventsJson').value = JSON.stringify(eventsData);
} catch (error) {
console.error('Error updating JSON data:', error);
throw new Error('Failed to process form data');
}
}
// Helpers to populate DOM lists from data
function populateStatsFromData(stats) {
const container = document.getElementById('statsContainer');
container.innerHTML = '';
(stats || []).forEach((stat, i) => {
addStat();
const el = container.lastElementChild;
el.querySelector(`[name="statNumber_${i}"]`).value = stat.number || '';
el.querySelector(`[name="statDescription_${i}"]`).value = stat.description || '';
});
}
function populateServicesFromData(services) {
const container = document.getElementById('servicesContainer');
container.innerHTML = '';
(services || []).forEach((s, i) => {
addService();
const el = container.lastElementChild;
el.querySelector(`[name="serviceTitle_${i}"]`).value = s.title || '';
el.querySelector(`[name="serviceDescription_${i}"]`).value = s.description || '';
});
}
function populateFeaturesFromData(features) {
const container = document.getElementById('featuresContainer');
container.innerHTML = '';
(features || []).forEach((f, i) => {
addFeature();
const el = container.lastElementChild;
el.querySelector(`[name="featureTitle_${i}"]`).value = f.title || '';
el.querySelector(`[name="featureDescription_${i}"]`).value = f.description || '';
// populate icon field and preview
const iconInput = el.querySelector(`[name="featureIcon_${i}"]`);
if (iconInput) iconInput.value = f.icon || '';
try {
let preview = el.querySelector('.uploaded-preview');
const iconPath = f.icon || '';
if (iconPath) {
const previewUrl = (iconPath.startsWith('http://') || iconPath.startsWith('https://')) ? iconPath : (window.location.origin + iconPath);
if (preview) {
preview.src = previewUrl;
preview.style.display = '';
} else if (iconInput) {
const parent = iconInput.parentElement || iconInput.closest('.input-group') || iconInput.closest('div');
if (parent) {
const img = document.createElement('img');
img.src = previewUrl;
img.className = 'img-thumbnail uploaded-preview mt-2';
img.style.height = '100px';
img.style.width = '100%';
img.style.objectFit = 'cover';
parent.insertAdjacentElement('afterend', img);
}
}
} else if (preview) {
preview.remove();
}
} catch (err) {
console.error('populateFeaturesFromData preview error', err);
}
});
}
function populateEventsFromData(events) {
const container = document.getElementById('eventsContainer');
container.innerHTML = '';
(events || []).forEach((ev, i) => {
addEvent();
const el = container.lastElementChild;
el.querySelector(`[name="eventImage_${i}"]`).value = ev.imageUrl || '';
el.querySelector(`[name="eventDate_${i}"]`).value = ev.date || '';
el.querySelector(`[name="eventTitle_${i}"]`).value = ev.title || '';
el.querySelector(`[name="eventDescription_${i}"]`).value = ev.description || '';
el.querySelector(`[name="eventAge_${i}"]`).value = ev.age || '';
// ensure preview exists or is updated for this event
try {
const input = el.querySelector(`[name="eventImage_${i}"]`);
const parent = input.parentElement || input.closest('.input-group') || input.closest('div');
let preview = el.querySelector('.uploaded-preview');
const imagePath = ev.imageUrl || '';
if (imagePath) {
const previewUrl = (imagePath.startsWith('http://') || imagePath.startsWith('https://')) ? imagePath : (window.location.origin + imagePath);
if (preview) {
preview.src = previewUrl;
preview.style.display = '';
} else if (parent) {
const img = document.createElement('img');
img.src = previewUrl;
img.className = 'img-thumbnail uploaded-preview mt-2';
img.style.height = '150px';
img.style.width = '100%';
img.style.objectFit = 'cover';
parent.insertAdjacentElement('afterend', img);
}
} else if (preview) {
preview.remove();
}
} catch (err) {
console.error('populateEventsFromData preview error', err);
}
});
}
function populateGalleryFromData(gallery) {
const container = document.getElementById('activitiesGalleryContainer');
container.innerHTML = '';
(gallery || []).forEach((item, i) => {
addGalleryItem();
const el = container.lastElementChild;
el.querySelector(`[name="galleryImage_${i}"]`).value = item.image || '';
el.querySelector(`[name="galleryTitle_${i}"]`).value = item.title || '';
el.querySelector(`[name="galleryDescription_${i}"]`).value = item.description || '';
// ensure preview exists or is updated
try {
const input = el.querySelector(`[name="galleryImage_${i}"]`);
const parent = input.parentElement || input.closest('.input-group') || input.closest('div');
let preview = el.querySelector('.uploaded-preview');
const imagePath = item.image || '';
if (imagePath) {
const previewUrl = (imagePath.startsWith('http://') || imagePath.startsWith('https://')) ? imagePath : (window.location.origin + imagePath);
if (preview) {
preview.src = previewUrl;
preview.style.display = '';
} else if (parent) {
const img = document.createElement('img');
img.src = previewUrl;
img.className = 'img-thumbnail uploaded-preview mt-2';
img.style.height = '100px';
img.style.width = '100%';
img.style.objectFit = 'cover';
parent.insertAdjacentElement('afterend', img);
}
} else if (preview) {
preview.remove();
}
} catch (err) {
console.error('populateGalleryFromData preview error', err);
}
});
}
function addGalleryItem() {
const container = document.getElementById('activitiesGalleryContainer');
const index = container.children.length;
const html = `
<div class="card mb-3 gallery-item">
<div class="card-body">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Image</label>
<div class="input-group">
<input type="text" class="form-control" name="galleryImage_${index}">
<button type="button" class="btn btn-outline-primary btn-upload-image" data-target-input="galleryImage_${index}" data-image-type="about">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
</div>
<div class="col-md-4">
<label class="form-label">Title</label>
<input type="text" class="form-control" name="galleryTitle_${index}">
</div>
<div class="col-md-4">
<label class="form-label">Description</label>
<textarea class="form-control" name="galleryDescription_${index}" rows="3"></textarea>
</div>
</div>
<button type="button" class="btn btn-outline-danger btn-sm mt-3" onclick="removeGalleryItem(this)">
<i class="fas fa-trash me-2"></i>Remove Gallery Item
</button>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
// Add event listener for new upload button
const newButton = container.lastElementChild.querySelector('.btn-upload-image');
newButton.addEventListener('click', function () {
const targetInput = this.dataset.targetInput;
const imageType = this.dataset.imageType;
openImageUploader(targetInput, imageType);
});
}
function removeGalleryItem(button) {
button.closest('.gallery-item').remove();
}
</script>