forked from UKSOURCE/cms.lams
599 lines
30 KiB
Plaintext
599 lines
30 KiB
Plaintext
<div class="container">
|
|
<div class="d-flex justify-content-between align-items-center mt-4 mb-4 flex-wrap gap-2">
|
|
<div>
|
|
<h1 class="h3 mb-0" style="color: var(--primary-dark);"><%= title %></h1>
|
|
<p class="text-muted mb-0">Edit the public Student Support page: hero, services directory, contact channels, and form labels.</p>
|
|
</div>
|
|
<div class="d-flex gap-2">
|
|
<% if (frontendUrl) { %>
|
|
<a href="<%= frontendUrl %>/student-support" class="btn btn-outline-primary" target="_blank" rel="noopener">
|
|
<i class="fas fa-external-link-alt me-2"></i>View Student Support Page
|
|
</a>
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="POST" class="content-with-fixed-buttons" id="studentSupportForm" action="/admin/student-support/update">
|
|
<input type="hidden" name="metadata" id="metadataJson">
|
|
<input type="hidden" name="hero" id="heroJson">
|
|
<input type="hidden" name="directory" id="directoryJson">
|
|
<input type="hidden" name="contactSection" id="contactSectionJson">
|
|
|
|
<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="#tab-meta" role="tab">
|
|
<i class="fas fa-tags me-2"></i>Metadata
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-bs-toggle="tab" href="#tab-hero" role="tab">
|
|
<i class="fas fa-image me-2"></i>Hero
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-bs-toggle="tab" href="#tab-directory" role="tab">
|
|
<i class="fas fa-list me-2"></i>Directory
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" data-bs-toggle="tab" href="#tab-contact" role="tab">
|
|
<i class="fas fa-headset me-2"></i>Contact & form
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="tab-content">
|
|
<div class="tab-pane fade show active" id="tab-meta" role="tabpanel">
|
|
<div class="row g-3">
|
|
<div class="col-md-12">
|
|
<label class="form-label fw-medium" for="metaTitle">Title (SEO)</label>
|
|
<input type="text" class="form-control" id="metaTitle" value="<%= data.metadata?.title || '' %>">
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label class="form-label fw-medium" for="metaDescription">Description (SEO)</label>
|
|
<textarea class="form-control" id="metaDescription" rows="3"><%= data.metadata?.description || '' %></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="tab-hero" role="tabpanel">
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-medium" for="heroBadge">Badge</label>
|
|
<input type="text" class="form-control" id="heroBadge" value="<%= data.hero?.badge || '' %>">
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label class="form-label fw-medium" for="heroTitle">Title</label>
|
|
<input type="text" class="form-control" id="heroTitle" value="<%= data.hero?.title || '' %>">
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label class="form-label fw-medium" for="heroDescription">Description</label>
|
|
<textarea class="form-control" id="heroDescription" rows="4"><%= data.hero?.description || '' %></textarea>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Primary button</label>
|
|
<input type="text" class="form-control mb-2" id="heroPrimaryLabel" placeholder="Label" value="<%= data.hero?.primaryButton?.label || '' %>">
|
|
<input type="text" class="form-control" id="heroPrimaryHref" placeholder="Href" value="<%= data.hero?.primaryButton?.href || '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">Secondary button</label>
|
|
<input type="text" class="form-control mb-2" id="heroSecondaryLabel" placeholder="Label" value="<%= data.hero?.secondaryButton?.label || '' %>">
|
|
<input type="text" class="form-control" id="heroSecondaryHref" placeholder="Href" value="<%= data.hero?.secondaryButton?.href || '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-medium" for="heroImage">Hero image</label>
|
|
<div class="input-group mb-2">
|
|
<input type="text" class="form-control" id="heroImage" value="<%= data.hero?.image || '' %>">
|
|
<button type="button" class="btn btn-outline-primary btn-upload-image" data-target-input="heroImage" data-image-type="student-support">
|
|
<i class="fas fa-upload me-1"></i>Upload
|
|
</button>
|
|
</div>
|
|
<small class="text-muted">Full URL or relative path such as <code>/uploads/...</code> after upload.</small>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label fw-medium" for="heroImageAlt">Image alt</label>
|
|
<input type="text" class="form-control" id="heroImageAlt" value="<%= data.hero?.imageAlt || '' %>">
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label text-muted small">Preview</label>
|
|
<div id="heroImagePreview" class="border rounded overflow-hidden bg-light" style="height: 380px; width: 100%;">
|
|
<% const hi = data.hero?.image; if (hi) { %>
|
|
<% let src = hi; if (!src.startsWith('http')) { src = src.startsWith('/') ? src : '/' + src; } %>
|
|
<img src="<%= src %>" alt="" class="w-100 h-100" style="object-fit: cover; object-position: center;" id="heroPreviewImg"
|
|
onerror="this.style.display='none'">
|
|
<% } else { %>
|
|
<div class="h-100 d-flex align-items-center justify-content-center text-muted">
|
|
<div><i class="fas fa-image fa-2x mb-2"></i><br>No image</div>
|
|
</div>
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="tab-directory" role="tabpanel">
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-12">
|
|
<label class="form-label fw-medium" for="dirTitle">Section title</label>
|
|
<input type="text" class="form-control" id="dirTitle" value="<%= data.directory?.title || '' %>">
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label class="form-label fw-medium" for="dirSubtitle">Subtitle</label>
|
|
<textarea class="form-control" id="dirSubtitle" rows="2"><%= data.directory?.subtitle || '' %></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<h6 class="mb-0">Services</h6>
|
|
<button type="button" class="btn btn-sm btn-primary" id="addServiceBtn"><i class="fas fa-plus me-1"></i>Add service</button>
|
|
</div>
|
|
<div id="servicesContainer" class="vstack gap-3">
|
|
<% const services = data.directory?.services || []; %>
|
|
<% services.forEach(function(svc, index) { %>
|
|
<div class="card service-row border">
|
|
<div class="card-body">
|
|
<div class="row g-2">
|
|
<div class="col-md-4">
|
|
<label class="form-label small">Icon</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text icon-preview-cell" style="min-width:38px">
|
|
<% if (svc.icon) { %><i class="<%= svc.icon %>"></i><% } %>
|
|
</span>
|
|
<input type="text" class="form-control ch-icon"
|
|
value="<%= svc.icon || '' %>" placeholder="Click to pick..." readonly
|
|
style="cursor:pointer;background:#fff" onclick="IconPicker.open(this)">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label small">Hours icon</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text icon-preview-cell" style="min-width:38px">
|
|
<% if (svc.hoursIcon) { %><i class="<%= svc.hoursIcon %>"></i><% } %>
|
|
</span>
|
|
<input type="text" class="form-control ch-icon"
|
|
value="<%= svc.hoursIcon || '' %>" placeholder="Click to pick..." readonly
|
|
style="cursor:pointer;background:#fff" onclick="IconPicker.open(this)">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 d-flex align-items-end justify-content-end">
|
|
<button type="button" class="btn btn-outline-danger btn-sm remove-service"><i class="fas fa-trash"></i></button>
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label class="form-label small">Title</label>
|
|
<input type="text" class="form-control svc-title" value="<%= svc.title || '' %>">
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label class="form-label small">Description</label>
|
|
<textarea class="form-control svc-description" rows="2"><%= svc.description || '' %></textarea>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small">Hours text</label>
|
|
<input type="text" class="form-control svc-hours" value="<%= svc.hours || '' %>">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label small">Button label</label>
|
|
<input type="text" class="form-control svc-btn-label" value="<%= svc.buttonLabel || '' %>">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label small">Button href</label>
|
|
<input type="text" class="form-control svc-btn-href" value="<%= svc.buttonHref || '' %>">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% }); %>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-pane fade" id="tab-contact" role="tabpanel">
|
|
<% const cs = data.contactSection || {}; const fm = cs.form || {}; const fld = fm.fields || {}; %>
|
|
<h6 class="text-uppercase text-muted small">Contact channels</h6>
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-12">
|
|
<label class="form-label" for="csHeading">Heading</label>
|
|
<input type="text" class="form-control" id="csHeading" value="<%= cs.heading || '' %>">
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label class="form-label" for="csDescription">Description</label>
|
|
<textarea class="form-control" id="csDescription" rows="2"><%= cs.description || '' %></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<span class="fw-medium">Channels</span>
|
|
<button type="button" class="btn btn-sm btn-primary" id="addChannelBtn"><i class="fas fa-plus me-1"></i>Add channel</button>
|
|
</div>
|
|
<div id="channelsContainer" class="vstack gap-2 mb-4">
|
|
<% (cs.channels || []).forEach(function(ch) { %>
|
|
<div class="card channel-row border">
|
|
<div class="card-body py-2">
|
|
<div class="row g-2 align-items-end">
|
|
<div class="col-md-3">
|
|
<label class="form-label small">Icon</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text icon-preview-cell" style="min-width:38px">
|
|
<% if (ch.icon) { %><i class="<%= ch.icon %>"></i><% } %>
|
|
</span>
|
|
<input type="text" class="form-control ch-icon"
|
|
value="<%= ch.icon || '' %>" placeholder="Click to pick..." readonly
|
|
style="cursor:pointer;background:#fff" onclick="IconPicker.open(this)">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label small">Title</label>
|
|
<input type="text" class="form-control ch-title" value="<%= ch.title || '' %>">
|
|
</div>
|
|
<div class="col-md-5">
|
|
<label class="form-label small">Detail</label>
|
|
<input type="text" class="form-control ch-detail" value="<%= ch.detail || '' %>">
|
|
</div>
|
|
<div class="col-md-1 text-end">
|
|
<button type="button" class="btn btn-outline-danger btn-sm remove-channel"><i class="fas fa-trash"></i></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% }); %>
|
|
</div>
|
|
|
|
<h6 class="text-uppercase text-muted small">Form (display labels)</h6>
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-12">
|
|
<label class="form-label" for="formHeading">Form heading</label>
|
|
<input type="text" class="form-control" id="formHeading" value="<%= fm.heading || '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small">First name — label</label>
|
|
<input type="text" class="form-control" id="fld_firstName_label" value="<%= fld.firstName?.label || '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small">First name — placeholder</label>
|
|
<input type="text" class="form-control" id="fld_firstName_ph" value="<%= fld.firstName?.placeholder || '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small">Last name — label</label>
|
|
<input type="text" class="form-control" id="fld_lastName_label" value="<%= fld.lastName?.label || '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small">Last name — placeholder</label>
|
|
<input type="text" class="form-control" id="fld_lastName_ph" value="<%= fld.lastName?.placeholder || '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small">Student ID — label</label>
|
|
<input type="text" class="form-control" id="fld_studentId_label" value="<%= fld.studentId?.label || '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small">Student ID — placeholder</label>
|
|
<input type="text" class="form-control" id="fld_studentId_ph" value="<%= fld.studentId?.placeholder || '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small">Department — label</label>
|
|
<input type="text" class="form-control" id="fld_department_label" value="<%= fld.department?.label || '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small">Message — label</label>
|
|
<input type="text" class="form-control" id="fld_message_label" value="<%= fld.message?.label || '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small">Message — placeholder</label>
|
|
<input type="text" class="form-control" id="fld_message_ph" value="<%= fld.message?.placeholder || '' %>">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small">Submit label</label>
|
|
<input type="text" class="form-control" id="formSubmitLabel" value="<%= fm.submitLabel || '' %>">
|
|
</div>
|
|
<div class="col-md-12">
|
|
<label class="form-label small">Success message</label>
|
|
<input type="text" class="form-control" id="formSuccessMsg" value="<%= fm.successMessage || '' %>">
|
|
</div>
|
|
</div>
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<span class="fw-medium">Departments (dropdown)</span>
|
|
<button type="button" class="btn btn-sm btn-secondary" id="addDeptBtn"><i class="fas fa-plus me-1"></i>Add row</button>
|
|
</div>
|
|
<div id="departmentsContainer" class="vstack gap-2">
|
|
<% (fm.departments || []).forEach(function(dep) { %>
|
|
<div class="input-group dept-row">
|
|
<input type="text" class="form-control dept-input" value="<%= dep %>">
|
|
<button type="button" class="btn btn-outline-danger remove-dept"><i class="fas fa-times"></i></button>
|
|
</div>
|
|
<% }); %>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="fixed-bottom-buttons">
|
|
<button type="button" class="btn btn-secondary" onclick="resetStudentSupportForm()">
|
|
<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>
|
|
|
|
<script>
|
|
function resetStudentSupportForm() {
|
|
if (confirm('Are you sure you want to reset all changes? Unsaved edits will be lost.')) {
|
|
location.reload();
|
|
}
|
|
}
|
|
|
|
// window.STUDENT_SUPPORT_ICON_OPTIONS = <%- JSON.stringify(iconOptions) %>;
|
|
|
|
function escapeHtml(s) {
|
|
return String(s || '')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"');
|
|
}
|
|
|
|
// function iconOptionsHtml(selected) {
|
|
// var opts = window.STUDENT_SUPPORT_ICON_OPTIONS || [];
|
|
// var html = '';
|
|
// for (var i = 0; i < opts.length; i++) {
|
|
// var o = opts[i];
|
|
// var sel = (o.value === selected) ? ' selected' : '';
|
|
// html += '<option value="' + escapeHtml(o.value) + '"' + sel + '>' + escapeHtml(o.label) + '</option>';
|
|
// }
|
|
// return html;
|
|
// }
|
|
|
|
function buildServiceRow(svc) {
|
|
svc = svc || {};
|
|
var iconSel = '<select class="form-select svc-icon-select">' + iconOptionsHtml(svc.icon) + '</select>';
|
|
var hoursIconSel = '<select class="form-select svc-hours-icon-select">' + iconOptionsHtml(svc.hoursIcon) + '</select>';
|
|
return (
|
|
'<div class="card service-row border">' +
|
|
'<div class="card-body">' +
|
|
'<div class="row g-2">' +
|
|
'<div class="col-md-4"><label class="form-label small">Icon</label>' + iconPickerInput('svc-icon-select', svc.icon) + '</div>' +
|
|
'<div class="col-md-4"><label class="form-label small">Hours icon</label>' + iconPickerInput('svc-hours-icon-select', svc.hoursIcon) + '</div>' +
|
|
'<div class="col-md-4 d-flex align-items-end justify-content-end">' +
|
|
'<button type="button" class="btn btn-outline-danger btn-sm remove-service"><i class="fas fa-trash"></i></button>' +
|
|
'</div>' +
|
|
'<div class="col-md-12"><label class="form-label small">Title</label>' +
|
|
'<input type="text" class="form-control svc-title" value="' + escapeHtml(svc.title) + '"></div>' +
|
|
'<div class="col-md-12"><label class="form-label small">Description</label>' +
|
|
'<textarea class="form-control svc-description" rows="2">' + escapeHtml(svc.description) + '</textarea></div>' +
|
|
'<div class="col-md-6"><label class="form-label small">Hours text</label>' +
|
|
'<input type="text" class="form-control svc-hours" value="' + escapeHtml(svc.hours) + '"></div>' +
|
|
'<div class="col-md-3"><label class="form-label small">Button label</label>' +
|
|
'<input type="text" class="form-control svc-btn-label" value="' + escapeHtml(svc.buttonLabel) + '"></div>' +
|
|
'<div class="col-md-3"><label class="form-label small">Button href</label>' +
|
|
'<input type="text" class="form-control svc-btn-href" value="' + escapeHtml(svc.buttonHref) + '"></div>' +
|
|
'</div></div></div>'
|
|
);
|
|
}
|
|
|
|
function buildChannelRow(ch) {
|
|
ch = ch || {};
|
|
return (
|
|
'<div class="card channel-row border"><div class="card-body py-2">' +
|
|
'<div class="row g-2 align-items-end">' +
|
|
'<div class="col-md-3"><label class="form-label small">Icon</label>' + iconPickerInput('ch-icon-select', ch.icon) + '</div>' +
|
|
'<div class="col-md-3"><label class="form-label small">Title</label>' +
|
|
'<input type="text" class="form-control ch-title" value="' + escapeHtml(ch.title) + '"></div>' +
|
|
'<div class="col-md-5"><label class="form-label small">Detail</label>' +
|
|
'<input type="text" class="form-control ch-detail" value="' + escapeHtml(ch.detail) + '"></div>' +
|
|
'<div class="col-md-1 text-end">' +
|
|
'<button type="button" class="btn btn-outline-danger btn-sm remove-channel"><i class="fas fa-trash"></i></button>' +
|
|
'</div></div></div></div>'
|
|
);
|
|
}
|
|
|
|
function updateHeroPreview(url) {
|
|
var el = document.getElementById('heroImagePreview');
|
|
if (!el) return;
|
|
if (!url) {
|
|
el.innerHTML = '<div class="h-100 d-flex align-items-center justify-content-center text-muted"><div><i class="fas fa-image fa-2x mb-2"></i><br>No image</div></div>';
|
|
return;
|
|
}
|
|
el.classList.remove('d-flex', 'align-items-center', 'justify-content-center');
|
|
el.style.height = '380px';
|
|
el.style.width = '100%';
|
|
var src = url;
|
|
if (!src.startsWith('http://') && !src.startsWith('https://')) {
|
|
src = src.startsWith('/') ? src : '/' + src;
|
|
}
|
|
el.innerHTML = '<img src="' + escapeHtml(src) + '" alt="" class="w-100 h-100" style="object-fit: cover; object-position: center;" id="heroPreviewImg">';
|
|
}
|
|
|
|
function syncHiddenJson() {
|
|
document.getElementById('metadataJson').value = JSON.stringify({
|
|
title: document.getElementById('metaTitle').value.trim(),
|
|
description: document.getElementById('metaDescription').value.trim(),
|
|
});
|
|
document.getElementById('heroJson').value = JSON.stringify({
|
|
badge: document.getElementById('heroBadge').value.trim(),
|
|
title: document.getElementById('heroTitle').value.trim(),
|
|
description: document.getElementById('heroDescription').value.trim(),
|
|
primaryButton: {
|
|
label: document.getElementById('heroPrimaryLabel').value.trim(),
|
|
href: document.getElementById('heroPrimaryHref').value.trim(),
|
|
},
|
|
secondaryButton: {
|
|
label: document.getElementById('heroSecondaryLabel').value.trim(),
|
|
href: document.getElementById('heroSecondaryHref').value.trim(),
|
|
},
|
|
image: document.getElementById('heroImage').value.trim(),
|
|
imageAlt: document.getElementById('heroImageAlt').value.trim(),
|
|
});
|
|
|
|
var services = [];
|
|
document.querySelectorAll('#servicesContainer .service-row').forEach(function(row) {
|
|
services.push({
|
|
icon: row.querySelector('.svc-icon-select').value,
|
|
title: row.querySelector('.svc-title').value.trim(),
|
|
description: row.querySelector('.svc-description').value.trim(),
|
|
hours: row.querySelector('.svc-hours').value.trim(),
|
|
hoursIcon: row.querySelector('.svc-hours-icon-select').value,
|
|
buttonLabel: row.querySelector('.svc-btn-label').value.trim(),
|
|
buttonHref: row.querySelector('.svc-btn-href').value.trim(),
|
|
});
|
|
});
|
|
document.getElementById('directoryJson').value = JSON.stringify({
|
|
title: document.getElementById('dirTitle').value.trim(),
|
|
subtitle: document.getElementById('dirSubtitle').value.trim(),
|
|
services: services,
|
|
});
|
|
|
|
var channels = [];
|
|
document.querySelectorAll('#channelsContainer .channel-row').forEach(function(row) {
|
|
channels.push({
|
|
icon: row.querySelector('.ch-icon-select').value,
|
|
title: row.querySelector('.ch-title').value.trim(),
|
|
detail: row.querySelector('.ch-detail').value.trim(),
|
|
});
|
|
});
|
|
var departments = [];
|
|
document.querySelectorAll('#departmentsContainer .dept-input').forEach(function(inp) {
|
|
var v = inp.value.trim();
|
|
if (v) departments.push(v);
|
|
});
|
|
document.getElementById('contactSectionJson').value = JSON.stringify({
|
|
heading: document.getElementById('csHeading').value.trim(),
|
|
description: document.getElementById('csDescription').value.trim(),
|
|
channels: channels,
|
|
form: {
|
|
heading: document.getElementById('formHeading').value.trim(),
|
|
fields: {
|
|
firstName: {
|
|
label: document.getElementById('fld_firstName_label').value.trim(),
|
|
placeholder: document.getElementById('fld_firstName_ph').value.trim(),
|
|
},
|
|
lastName: {
|
|
label: document.getElementById('fld_lastName_label').value.trim(),
|
|
placeholder: document.getElementById('fld_lastName_ph').value.trim(),
|
|
},
|
|
studentId: {
|
|
label: document.getElementById('fld_studentId_label').value.trim(),
|
|
placeholder: document.getElementById('fld_studentId_ph').value.trim(),
|
|
},
|
|
department: {
|
|
label: document.getElementById('fld_department_label').value.trim(),
|
|
},
|
|
message: {
|
|
label: document.getElementById('fld_message_label').value.trim(),
|
|
placeholder: document.getElementById('fld_message_ph').value.trim(),
|
|
},
|
|
},
|
|
departments: departments,
|
|
submitLabel: document.getElementById('formSubmitLabel').value.trim(),
|
|
successMessage: document.getElementById('formSuccessMsg').value.trim(),
|
|
},
|
|
});
|
|
}
|
|
|
|
function openImageUploader(targetInputId, imageType) {
|
|
var fileInput = document.createElement('input');
|
|
fileInput.type = 'file';
|
|
fileInput.accept = 'image/*';
|
|
fileInput.style.display = 'none';
|
|
document.body.appendChild(fileInput);
|
|
fileInput.onchange = async function (e) {
|
|
var file = e.target.files[0];
|
|
if (!file) return;
|
|
try {
|
|
var formData = new FormData();
|
|
formData.append('image', file);
|
|
var uploadBtn = document.querySelector('[data-target-input="' + targetInputId + '"]');
|
|
var original = uploadBtn ? uploadBtn.innerHTML : '';
|
|
if (uploadBtn) {
|
|
uploadBtn.disabled = true;
|
|
uploadBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>...';
|
|
}
|
|
var res = await fetch('/admin/upload/image?imageType=' + encodeURIComponent(imageType || 'general'), {
|
|
method: 'POST',
|
|
body: formData,
|
|
});
|
|
var result = await res.json();
|
|
if (!result.success) throw new Error(result.error || 'Upload failed');
|
|
var input = document.getElementById(targetInputId);
|
|
if (input) input.value = result.path || '';
|
|
if (targetInputId === 'heroImage') updateHeroPreview(input.value);
|
|
if (uploadBtn) {
|
|
uploadBtn.disabled = false;
|
|
uploadBtn.innerHTML = original;
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
alert('Upload failed: ' + (err.message || err));
|
|
var b = document.querySelector('[data-target-input="' + targetInputId + '"]');
|
|
if (b) { b.disabled = false; b.innerHTML = '<i class="fas fa-upload me-1"></i>Upload'; }
|
|
} finally {
|
|
document.body.removeChild(fileInput);
|
|
}
|
|
};
|
|
fileInput.click();
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
var form = document.getElementById('studentSupportForm');
|
|
form.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
var btn = document.getElementById('submitBtn');
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Saving...';
|
|
try {
|
|
syncHiddenJson();
|
|
form.submit();
|
|
} catch (err) {
|
|
console.error(err);
|
|
alert('Could not prepare form data. Please try again.');
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="fas fa-save me-2"></i>Save Changes';
|
|
}
|
|
});
|
|
|
|
document.querySelectorAll('.btn-upload-image').forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
openImageUploader(this.dataset.targetInput, this.dataset.imageType);
|
|
});
|
|
});
|
|
|
|
var heroImgInput = document.getElementById('heroImage');
|
|
if (heroImgInput) {
|
|
heroImgInput.addEventListener('input', function () { updateHeroPreview(this.value); });
|
|
}
|
|
|
|
document.getElementById('addServiceBtn').addEventListener('click', function () {
|
|
var wrap = document.getElementById('servicesContainer');
|
|
wrap.insertAdjacentHTML('beforeend', buildServiceRow({ icon: 'fa-solid fa-circle-info', hoursIcon: 'fa-regular fa-clock' }));
|
|
});
|
|
document.getElementById('addChannelBtn').addEventListener('click', function () {
|
|
document.getElementById('channelsContainer').insertAdjacentHTML('beforeend', buildChannelRow({ icon: 'fa-solid fa-phone' }));
|
|
});
|
|
document.getElementById('addDeptBtn').addEventListener('click', function () {
|
|
document.getElementById('departmentsContainer').insertAdjacentHTML(
|
|
'beforeend',
|
|
'<div class="input-group dept-row">' +
|
|
'<input type="text" class="form-control dept-input" value="">' +
|
|
'<button type="button" class="btn btn-outline-danger remove-dept"><i class="fas fa-times"></i></button>' +
|
|
'</div>'
|
|
);
|
|
});
|
|
|
|
document.getElementById('servicesContainer').addEventListener('click', function (e) {
|
|
if (e.target.closest('.remove-service')) {
|
|
e.target.closest('.service-row').remove();
|
|
}
|
|
});
|
|
document.getElementById('channelsContainer').addEventListener('click', function (e) {
|
|
if (e.target.closest('.remove-channel')) {
|
|
e.target.closest('.channel-row').remove();
|
|
}
|
|
});
|
|
document.getElementById('departmentsContainer').addEventListener('click', function (e) {
|
|
if (e.target.closest('.remove-dept')) {
|
|
e.target.closest('.dept-row').remove();
|
|
}
|
|
});
|
|
});
|
|
</script>
|