forked from UKSOURCE/cms.hailearning.edu.vn
1124 lines
70 KiB
Plaintext
1124 lines
70 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 Contact Us page</p>
|
||
</div>
|
||
<div>
|
||
<a href="<%= frontendUrl %>/contact-us/" class="btn btn-outline-primary" target="_blank">
|
||
<i class="fas fa-external-link-alt me-2"></i>View Contact Us Page
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<form method="POST" class="content-with-fixed-buttons" id="contactForm"
|
||
action="/admin/contact/update">
|
||
<!-- Hidden inputs for JSON data -->
|
||
<input type="hidden" name="hero" id="heroJson">
|
||
<input type="hidden" name="contactCards" id="contactCardsJson">
|
||
<input type="hidden" name="map" id="mapJson">
|
||
<input type="hidden" name="form" id="formJson">
|
||
|
||
<!-- 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="#contactCards" role="tab">
|
||
<i class="fas fa-address-card me-2"></i>Contact Cards
|
||
</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link" data-bs-toggle="tab" href="#map" role="tab">
|
||
<i class="fas fa-map-marker-alt me-2"></i>Map
|
||
</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link" data-bs-toggle="tab" href="#form" role="tab">
|
||
<i class="fas fa-envelope me-2"></i>Form
|
||
</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="contact">
|
||
<i class="fas fa-upload me-1"></i>Upload
|
||
</button>
|
||
</div>
|
||
<small class="text-muted">Recommended size: 1920x1080px</small>
|
||
</div>
|
||
<div class="col-md-7">
|
||
<div id="heroImagePreview" style="height: 300px;">
|
||
<% if (data.hero?.backgroundImage) { %>
|
||
<%
|
||
let heroImgSrc = data.hero.backgroundImage;
|
||
if (heroImgSrc && !heroImgSrc.startsWith('http://') && !heroImgSrc.startsWith('https://')) {
|
||
heroImgSrc = heroImgSrc.startsWith('/') ? heroImgSrc : '/' + heroImgSrc;
|
||
}
|
||
%>
|
||
<img src="<%= heroImgSrc %>" class="img-thumbnail" id="heroPreviewImg"
|
||
style="height: 300px; width: 100%; object-fit: cover;"
|
||
alt="Background image preview" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
|
||
<div class="border rounded p-5 text-center text-muted" style="height: 300px; display: none; align-items: center; justify-content: center;">
|
||
Image preview
|
||
</div>
|
||
<% } else { %>
|
||
<div class="border rounded p-5 text-center text-muted" style="height: 300px; display: flex; align-items: center; justify-content: center;">
|
||
Image preview
|
||
</div>
|
||
<% } %>
|
||
</div>
|
||
</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>
|
||
<!-- Hidden field for overlayColor - keep default value -->
|
||
<input type="hidden" id="heroOverlayColor" value="<%= data.hero?.overlayColor || 'rgba(0, 0, 0, 0)' %>">
|
||
<!-- Hidden fields for class values - keep in data but not editable -->
|
||
<input type="hidden" id="heroSectionClass" value="<%= data.hero?.sectionClass || '' %>">
|
||
<input type="hidden" id="heroTitleClass" value="<%= data.hero?.titleClass || '' %>">
|
||
<input type="hidden" id="heroBackgroundPosition" value="<%= data.hero?.backgroundPosition || 'center' %>">
|
||
<input type="hidden" id="heroEnableScrollspy" value="<%= data.hero?.enableScrollspy ? 'true' : 'false' %>">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Contact Cards Tab -->
|
||
<div class="tab-pane fade" id="contactCards" 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">Contact Cards</h6>
|
||
<button type="button" class="btn btn-primary btn-sm" onclick="addContactCard()">
|
||
<i class="fas fa-plus"></i> Add Card
|
||
</button>
|
||
</div>
|
||
<div id="contactCardsContainer">
|
||
<% if (data.contactCards && data.contactCards.length > 0) { %>
|
||
<% data.contactCards.forEach((card, index) => { %>
|
||
<%
|
||
const iconSource = card.iconSource || (card.iconType && card.iconType.startsWith('/uploads/') ? 'image' : 'fontawesome');
|
||
const isImageIcon = iconSource === 'image';
|
||
const faIconValue = !isImageIcon ? (card.iconType || '') : '';
|
||
const imageIconValue = isImageIcon ? (card.iconType || '') : '';
|
||
%>
|
||
<div class="card mb-3 contact-card-item">
|
||
<div class="card-body">
|
||
<div class="row g-3">
|
||
<div class="col-md-3">
|
||
<label class="form-label">Type</label>
|
||
<select class="form-select card-type-select" name="cardType_<%= index %>" data-index="<%= index %>">
|
||
<option value="phone" <%= card.type === 'phone' ? 'selected' : '' %>>Phone</option>
|
||
<option value="email" <%= card.type === 'email' ? 'selected' : '' %>>Email</option>
|
||
<option value="location" <%= card.type === 'location' ? 'selected' : '' %>>Location</option>
|
||
<option value="hours" <%= card.type === 'hours' ? 'selected' : '' %>>Hours</option>
|
||
<option value="website" <%= card.type === 'website' ? 'selected' : '' %>>Website</option>
|
||
<option value="social" <%= card.type === 'social' ? 'selected' : '' %>>Social Media</option>
|
||
<option value="custom" <%= card.type === 'custom' ? 'selected' : '' %>>Custom</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label class="form-label">Title</label>
|
||
<input type="text" class="form-control" name="cardTitle_<%= index %>"
|
||
value="<%= card.title || '' %>">
|
||
</div>
|
||
<div class="col-md-12">
|
||
<label class="form-label">Icon Type</label>
|
||
<div class="btn-group w-100 mb-3" role="group">
|
||
<input type="radio" class="btn-check icon-source-radio" name="cardIconSource_<%= index %>"
|
||
id="iconSource_fa_<%= index %>" value="fontawesome"
|
||
<%= iconSource === 'fontawesome' ? 'checked' : '' %>
|
||
data-index="<%= index %>" onchange="handleIconSourceChange(this)">
|
||
<label class="btn btn-outline-primary" for="iconSource_fa_<%= index %>">
|
||
<i class="fas fa-icons me-2"></i>Font Awesome
|
||
</label>
|
||
|
||
<input type="radio" class="btn-check icon-source-radio" name="cardIconSource_<%= index %>"
|
||
id="iconSource_img_<%= index %>" value="image"
|
||
<%= iconSource === 'image' ? 'checked' : '' %>
|
||
data-index="<%= index %>" onchange="handleIconSourceChange(this)">
|
||
<label class="btn btn-outline-primary" for="iconSource_img_<%= index %>">
|
||
<i class="fas fa-image me-2"></i>Upload Image
|
||
</label>
|
||
</div>
|
||
|
||
<!-- Font Awesome Icon Selector -->
|
||
<div class="icon-fa-container" data-index="<%= index %>" style="display: <%= iconSource === 'fontawesome' ? 'block' : 'none' %>;">
|
||
<label class="form-label">Select Font Awesome Icon</label>
|
||
<select class="form-select fa-icon-select" name="cardFaIcon_<%= index %>" data-index="<%= index %>">
|
||
<option value="">-- Select Icon --</option>
|
||
<option value="fas fa-phone" <%= faIconValue === 'fas fa-phone' ? 'selected' : '' %>>📞 Phone</option>
|
||
<option value="fas fa-envelope" <%= faIconValue === 'fas fa-envelope' ? 'selected' : '' %>>✉️ Email</option>
|
||
<option value="fas fa-map-marker-alt" <%= faIconValue === 'fas fa-map-marker-alt' ? 'selected' : '' %>>📍 Location</option>
|
||
<option value="fas fa-clock" <%= faIconValue === 'fas fa-clock' ? 'selected' : '' %>>🕐 Clock</option>
|
||
<option value="fas fa-globe" <%= faIconValue === 'fas fa-globe' ? 'selected' : '' %>>🌐 Website</option>
|
||
<option value="fas fa-calendar" <%= faIconValue === 'fas fa-calendar' ? 'selected' : '' %>>📅 Calendar</option>
|
||
<option value="fas fa-user" <%= faIconValue === 'fas fa-user' ? 'selected' : '' %>>👤 User</option>
|
||
<option value="fas fa-users" <%= faIconValue === 'fas fa-users' ? 'selected' : '' %>>👥 Users</option>
|
||
<option value="fas fa-building" <%= faIconValue === 'fas fa-building' ? 'selected' : '' %>>🏢 Building</option>
|
||
<option value="fas fa-home" <%= faIconValue === 'fas fa-home' ? 'selected' : '' %>>🏠 Home</option>
|
||
<option value="fab fa-facebook" <%= faIconValue === 'fab fa-facebook' ? 'selected' : '' %>>📘 Facebook</option>
|
||
<option value="fab fa-twitter" <%= faIconValue === 'fab fa-twitter' ? 'selected' : '' %>>🐦 Twitter</option>
|
||
<option value="fab fa-instagram" <%= faIconValue === 'fab fa-instagram' ? 'selected' : '' %>>📷 Instagram</option>
|
||
<option value="fab fa-linkedin" <%= faIconValue === 'fab fa-linkedin' ? 'selected' : '' %>>💼 LinkedIn</option>
|
||
<option value="fab fa-youtube" <%= faIconValue === 'fab fa-youtube' ? 'selected' : '' %>>📺 YouTube</option>
|
||
<option value="fas fa-mobile-alt" <%= faIconValue === 'fas fa-mobile-alt' ? 'selected' : '' %>>📱 Mobile</option>
|
||
<option value="fas fa-fax" <%= faIconValue === 'fas fa-fax' ? 'selected' : '' %>>📠 Fax</option>
|
||
<option value="fas fa-headset" <%= faIconValue === 'fas fa-headset' ? 'selected' : '' %>>🎧 Support</option>
|
||
<option value="fas fa-info-circle" <%= faIconValue === 'fas fa-info-circle' ? 'selected' : '' %>>ℹ️ Info</option>
|
||
</select>
|
||
<small class="text-muted">Choose a Font Awesome icon from the list</small>
|
||
<div class="mt-2 fa-icon-preview" data-index="<%= index %>">
|
||
<% if (faIconValue) { %>
|
||
<i class="<%= faIconValue %> fa-2x text-primary"></i>
|
||
<% } %>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Upload Image -->
|
||
<div class="icon-image-container" data-index="<%= index %>" style="display: <%= iconSource === 'image' ? 'block' : 'none' %>;">
|
||
<label class="form-label">Upload Icon Image</label>
|
||
<div class="input-group">
|
||
<input type="text" class="form-control card-icon-image-input" name="cardIconImage_<%= index %>"
|
||
value="<%= imageIconValue %>" placeholder="/uploads/icon.png">
|
||
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
||
data-target-input="cardIconImage_<%= index %>" data-image-type="contact">
|
||
<i class="fas fa-upload me-1"></i>Upload Icon
|
||
</button>
|
||
</div>
|
||
<% if (imageIconValue) { %>
|
||
<img src="<%= imageIconValue %>" class="img-thumbnail mt-2 icon-image-preview" data-index="<%= index %>" style="max-height: 100px; width: auto;" alt="Icon preview">
|
||
<% } else { %>
|
||
<img src="" class="img-thumbnail mt-2 icon-image-preview" data-index="<%= index %>" style="max-height: 100px; width: auto; display: none;" alt="Icon preview">
|
||
<% } %>
|
||
<small class="text-muted">Upload a custom icon image for this contact card</small>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-12">
|
||
<label class="form-label">Content (one per line)</label>
|
||
<textarea class="form-control" name="cardContent_<%= index %>" rows="3"><%= (card.content || []).join('\n') %></textarea>
|
||
<small class="text-muted">Enter each content item on a new line</small>
|
||
</div>
|
||
</div>
|
||
<button type="button"
|
||
class="btn btn-outline-danger btn-sm mt-3"
|
||
onclick="removeContactCard(this)">
|
||
<i class="fas fa-trash me-2"></i>Remove Card
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<% }); %>
|
||
<% } %>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Map Tab -->
|
||
<div class="tab-pane fade" id="map" role="tabpanel">
|
||
<div class="card border shadow-sm">
|
||
<div class="card-body">
|
||
<h6 class="fw-medium mb-3">Map Settings</h6>
|
||
<div class="row g-3">
|
||
<div class="col-md-6">
|
||
<label class="form-label">Marker Title</label>
|
||
<input type="text" class="form-control" id="mapMarkerTitle"
|
||
value="<%= data.map?.markerTitle || '' %>" placeholder="e.g., Our Office">
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label class="form-label">Location</label>
|
||
<input type="text" class="form-control" id="mapLocation"
|
||
value="<%= data.map?.location || '' %>" placeholder="e.g., 123 Main St, City, Country">
|
||
<small class="text-muted">Enter address - map will be automatically shown</small>
|
||
</div>
|
||
<div class="col-md-12">
|
||
<div id="mapPreview" style="height: 400px; border: 1px solid #ddd; border-radius: 4px; margin-top: 15px; background: #f5f5f5; display: flex; align-items: center; justify-content: center; color: #999;">
|
||
<% if (data.map?.location && data.map?.coordinates?.lat && data.map?.coordinates?.lng) { %>
|
||
<%
|
||
const lat = data.map.coordinates.lat;
|
||
const lng = data.map.coordinates.lng;
|
||
const zoom = data.map.zoom || 15;
|
||
const markerTitle = data.map.markerTitle || data.map.location;
|
||
// Calculate bbox for proper zoom - smaller bbox = more zoomed in
|
||
const zoomDelta = {
|
||
10: 0.1, 11: 0.05, 12: 0.025, 13: 0.0125, 14: 0.006,
|
||
15: 0.003, 16: 0.0015, 17: 0.00075, 18: 0.000375
|
||
};
|
||
const delta = zoomDelta[zoom] || 0.003;
|
||
const latDelta = delta;
|
||
const lngDelta = delta * 1.5;
|
||
%>
|
||
<iframe
|
||
width="100%"
|
||
height="100%"
|
||
style="border:0; border-radius: 4px;"
|
||
src="https://www.openstreetmap.org/export/embed.html?bbox=<%= lng-lngDelta %>,<%= lat-latDelta %>,<%= lng+lngDelta %>,<%= lat+latDelta %>&layer=mapnik&marker=<%= lat %>,<%= lng %>"
|
||
frameborder="0">
|
||
</iframe>
|
||
<div class="mt-2 text-center">
|
||
<small class="text-muted">📍 <%= markerTitle %></small>
|
||
</div>
|
||
<% } else { %>
|
||
Enter location above to see map preview
|
||
<% } %>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Hidden fields for map settings - keep defaults -->
|
||
<input type="hidden" id="mapLat" value="<%= data.map?.coordinates?.lat || 0 %>">
|
||
<input type="hidden" id="mapLng" value="<%= data.map?.coordinates?.lng || 0 %>">
|
||
<input type="hidden" id="mapZoom" value="<%= data.map?.zoom || 15 %>">
|
||
<input type="hidden" id="tileLayerUrl" value="<%= data.map?.tileLayer?.url || 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' %>">
|
||
<input type="hidden" id="tileLayerAttribution" value="<%= data.map?.tileLayer?.attribution || '' %>">
|
||
<input type="hidden" id="tileLayerMaxZoom" value="<%= data.map?.tileLayer?.maxZoom || 18 %>">
|
||
<input type="hidden" id="tileLayerMinZoom" value="<%= data.map?.tileLayer?.minZoom || 0 %>">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Form Tab -->
|
||
<div class="tab-pane fade" id="form" role="tabpanel">
|
||
<div class="card border shadow-sm">
|
||
<div class="card-body">
|
||
<h6 class="fw-medium mb-3">Form Settings</h6>
|
||
<div class="row g-3">
|
||
<div class="col-md-6">
|
||
<label class="form-label">Section Label</label>
|
||
<input type="text" class="form-control" id="formSectionLabel"
|
||
value="<%= data.form?.sectionLabel || '' %>">
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label class="form-label">Submit Button Text</label>
|
||
<input type="text" class="form-control" id="formSubmitButtonText"
|
||
value="<%= data.form?.submitButton?.text || 'Send Message' %>">
|
||
</div>
|
||
<div class="col-md-12">
|
||
<label class="form-label">Heading</label>
|
||
<input type="text" class="form-control" id="formHeading"
|
||
value="<%= data.form?.heading || '' %>">
|
||
</div>
|
||
</div>
|
||
<hr class="my-4">
|
||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||
<h6 class="fw-medium mb-0">Form Fields</h6>
|
||
<button type="button" class="btn btn-primary btn-sm" onclick="addFormField()">
|
||
<i class="fas fa-plus"></i> Add Field
|
||
</button>
|
||
</div>
|
||
<div id="formFieldsContainer">
|
||
<% if (data.form?.fields && data.form.fields.length > 0) { %>
|
||
<% data.form.fields.forEach((field, index) => { %>
|
||
<div class="card mb-3 form-field-item">
|
||
<div class="card-body">
|
||
<div class="row g-3">
|
||
<div class="col-md-3">
|
||
<label class="form-label">Label</label>
|
||
<input type="text" class="form-control" name="fieldName_<%= index %>"
|
||
value="<%= field.name || '' %>" placeholder="e.g., Your Name">
|
||
</div>
|
||
<div class="col-md-3">
|
||
<label class="form-label">Field Type</label>
|
||
<select class="form-select field-type-select" name="fieldType_<%= index %>" data-index="<%= index %>" onchange="handleFieldTypeChange(this)">
|
||
<option value="text" <%= field.type === 'text' ? 'selected' : '' %>>Text</option>
|
||
<option value="email" <%= field.type === 'email' ? 'selected' : '' %>>Email</option>
|
||
<option value="tel" <%= field.type === 'tel' ? 'selected' : '' %>>Tel</option>
|
||
<option value="textarea" <%= field.type === 'textarea' ? 'selected' : '' %>>Textarea</option>
|
||
<option value="programme" <%= field.type === 'programme' ? 'selected' : '' %>>Programme</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Placeholder</label>
|
||
<input type="text" class="form-control" name="fieldPlaceholder_<%= index %>"
|
||
value="<%= field.placeholder || '' %>">
|
||
</div>
|
||
<div class="col-md-2">
|
||
<label class="form-label">Required</label>
|
||
<div class="form-check mt-2">
|
||
<input class="form-check-input" type="checkbox" name="fieldRequired_<%= index %>"
|
||
<%= field.required ? 'checked' : '' %>>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3 programme-name-field" style="display: <%= field.type === 'programme' ? 'block' : 'none' %>;">
|
||
<label class="form-label">Programme Name</label>
|
||
<input type="text" class="form-control" name="fieldProgrammeName_<%= index %>"
|
||
value="<%= field.programmeName || '' %>" placeholder="e.g., Summer Camp 2024">
|
||
<small class="text-muted">Internal name for the programme</small>
|
||
</div>
|
||
</div>
|
||
<button type="button"
|
||
class="btn btn-outline-danger btn-sm mt-3"
|
||
onclick="removeFormField(this)">
|
||
<i class="fas fa-trash me-2"></i>Remove Field
|
||
</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">
|
||
<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('contactForm');
|
||
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);
|
||
});
|
||
});
|
||
|
||
// Initialize map location geocoding
|
||
const mapLocationInput = document.getElementById('mapLocation');
|
||
if (mapLocationInput) {
|
||
let geocodeTimeout;
|
||
mapLocationInput.addEventListener('input', function() {
|
||
clearTimeout(geocodeTimeout);
|
||
const location = this.value.trim();
|
||
|
||
if (!location) {
|
||
document.getElementById('mapPreview').innerHTML = 'Enter location above to see map preview';
|
||
return;
|
||
}
|
||
|
||
geocodeTimeout = setTimeout(() => {
|
||
geocodeLocation(location);
|
||
}, 800); // Wait 800ms after user stops typing
|
||
});
|
||
|
||
// Show map immediately if coordinates already exist
|
||
const mapLat = document.getElementById('mapLat');
|
||
const mapLng = document.getElementById('mapLng');
|
||
if (mapLat && mapLng && mapLat.value && mapLng.value && parseFloat(mapLat.value) !== 0 && parseFloat(mapLng.value) !== 0) {
|
||
// Coordinates already exist, show map immediately
|
||
showMapPreview(parseFloat(mapLat.value), parseFloat(mapLng.value));
|
||
} else if (mapLocationInput.value) {
|
||
// Only geocode if coordinates don't exist
|
||
setTimeout(() => geocodeLocation(mapLocationInput.value), 500);
|
||
}
|
||
}
|
||
}
|
||
|
||
function showMapPreview(lat, lng, address = null) {
|
||
const mapPreview = document.getElementById('mapPreview');
|
||
if (!mapPreview) return;
|
||
|
||
const zoom = parseInt(document.getElementById('mapZoom')?.value) || 15;
|
||
const markerTitle = document.getElementById('mapMarkerTitle')?.value || address || 'Location';
|
||
|
||
// Calculate bbox for proper zoom - smaller bbox = more zoomed in
|
||
// For zoom level 15, use approximately 0.01 degree = ~1km
|
||
const zoomDelta = {
|
||
10: 0.1,
|
||
11: 0.05,
|
||
12: 0.025,
|
||
13: 0.0125,
|
||
14: 0.006,
|
||
15: 0.003,
|
||
16: 0.0015,
|
||
17: 0.00075,
|
||
18: 0.000375
|
||
};
|
||
|
||
const delta = zoomDelta[zoom] || 0.003;
|
||
const latDelta = delta;
|
||
const lngDelta = delta * 1.5; // Adjust for latitude
|
||
|
||
mapPreview.innerHTML = `
|
||
<iframe
|
||
width="100%"
|
||
height="100%"
|
||
style="border:0; border-radius: 4px;"
|
||
src="https://www.openstreetmap.org/export/embed.html?bbox=${lng-lngDelta},${lat-latDelta},${lng+lngDelta},${lat+latDelta}&layer=mapnik&marker=${lat},${lng}"
|
||
frameborder="0">
|
||
</iframe>
|
||
<div class="mt-2 text-center">
|
||
<small class="text-muted">📍 ${markerTitle}</small>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
async function geocodeLocation(address) {
|
||
const mapPreview = document.getElementById('mapPreview');
|
||
if (!mapPreview) return;
|
||
|
||
try {
|
||
mapPreview.innerHTML = '<div class="text-center p-4"><i class="fas fa-spinner fa-spin me-2"></i>Finding location...</div>';
|
||
|
||
// Use Nominatim API (free, no key required)
|
||
const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(address)}&limit=1`);
|
||
const data = await response.json();
|
||
|
||
if (data && data.length > 0) {
|
||
const result = data[0];
|
||
const lat = parseFloat(result.lat);
|
||
const lng = parseFloat(result.lon);
|
||
|
||
// Update hidden fields
|
||
document.getElementById('mapLat').value = lat;
|
||
document.getElementById('mapLng').value = lng;
|
||
|
||
// Show map preview with proper zoom
|
||
showMapPreview(lat, lng, address);
|
||
} else {
|
||
mapPreview.innerHTML = '<div class="text-center p-4 text-danger"><i class="fas fa-exclamation-triangle me-2"></i>Location not found. Please try a more specific address.</div>';
|
||
}
|
||
} catch (error) {
|
||
console.error('Geocoding error:', error);
|
||
mapPreview.innerHTML = '<div class="text-center p-4 text-danger"><i class="fas fa-exclamation-triangle me-2"></i>Error finding location. Please try again.</div>';
|
||
}
|
||
}
|
||
|
||
function openImageUploader(targetInput, imageType) {
|
||
const fileInput = document.createElement('input');
|
||
fileInput.type = 'file';
|
||
fileInput.accept = 'image/*';
|
||
fileInput.style.display = 'none';
|
||
document.body.appendChild(fileInput);
|
||
|
||
fileInput.onchange = async function (e) {
|
||
const file = e.target.files[0];
|
||
if (!file) return;
|
||
|
||
try {
|
||
const formData = new FormData();
|
||
formData.append('image', file);
|
||
|
||
const uploadBtn = document.querySelector(`[data-target-input="${targetInput}"]`);
|
||
const originalBtnHtml = uploadBtn.innerHTML;
|
||
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');
|
||
}
|
||
|
||
const input = document.getElementById(targetInput) || document.querySelector(`[name="${targetInput}"]`);
|
||
if (!input) {
|
||
throw new Error('Target input not found');
|
||
}
|
||
|
||
const previewUrl = (result.path && (result.path.startsWith('http://') || result.path.startsWith('https://'))) ? result.path : (window.location.origin + result.path);
|
||
|
||
input.value = result.path;
|
||
|
||
// Handle hero image preview
|
||
if (targetInput === 'heroBackgroundImage') {
|
||
const heroPreview = document.getElementById('heroImagePreview');
|
||
if (heroPreview) {
|
||
let previewImg = document.getElementById('heroPreviewImg');
|
||
if (previewImg) {
|
||
previewImg.src = previewUrl;
|
||
previewImg.style.display = 'block';
|
||
previewImg.nextElementSibling.style.display = 'none';
|
||
} else {
|
||
heroPreview.innerHTML = `
|
||
<img src="${previewUrl}" class="img-thumbnail" id="heroPreviewImg"
|
||
style="height: 300px; width: 100%; object-fit: cover;"
|
||
alt="Background image preview">
|
||
`;
|
||
}
|
||
}
|
||
} else {
|
||
// Handle contact card icon image previews
|
||
if (targetInput.startsWith('cardIconImage_')) {
|
||
const index = targetInput.replace('cardIconImage_', '');
|
||
const previewImg = document.querySelector(`.icon-image-preview[data-index="${index}"]`);
|
||
if (previewImg) {
|
||
previewImg.src = previewUrl;
|
||
previewImg.style.display = 'block';
|
||
}
|
||
} else {
|
||
// Handle other image previews
|
||
let card = input.closest('.card');
|
||
let previewImg = card ? card.querySelector('img') : null;
|
||
|
||
if (!previewImg) {
|
||
const parent = input.parentElement || input.closest('.input-group') || input.closest('div');
|
||
previewImg = parent ? parent.querySelector('.uploaded-preview') : null;
|
||
}
|
||
|
||
if (previewImg) {
|
||
previewImg.src = previewUrl;
|
||
previewImg.style.display = 'block';
|
||
} else {
|
||
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';
|
||
|
||
const parent = input.parentElement || input.closest('.input-group') || input.closest('div');
|
||
if (parent) parent.appendChild(img);
|
||
}
|
||
}
|
||
}
|
||
|
||
showToast('Success', 'Image uploaded successfully', 'success');
|
||
|
||
uploadBtn.disabled = false;
|
||
uploadBtn.innerHTML = originalBtnHtml;
|
||
|
||
} catch (error) {
|
||
console.error('Upload error:', error);
|
||
showToast('Error', 'Failed to upload image: ' + error.message, 'error');
|
||
|
||
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 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>
|
||
`;
|
||
|
||
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);
|
||
|
||
const bsToast = new bootstrap.Toast(toast, {
|
||
animation: true,
|
||
autohide: true,
|
||
delay: 3000
|
||
});
|
||
bsToast.show();
|
||
|
||
toast.addEventListener('hidden.bs.toast', () => {
|
||
toast.remove();
|
||
});
|
||
}
|
||
|
||
function updateAllJsonInputs(data) {
|
||
document.getElementById('heroJson').value = JSON.stringify(data.hero || {});
|
||
document.getElementById('contactCardsJson').value = JSON.stringify(data.contactCards || []);
|
||
document.getElementById('mapJson').value = JSON.stringify(data.map || {});
|
||
document.getElementById('formJson').value = JSON.stringify(data.form || {});
|
||
|
||
populateContactCardsFromData(data.contactCards || []);
|
||
populateFormFieldsFromData(data.form?.fields || []);
|
||
}
|
||
|
||
function addContactCard() {
|
||
const container = document.getElementById('contactCardsContainer');
|
||
const index = container.children.length;
|
||
const html = `
|
||
<div class="card mb-3 contact-card-item">
|
||
<div class="card-body">
|
||
<div class="row g-3">
|
||
<div class="col-md-3">
|
||
<label class="form-label">Type</label>
|
||
<select class="form-select card-type-select" name="cardType_${index}" data-index="${index}">
|
||
<option value="phone">Phone</option>
|
||
<option value="email">Email</option>
|
||
<option value="location">Location</option>
|
||
<option value="hours">Hours</option>
|
||
<option value="website">Website</option>
|
||
<option value="social">Social Media</option>
|
||
<option value="custom">Custom</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label class="form-label">Title</label>
|
||
<input type="text" class="form-control" name="cardTitle_${index}">
|
||
</div>
|
||
<div class="col-md-12">
|
||
<label class="form-label">Icon Type</label>
|
||
<div class="btn-group w-100 mb-3" role="group">
|
||
<input type="radio" class="btn-check icon-source-radio" name="cardIconSource_${index}"
|
||
id="iconSource_fa_${index}" value="fontawesome" checked
|
||
data-index="${index}" onchange="handleIconSourceChange(this)">
|
||
<label class="btn btn-outline-primary" for="iconSource_fa_${index}">
|
||
<i class="fas fa-icons me-2"></i>Font Awesome
|
||
</label>
|
||
|
||
<input type="radio" class="btn-check icon-source-radio" name="cardIconSource_${index}"
|
||
id="iconSource_img_${index}" value="image"
|
||
data-index="${index}" onchange="handleIconSourceChange(this)">
|
||
<label class="btn btn-outline-primary" for="iconSource_img_${index}">
|
||
<i class="fas fa-image me-2"></i>Upload Image
|
||
</label>
|
||
</div>
|
||
|
||
<!-- Font Awesome Icon Selector -->
|
||
<div class="icon-fa-container" data-index="${index}">
|
||
<label class="form-label">Select Font Awesome Icon</label>
|
||
<select class="form-select fa-icon-select" name="cardFaIcon_${index}" data-index="${index}" onchange="updateFaIconPreview(this)">
|
||
<option value="">-- Select Icon --</option>
|
||
<option value="fas fa-phone">📞 Phone</option>
|
||
<option value="fas fa-envelope">✉️ Email</option>
|
||
<option value="fas fa-map-marker-alt">📍 Location</option>
|
||
<option value="fas fa-clock">🕐 Clock</option>
|
||
<option value="fas fa-globe">🌐 Website</option>
|
||
<option value="fas fa-calendar">📅 Calendar</option>
|
||
<option value="fas fa-user">👤 User</option>
|
||
<option value="fas fa-users">👥 Users</option>
|
||
<option value="fas fa-building">🏢 Building</option>
|
||
<option value="fas fa-home">🏠 Home</option>
|
||
<option value="fab fa-facebook">📘 Facebook</option>
|
||
<option value="fab fa-twitter">🐦 Twitter</option>
|
||
<option value="fab fa-instagram">📷 Instagram</option>
|
||
<option value="fab fa-linkedin">💼 LinkedIn</option>
|
||
<option value="fab fa-youtube">📺 YouTube</option>
|
||
<option value="fas fa-mobile-alt">📱 Mobile</option>
|
||
<option value="fas fa-fax">📠 Fax</option>
|
||
<option value="fas fa-headset">🎧 Support</option>
|
||
<option value="fas fa-info-circle">ℹ️ Info</option>
|
||
</select>
|
||
<small class="text-muted">Choose a Font Awesome icon from the list</small>
|
||
<div class="mt-2 fa-icon-preview" data-index="${index}"></div>
|
||
</div>
|
||
|
||
<!-- Upload Image -->
|
||
<div class="icon-image-container" data-index="${index}" style="display: none;">
|
||
<label class="form-label">Upload Icon Image</label>
|
||
<div class="input-group">
|
||
<input type="text" class="form-control card-icon-image-input" name="cardIconImage_${index}"
|
||
placeholder="/uploads/icon.png">
|
||
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
||
data-target-input="cardIconImage_${index}" data-image-type="contact">
|
||
<i class="fas fa-upload me-1"></i>Upload Icon
|
||
</button>
|
||
</div>
|
||
<img src="" class="img-thumbnail mt-2 icon-image-preview" data-index="${index}" style="max-height: 100px; width: auto; display: none;" alt="Icon preview">
|
||
<small class="text-muted">Upload a custom icon image for this contact card</small>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-12">
|
||
<label class="form-label">Content (one per line)</label>
|
||
<textarea class="form-control" name="cardContent_${index}" rows="3"></textarea>
|
||
<small class="text-muted">Enter each content item on a new line</small>
|
||
</div>
|
||
</div>
|
||
<button type="button" class="btn btn-outline-danger btn-sm mt-3" onclick="removeContactCard(this)">
|
||
<i class="fas fa-trash me-2"></i>Remove Card
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
container.insertAdjacentHTML('beforeend', html);
|
||
|
||
// Initialize upload button for new card
|
||
const newCard = container.lastElementChild;
|
||
const uploadBtn = newCard.querySelector('.btn-upload-image');
|
||
if (uploadBtn) {
|
||
uploadBtn.addEventListener('click', function() {
|
||
const targetInput = this.dataset.targetInput;
|
||
const imageType = this.dataset.imageType;
|
||
openImageUploader(targetInput, imageType);
|
||
});
|
||
}
|
||
}
|
||
|
||
function handleIconSourceChange(radio) {
|
||
const index = radio.dataset.index;
|
||
const source = radio.value;
|
||
const faContainer = document.querySelector(`.icon-fa-container[data-index="${index}"]`);
|
||
const imageContainer = document.querySelector(`.icon-image-container[data-index="${index}"]`);
|
||
|
||
if (source === 'fontawesome') {
|
||
if (faContainer) faContainer.style.display = 'block';
|
||
if (imageContainer) imageContainer.style.display = 'none';
|
||
} else {
|
||
if (faContainer) faContainer.style.display = 'none';
|
||
if (imageContainer) imageContainer.style.display = 'block';
|
||
}
|
||
}
|
||
|
||
function updateFaIconPreview(select) {
|
||
const index = select.dataset.index;
|
||
const iconClass = select.value;
|
||
const preview = document.querySelector(`.fa-icon-preview[data-index="${index}"]`);
|
||
|
||
if (preview) {
|
||
if (iconClass) {
|
||
preview.innerHTML = `<i class="${iconClass} fa-2x text-primary"></i>`;
|
||
} else {
|
||
preview.innerHTML = '';
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
function removeContactCard(button) {
|
||
button.closest('.contact-card-item').remove();
|
||
}
|
||
|
||
function addFormField() {
|
||
const container = document.getElementById('formFieldsContainer');
|
||
const index = container.children.length;
|
||
const html = `
|
||
<div class="card mb-3 form-field-item">
|
||
<div class="card-body">
|
||
<div class="row g-3">
|
||
<div class="col-md-3">
|
||
<label class="form-label">Label</label>
|
||
<input type="text" class="form-control" name="fieldName_${index}" placeholder="e.g., Your Name">
|
||
</div>
|
||
<div class="col-md-3">
|
||
<label class="form-label">Field Type</label>
|
||
<select class="form-select field-type-select" name="fieldType_${index}" data-index="${index}" onchange="handleFieldTypeChange(this)">
|
||
<option value="text">Text</option>
|
||
<option value="email">Email</option>
|
||
<option value="tel">Tel</option>
|
||
<option value="textarea">Textarea</option>
|
||
<option value="programme">Programme</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Placeholder</label>
|
||
<input type="text" class="form-control" name="fieldPlaceholder_${index}">
|
||
</div>
|
||
<div class="col-md-2">
|
||
<label class="form-label">Required</label>
|
||
<div class="form-check mt-2">
|
||
<input class="form-check-input" type="checkbox" name="fieldRequired_${index}">
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3 programme-name-field" style="display: none;">
|
||
<label class="form-label">Programme Name</label>
|
||
<input type="text" class="form-control" name="fieldProgrammeName_${index}" placeholder="e.g., Summer Camp 2024">
|
||
<small class="text-muted">Internal name for the programme</small>
|
||
</div>
|
||
</div>
|
||
<button type="button" class="btn btn-outline-danger btn-sm mt-3" onclick="removeFormField(this)">
|
||
<i class="fas fa-trash me-2"></i>Remove Field
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
container.insertAdjacentHTML('beforeend', html);
|
||
}
|
||
|
||
function removeFormField(button) {
|
||
button.closest('.form-field-item').remove();
|
||
}
|
||
|
||
function handleFieldTypeChange(selectElement) {
|
||
const index = selectElement.dataset.index;
|
||
const fieldType = selectElement.value;
|
||
const fieldItem = selectElement.closest('.form-field-item');
|
||
const labelInput = fieldItem.querySelector(`[name="fieldName_${index}"]`);
|
||
const programmeNameField = fieldItem.querySelector('.programme-name-field');
|
||
|
||
if (fieldType === 'programme') {
|
||
// Auto-generate label for programme field
|
||
if (labelInput && (!labelInput.value || labelInput.value.trim() === '')) {
|
||
labelInput.value = 'Programme that you are looking for';
|
||
}
|
||
// Show programme name field
|
||
if (programmeNameField) {
|
||
programmeNameField.style.display = 'block';
|
||
}
|
||
} else {
|
||
// Hide programme name field
|
||
if (programmeNameField) {
|
||
programmeNameField.style.display = 'none';
|
||
}
|
||
}
|
||
}
|
||
|
||
function updateJsonData() {
|
||
try {
|
||
// Hero
|
||
const heroSectionClassEl = document.getElementById('heroSectionClass');
|
||
const heroTitleClassEl = document.getElementById('heroTitleClass');
|
||
const heroEnableScrollspyEl = document.getElementById('heroEnableScrollspy');
|
||
const heroBackgroundPositionEl = document.getElementById('heroBackgroundPosition');
|
||
|
||
const heroData = {
|
||
title: (document.getElementById('heroTitle') || {}).value?.trim() || '',
|
||
backgroundImage: (document.getElementById('heroBackgroundImage') || {}).value?.trim() || '',
|
||
overlayColor: (document.getElementById('heroOverlayColor') || {}).value?.trim() || 'rgba(0, 0, 0, 0)',
|
||
sectionClass: heroSectionClassEl?.value?.trim() || (originalFormData?.hero?.sectionClass || ''),
|
||
titleClass: heroTitleClassEl?.value?.trim() || (originalFormData?.hero?.titleClass || ''),
|
||
enableScrollspy: heroEnableScrollspyEl?.value === 'true' || (originalFormData?.hero?.enableScrollspy || false),
|
||
backgroundPosition: heroBackgroundPositionEl?.value?.trim() || (originalFormData?.hero?.backgroundPosition || 'center')
|
||
};
|
||
document.getElementById('heroJson').value = JSON.stringify(heroData);
|
||
|
||
// Contact Cards
|
||
const contactCardsData = Array.from(document.querySelectorAll('.contact-card-item'))
|
||
.map((item) => {
|
||
const typeEl = item.querySelector('[name^="cardType_"]');
|
||
const iconSourceRadio = item.querySelector('.icon-source-radio:checked');
|
||
const faIconEl = item.querySelector('[name^="cardFaIcon_"]');
|
||
const imageIconEl = item.querySelector('[name^="cardIconImage_"]');
|
||
const titleEl = item.querySelector('[name^="cardTitle_"]');
|
||
const contentEl = item.querySelector('[name^="cardContent_"]');
|
||
const content = (contentEl?.value || '').split('\n').filter(line => line.trim() !== '');
|
||
|
||
const iconSource = (iconSourceRadio?.value || 'fontawesome');
|
||
let iconType = '';
|
||
|
||
if (iconSource === 'fontawesome') {
|
||
iconType = (faIconEl?.value || '').trim();
|
||
} else {
|
||
iconType = (imageIconEl?.value || '').trim();
|
||
}
|
||
|
||
return {
|
||
type: (typeEl?.value || '').trim(),
|
||
iconType: iconType,
|
||
iconSource: iconSource,
|
||
title: (titleEl?.value || '').trim(),
|
||
content: content
|
||
};
|
||
})
|
||
.filter(card => card.type !== '' || card.title !== '');
|
||
document.getElementById('contactCardsJson').value = JSON.stringify(contactCardsData);
|
||
|
||
// Map
|
||
const mapData = {
|
||
coordinates: {
|
||
lat: parseFloat((document.getElementById('mapLat') || {}).value) || 0,
|
||
lng: parseFloat((document.getElementById('mapLng') || {}).value) || 0
|
||
},
|
||
zoom: parseInt((document.getElementById('mapZoom') || {}).value) || 15,
|
||
location: (document.getElementById('mapLocation') || {}).value?.trim() || '',
|
||
markerTitle: (document.getElementById('mapMarkerTitle') || {}).value?.trim() || '',
|
||
tileLayer: {
|
||
url: (document.getElementById('tileLayerUrl') || {}).value?.trim() || '',
|
||
attribution: (document.getElementById('tileLayerAttribution') || {}).value?.trim() || '',
|
||
maxZoom: parseInt((document.getElementById('tileLayerMaxZoom') || {}).value) || 18,
|
||
minZoom: parseInt((document.getElementById('tileLayerMinZoom') || {}).value) || 0
|
||
}
|
||
};
|
||
document.getElementById('mapJson').value = JSON.stringify(mapData);
|
||
|
||
// Form
|
||
const formFieldsData = Array.from(document.querySelectorAll('.form-field-item'))
|
||
.map((item) => {
|
||
const nameEl = item.querySelector('[name^="fieldName_"]');
|
||
const typeEl = item.querySelector('[name^="fieldType_"]');
|
||
const placeholderEl = item.querySelector('[name^="fieldPlaceholder_"]');
|
||
const requiredEl = item.querySelector('[name^="fieldRequired_"]');
|
||
const programmeNameEl = item.querySelector('[name^="fieldProgrammeName_"]');
|
||
|
||
const fieldData = {
|
||
name: (nameEl?.value || '').trim(),
|
||
type: (typeEl?.value || '').trim(),
|
||
placeholder: (placeholderEl?.value || '').trim(),
|
||
required: (requiredEl?.checked || false)
|
||
};
|
||
|
||
// Add programmeName if field type is programme
|
||
if (fieldData.type === 'programme' && programmeNameEl) {
|
||
fieldData.programmeName = (programmeNameEl?.value || '').trim();
|
||
}
|
||
|
||
return fieldData;
|
||
})
|
||
.filter(field => field.name !== '');
|
||
|
||
const formData = {
|
||
sectionLabel: (document.getElementById('formSectionLabel') || {}).value?.trim() || '',
|
||
heading: (document.getElementById('formHeading') || {}).value?.trim() || '',
|
||
fields: formFieldsData,
|
||
submitButton: {
|
||
text: (document.getElementById('formSubmitButtonText') || {}).value?.trim() || 'Send Message'
|
||
}
|
||
};
|
||
document.getElementById('formJson').value = JSON.stringify(formData);
|
||
|
||
} catch (error) {
|
||
console.error('Error updating JSON data:', error);
|
||
throw new Error('Failed to process form data');
|
||
}
|
||
}
|
||
|
||
function populateContactCardsFromData(cards) {
|
||
const container = document.getElementById('contactCardsContainer');
|
||
container.innerHTML = '';
|
||
(cards || []).forEach((card, i) => {
|
||
addContactCard();
|
||
const el = container.lastElementChild;
|
||
|
||
// Set basic fields
|
||
el.querySelector(`[name="cardType_${i}"]`).value = card.type || '';
|
||
el.querySelector(`[name="cardTitle_${i}"]`).value = card.title || '';
|
||
el.querySelector(`[name="cardContent_${i}"]`).value = (card.content || []).join('\n');
|
||
|
||
// Determine icon source
|
||
const iconType = card.iconType || '';
|
||
const iconSource = card.iconSource || (iconType && iconType.startsWith('/uploads/') ? 'image' : 'fontawesome');
|
||
|
||
// Set icon source radio button
|
||
const faRadio = el.querySelector(`#iconSource_fa_${i}`);
|
||
const imgRadio = el.querySelector(`#iconSource_img_${i}`);
|
||
if (iconSource === 'image') {
|
||
if (imgRadio) imgRadio.checked = true;
|
||
handleIconSourceChange(imgRadio);
|
||
} else {
|
||
if (faRadio) faRadio.checked = true;
|
||
handleIconSourceChange(faRadio);
|
||
}
|
||
|
||
// Set icon value based on source
|
||
if (iconSource === 'fontawesome') {
|
||
const faSelect = el.querySelector(`[name="cardFaIcon_${i}"]`);
|
||
if (faSelect && iconType) {
|
||
faSelect.value = iconType;
|
||
updateFaIconPreview(faSelect);
|
||
}
|
||
} else {
|
||
const imageInput = el.querySelector(`[name="cardIconImage_${i}"]`);
|
||
if (imageInput && iconType) {
|
||
imageInput.value = iconType;
|
||
|
||
// Show preview image
|
||
const previewImg = el.querySelector(`.icon-image-preview[data-index="${i}"]`);
|
||
if (previewImg) {
|
||
const previewUrl = (iconType.startsWith('http://') || iconType.startsWith('https://'))
|
||
? iconType
|
||
: (window.location.origin + iconType);
|
||
previewImg.src = previewUrl;
|
||
previewImg.style.display = 'block';
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function populateFormFieldsFromData(fields) {
|
||
const container = document.getElementById('formFieldsContainer');
|
||
container.innerHTML = '';
|
||
(fields || []).forEach((field, i) => {
|
||
addFormField();
|
||
const el = container.lastElementChild;
|
||
el.querySelector(`[name="fieldName_${i}"]`).value = field.name || '';
|
||
el.querySelector(`[name="fieldType_${i}"]`).value = field.type || '';
|
||
el.querySelector(`[name="fieldPlaceholder_${i}"]`).value = field.placeholder || '';
|
||
el.querySelector(`[name="fieldRequired_${i}"]`).checked = field.required || false;
|
||
|
||
// Handle programme type - show/hide programme name field and set value
|
||
if (field.type === 'programme') {
|
||
const typeSelect = el.querySelector(`[name="fieldType_${i}"]`);
|
||
if (typeSelect) {
|
||
handleFieldTypeChange(typeSelect);
|
||
}
|
||
|
||
const programmeNameInput = el.querySelector(`[name="fieldProgrammeName_${i}"]`);
|
||
if (programmeNameInput && field.programmeName) {
|
||
programmeNameInput.value = field.programmeName || '';
|
||
}
|
||
}
|
||
});
|
||
}
|
||
</script>
|
||
|