Files
cms.techvanguard.vn/views/admin/about/index.ejs
T

812 lines
48 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 the About page</p>
</div>
</div>
<div class="row">
<div class="col-12">
<form method="POST" id="aboutForm" action="/admin/about-us/update">
<!-- Hidden JSON inputs -->
<input type="hidden" name="hero" id="heroJson">
<input type="hidden" name="leadership" id="leadershipJson">
<input type="hidden" name="learningModel" id="learningModelJson">
<input type="hidden" name="accreditation" id="accreditationJson">
<input type="hidden" name="successStories" id="successStoriesJson">
<input type="hidden" name="cta" id="ctaJson">
<!-- 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" id="aboutTabs" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" data-bs-toggle="tab" href="#tab-hero-pane" role="tab"><i
class="fas fa-image me-2"></i>Hero</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" data-bs-toggle="tab" href="#tab-leadership-pane" role="tab"><i
class="fas fa-users me-2"></i>Leadership</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" data-bs-toggle="tab" href="#tab-learningModel-pane" role="tab"><i
class="fas fa-chalkboard-teacher me-2"></i>Learning Model</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" data-bs-toggle="tab" href="#tab-accreditation-pane" role="tab"><i
class="fas fa-award me-2"></i>Accreditation</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" data-bs-toggle="tab" href="#tab-successStories-pane" role="tab"><i
class="fas fa-quote-left me-2"></i>Success Stories</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" data-bs-toggle="tab" href="#tab-cta-pane" role="tab"><i
class="fas fa-bullhorn me-2"></i>CTA</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<!-- ===== HERO TAB ===== -->
<div class="tab-pane fade show active" id="tab-hero-pane" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="fas fa-image me-2"></i>Hero Section</h6>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Badge</label>
<input type="text" class="form-control" id="heroBadge"
value="<%= data.hero?.badge || '' %>">
</div>
<div class="col-md-4">
<label class="form-label">Student Count</label>
<input type="text" class="form-control" id="heroStudentCount"
value="<%= data.hero?.studentCount || '' %>">
</div>
<div class="col-md-4">
<label class="form-label">Image Alt Text</label>
<input type="text" class="form-control" id="heroImageAlt"
value="<%= data.hero?.imageAlt || '' %>">
</div>
<div class="col-md-12">
<label class="form-label">Title</label>
<input type="text" class="form-control" id="heroTitle"
value="<%= data.hero?.title || '' %>">
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="heroDescription"
rows="3"><%= data.hero?.description || '' %></textarea>
</div>
<div class="col-md-12">
<label class="form-label">Image URL</label>
<div class="input-group">
<input type="text" class="form-control" id="heroImage"
value="<%= data.hero?.image || '' %>">
<button class="btn btn-outline-primary" type="button"
onclick="openImageUploader('heroImage', 'about')"><i
class="fas fa-upload me-1"></i>Upload</button>
</div>
<% if (data.hero?.image) { %>
<img src="<%= data.hero.image %>"
class="img-thumbnail uploaded-preview mt-2"
style="max-height:180px;">
<% } %>
</div>
</div>
<div class="mt-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Core Values</h6>
<button type="button" class="btn btn-outline-primary btn-sm"
onclick="addCoreValue()"><i class="fas fa-plus me-1"></i>Add
Value</button>
</div>
<div id="coreValuesContainer">
<% (data.hero?.coreValues || []).forEach(val=> { %>
<div class="input-group input-group-sm mb-2">
<input type="text" class="form-control core-value-input"
value="<%= val %>">
<button class="btn btn-outline-danger" type="button"
onclick="this.parentElement.remove()"><i
class="fas fa-times"></i></button>
</div>
<% }) %>
</div>
</div>
</div>
</div>
</div>
<!-- ===== LEADERSHIP TAB ===== -->
<div class="tab-pane fade" id="tab-leadership-pane" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h6 class="mb-0"><i class="fas fa-users me-2"></i>Leadership</h6>
<button type="button" class="btn btn-outline-primary btn-sm"
onclick="addLeadershipMember()"><i class="fas fa-plus me-1"></i>Add
Member</button>
</div>
<div class="card-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label">Heading</label>
<input type="text" class="form-control" id="leadershipHeading"
value="<%= data.leadership?.heading || '' %>">
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="leadershipDescription"
rows="2"><%= data.leadership?.description || '' %></textarea>
</div>
</div>
<div id="leadershipMembersContainer"></div>
</div>
</div>
</div>
<!-- ===== LEARNING MODEL TAB ===== -->
<div class="tab-pane fade" id="tab-learningModel-pane" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="fas fa-chalkboard-teacher me-2"></i>Learning Model
</h6>
</div>
<div class="card-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label">Heading</label>
<input type="text" class="form-control" id="lmHeading"
value="<%= data.learningModel?.heading || '' %>">
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="lmDescription"
rows="2"><%= data.learningModel?.description || '' %></textarea>
</div>
</div>
<div class="row g-4">
<!-- Async -->
<div class="col-md-6">
<div class="card border">
<div
class="card-header bg-light d-flex justify-content-between align-items-center">
<h6 class="mb-0">Asynchronous Learning</h6>
<button type="button" class="btn btn-outline-primary btn-sm"
onclick="addLearningFeature('async')"><i
class="fas fa-plus me-1"></i>Add Feature</button>
</div>
<div class="card-body p-3">
<div class="mb-2">
<label class="form-label small">Title</label>
<input type="text" class="form-control form-control-sm"
id="asyncTitle"
value="<%= data.learningModel?.async?.title || '' %>">
</div>
<div class="mb-3">
<label class="form-label small">Description</label>
<textarea class="form-control form-control-sm"
id="asyncDescription"
rows="2"><%= data.learningModel?.async?.description || '' %></textarea>
</div>
<div id="asyncFeaturesContainer"></div>
</div>
</div>
</div>
<!-- Sync -->
<div class="col-md-6">
<div class="card border">
<div
class="card-header bg-light d-flex justify-content-between align-items-center">
<h6 class="mb-0">Synchronous Learning</h6>
<button type="button" class="btn btn-outline-primary btn-sm"
onclick="addLearningFeature('sync')"><i
class="fas fa-plus me-1"></i>Add Feature</button>
</div>
<div class="card-body p-3">
<div class="mb-2">
<label class="form-label small">Title</label>
<input type="text" class="form-control form-control-sm"
id="syncTitle"
value="<%= data.learningModel?.sync?.title || '' %>">
</div>
<div class="mb-3">
<label class="form-label small">Description</label>
<textarea class="form-control form-control-sm"
id="syncDescription"
rows="2"><%= data.learningModel?.sync?.description || '' %></textarea>
</div>
<div id="syncFeaturesContainer"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ===== ACCREDITATION TAB ===== -->
<div class="tab-pane fade" id="tab-accreditation-pane" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="fas fa-award me-2"></i>Accreditation</h6>
</div>
<div class="card-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label">Heading</label>
<input type="text" class="form-control" id="accHeading"
value="<%= data.accreditation?.heading || '' %>">
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="accDescription"
rows="2"><%= data.accreditation?.description || '' %></textarea>
</div>
</div>
<div class="row g-4">
<div class="col-md-6">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Badges</h6>
<button type="button" class="btn btn-outline-primary btn-sm"
onclick="addAccBadge()"><i class="fas fa-plus me-1"></i>Add
Badge</button>
</div>
<div id="accBadgesContainer"></div>
</div>
<div class="col-md-6">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Stats</h6>
<button type="button" class="btn btn-outline-primary btn-sm"
onclick="addAccStat()"><i class="fas fa-plus me-1"></i>Add
Stat</button>
</div>
<div id="accStatsContainer"></div>
</div>
</div>
</div>
</div>
</div>
<!-- ===== SUCCESS STORIES TAB ===== -->
<div class="tab-pane fade" id="tab-successStories-pane" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h6 class="mb-0"><i class="fas fa-quote-left me-2"></i>Success Stories</h6>
<button type="button" class="btn btn-outline-primary btn-sm"
onclick="addStory()"><i class="fas fa-plus me-1"></i>Add Story</button>
</div>
<div class="card-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label">Heading</label>
<input type="text" class="form-control" id="ssHeading"
value="<%= data.successStories?.heading || '' %>">
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="ssDescription"
rows="2"><%= data.successStories?.description || '' %></textarea>
</div>
</div>
<div id="storiesContainer"></div>
</div>
</div>
</div>
<!-- ===== CTA TAB ===== -->
<div class="tab-pane fade" id="tab-cta-pane" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="fas fa-bullhorn me-2"></i>Call to Action</h6>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-12">
<label class="form-label">Heading</label>
<input type="text" class="form-control" id="ctaHeading"
value="<%= data.cta?.heading || '' %>">
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="ctaDescription"
rows="2"><%= data.cta?.description || '' %></textarea>
</div>
<div class="col-md-3">
<label class="form-label">Primary Button Label</label>
<input type="text" class="form-control" id="ctaPrimaryLabel"
value="<%= data.cta?.primaryButton?.label || '' %>">
</div>
<div class="col-md-3">
<label class="form-label">Primary Button Href</label>
<input type="text" class="form-control" id="ctaPrimaryHref"
value="<%= data.cta?.primaryButton?.href || '' %>">
</div>
<div class="col-md-3">
<label class="form-label">Secondary Button Label</label>
<input type="text" class="form-control" id="ctaSecondaryLabel"
value="<%= data.cta?.secondaryButton?.label || '' %>">
</div>
<div class="col-md-3">
<label class="form-label">Secondary Button Href</label>
<input type="text" class="form-control" id="ctaSecondaryHref"
value="<%= data.cta?.secondaryButton?.href || '' %>">
</div>
</div>
</div>
</div>
</div>
</div><!-- /.tab-content -->
</div><!-- /.card-body -->
<div class="card-footer bg-light d-flex justify-content-end py-3 gap-2">
<button type="button" class="btn btn-outline-secondary px-4" onclick="resetForm()"><i
class="fas fa-undo me-2"></i>Reset</button>
<button type="submit" class="btn btn-outline-primary px-4" id="submitBtn"><i
class="fas fa-save me-2"></i>Save Changes</button>
</div>
</div>
</form>
</div>
</div>
</div>
<script>
let originalFormData = null;
document.addEventListener('DOMContentLoaded', function () {
originalFormData = <%- JSON.stringify(data) %>;
populateAll(originalFormData);
document.getElementById('aboutForm').addEventListener('submit', function (e) {
e.preventDefault();
serializeAll();
this.submit();
});
document.body.addEventListener('click', function (e) {
const btn = e.target.closest('.btn-upload-image');
if (btn) openImageUploader(btn.dataset.targetInput, btn.dataset.imageType);
});
});
// ── Populate all sections ────────────────────────────────────────────────
function populateAll(data) {
if (!data) return;
// Hero
const h = data.hero || {};
setVal('heroBadge', h.badge);
setVal('heroTitle', h.title);
setVal('heroDescription', h.description);
setVal('heroStudentCount', h.studentCount);
setVal('heroImage', h.image);
setVal('heroImageAlt', h.imageAlt);
updateImagePreview('heroImage', h.image);
populateCoreValues(h.coreValues || []);
// Leadership
const l = data.leadership || {};
setVal('leadershipHeading', l.heading);
setVal('leadershipDescription', l.description);
populateLeadershipMembers(l.members || []);
// Learning Model
const lm = data.learningModel || {};
setVal('lmHeading', lm.heading);
setVal('lmDescription', lm.description);
setVal('asyncTitle', lm.async?.title);
setVal('asyncDescription', lm.async?.description);
setVal('syncTitle', lm.sync?.title);
setVal('syncDescription', lm.sync?.description);
populateLearningFeatures('async', lm.async?.features || []);
populateLearningFeatures('sync', lm.sync?.features || []);
// Accreditation
const acc = data.accreditation || {};
setVal('accHeading', acc.heading);
setVal('accDescription', acc.description);
populateAccBadges(acc.badges || []);
populateAccStats(acc.stats || []);
// Success Stories
const ss = data.successStories || {};
setVal('ssHeading', ss.heading);
setVal('ssDescription', ss.description);
populateStories(ss.stories || []);
// CTA
const cta = data.cta || {};
setVal('ctaHeading', cta.heading);
setVal('ctaDescription', cta.description);
setVal('ctaPrimaryLabel', cta.primaryButton?.label);
setVal('ctaPrimaryHref', cta.primaryButton?.href);
setVal('ctaSecondaryLabel', cta.secondaryButton?.label);
setVal('ctaSecondaryHref', cta.secondaryButton?.href);
}
function setVal(id, val) {
const el = document.getElementById(id);
if (el) el.value = val || '';
}
// ── Serialize all sections into hidden JSON inputs ───────────────────────
function serializeAll() {
// Hero
document.getElementById('heroJson').value = JSON.stringify({
badge: v('heroBadge'),
title: v('heroTitle'),
description: v('heroDescription'),
studentCount: v('heroStudentCount'),
image: v('heroImage'),
imageAlt: v('heroImageAlt'),
coreValues: Array.from(document.querySelectorAll('.core-value-input')).map(i => i.value.trim()).filter(Boolean)
});
// Leadership
document.getElementById('leadershipJson').value = JSON.stringify({
heading: v('leadershipHeading'),
description: v('leadershipDescription'),
members: Array.from(document.querySelectorAll('.leadership-member')).map(row => ({
name: row.querySelector('[data-field="name"]').value.trim(),
role: row.querySelector('[data-field="role"]').value.trim(),
bio: row.querySelector('[data-field="bio"]').value.trim(),
avatar: row.querySelector('[data-field="avatar"]').value.trim(),
social: {
linkedin: row.querySelector('[data-field="linkedin"]').value.trim(),
twitter: row.querySelector('[data-field="twitter"]').value.trim()
}
}))
});
// Learning Model
document.getElementById('learningModelJson').value = JSON.stringify({
heading: v('lmHeading'),
description: v('lmDescription'),
async: {
title: v('asyncTitle'),
description: v('asyncDescription'),
features: collectLearningFeatures('async')
},
sync: {
title: v('syncTitle'),
description: v('syncDescription'),
features: collectLearningFeatures('sync')
}
});
// Accreditation
document.getElementById('accreditationJson').value = JSON.stringify({
heading: v('accHeading'),
description: v('accDescription'),
badges: Array.from(document.querySelectorAll('.acc-badge-item')).map(row => ({
icon: row.querySelector('[data-field="icon"]').value.trim(),
title: row.querySelector('[data-field="title"]').value.trim(),
desc: row.querySelector('[data-field="desc"]').value.trim()
})),
stats: Array.from(document.querySelectorAll('.acc-stat-item')).map(row => ({
value: row.querySelector('[data-field="value"]').value.trim(),
label: row.querySelector('[data-field="label"]').value.trim(),
isPrimary: row.querySelector('[data-field="isPrimary"]').checked
}))
});
// Success Stories
document.getElementById('successStoriesJson').value = JSON.stringify({
heading: v('ssHeading'),
description: v('ssDescription'),
stories: Array.from(document.querySelectorAll('.story-item')).map(row => ({
quote: row.querySelector('[data-field="quote"]').value.trim(),
name: row.querySelector('[data-field="name"]').value.trim(),
program: row.querySelector('[data-field="program"]').value.trim(),
avatar: row.querySelector('[data-field="avatar"]').value.trim()
}))
});
// CTA
document.getElementById('ctaJson').value = JSON.stringify({
heading: v('ctaHeading'),
description: v('ctaDescription'),
primaryButton: { label: v('ctaPrimaryLabel'), href: v('ctaPrimaryHref') },
secondaryButton: { label: v('ctaSecondaryLabel'), href: v('ctaSecondaryHref') }
});
}
function v(id) {
const el = document.getElementById(id);
return el ? el.value.trim() : '';
}
// ── Core Values ──────────────────────────────────────────────────────────
function addCoreValue(val = '') {
const c = document.getElementById('coreValuesContainer');
c.insertAdjacentHTML('beforeend', `
<div class="input-group input-group-sm mb-2">
<input type="text" class="form-control core-value-input" value="${esc(val)}" placeholder="e.g. Radical Affordability">
<button class="btn btn-outline-danger" type="button" onclick="this.parentElement.remove()"><i class="fas fa-times"></i></button>
</div>`);
}
function populateCoreValues(values) {
document.getElementById('coreValuesContainer').innerHTML = '';
values.forEach(v => addCoreValue(v));
}
// ── Leadership Members ───────────────────────────────────────────────────
function addLeadershipMember(item = {}) {
const idx = Date.now();
const c = document.getElementById('leadershipMembersContainer');
c.insertAdjacentHTML('beforeend', `
<div class="card mb-3 leadership-member">
<div class="card-body p-3">
<div class="row g-2">
<div class="col-md-3">
<label class="form-label small">Name</label>
<input type="text" class="form-control form-control-sm" data-field="name" value="${esc(item.name)}">
</div>
<div class="col-md-3">
<label class="form-label small">Role</label>
<input type="text" class="form-control form-control-sm" data-field="role" value="${esc(item.role)}">
</div>
<div class="col-md-6">
<label class="form-label small">Bio</label>
<input type="text" class="form-control form-control-sm" data-field="bio" value="${esc(item.bio)}">
</div>
<div class="col-md-4">
<label class="form-label small">Avatar URL</label>
<div class="input-group input-group-sm">
<input type="text" class="form-control" data-field="avatar" id="memberAvatar_${idx}" value="${esc(item.avatar)}">
<button class="btn btn-outline-primary btn-upload-image" type="button" data-target-input="memberAvatar_${idx}" data-image-type="about"><i class="fas fa-upload"></i></button>
</div>
</div>
<div class="col-md-4">
<label class="form-label small">LinkedIn URL</label>
<input type="text" class="form-control form-control-sm" data-field="linkedin" value="${esc(item.social?.linkedin)}">
</div>
<div class="col-md-4">
<label class="form-label small">Twitter URL</label>
<input type="text" class="form-control form-control-sm" data-field="twitter" value="${esc(item.social?.twitter)}">
</div>
</div>
<button type="button" class="btn btn-link text-danger btn-sm p-0 mt-2" onclick="this.closest('.leadership-member').remove()"><i class="fas fa-trash me-1"></i>Remove</button>
</div>
</div>`);
}
function populateLeadershipMembers(members) {
document.getElementById('leadershipMembersContainer').innerHTML = '';
members.forEach(m => addLeadershipMember(m));
}
// ── Learning Features ────────────────────────────────────────────────────
function addLearningFeature(mode, item = {}) {
const c = document.getElementById(mode + 'FeaturesContainer');
c.insertAdjacentHTML('beforeend', `
<div class="card mb-2 lf-feature-item" data-mode="${mode}">
<div class="card-body p-2">
<div class="row g-2">
<div class="col-md-3">
<input type="text" class="form-control form-control-sm" data-field="icon" value="${esc(item.icon)}" placeholder="fa-play">
</div>
<div class="col-md-4">
<input type="text" class="form-control form-control-sm" data-field="title" value="${esc(item.title)}" placeholder="Title">
</div>
<div class="col-md-5">
<input type="text" class="form-control form-control-sm" data-field="desc" value="${esc(item.desc)}" placeholder="Description">
</div>
</div>
<button type="button" class="btn btn-link text-danger btn-sm p-0 mt-1" onclick="this.closest('.lf-feature-item').remove()"><i class="fas fa-trash me-1"></i>Remove</button>
</div>
</div>`);
}
function populateLearningFeatures(mode, features) {
document.getElementById(mode + 'FeaturesContainer').innerHTML = '';
features.forEach(f => addLearningFeature(mode, f));
}
function collectLearningFeatures(mode) {
return Array.from(document.querySelectorAll(`.lf-feature-item[data-mode="${mode}"]`)).map(row => ({
icon: row.querySelector('[data-field="icon"]').value.trim(),
title: row.querySelector('[data-field="title"]').value.trim(),
desc: row.querySelector('[data-field="desc"]').value.trim()
}));
}
// ── Accreditation Badges ─────────────────────────────────────────────────
function addAccBadge(item = {}) {
const c = document.getElementById('accBadgesContainer');
c.insertAdjacentHTML('beforeend', `
<div class="card mb-2 acc-badge-item">
<div class="card-body p-2">
<div class="row g-2">
<div class="col-md-3">
<input type="text" class="form-control form-control-sm" data-field="icon" value="${esc(item.icon)}" placeholder="fa-award">
</div>
<div class="col-md-4">
<input type="text" class="form-control form-control-sm" data-field="title" value="${esc(item.title)}" placeholder="Title">
</div>
<div class="col-md-5">
<input type="text" class="form-control form-control-sm" data-field="desc" value="${esc(item.desc)}" placeholder="Description">
</div>
</div>
<button type="button" class="btn btn-link text-danger btn-sm p-0 mt-1" onclick="this.closest('.acc-badge-item').remove()"><i class="fas fa-trash me-1"></i>Remove</button>
</div>
</div>`);
}
function populateAccBadges(badges) {
document.getElementById('accBadgesContainer').innerHTML = '';
badges.forEach(b => addAccBadge(b));
}
// ── Accreditation Stats ──────────────────────────────────────────────────
function addAccStat(item = {}) {
const c = document.getElementById('accStatsContainer');
c.insertAdjacentHTML('beforeend', `
<div class="card mb-2 acc-stat-item">
<div class="card-body p-2">
<div class="row g-2 align-items-center">
<div class="col-md-3">
<input type="text" class="form-control form-control-sm" data-field="value" value="${esc(item.value)}" placeholder="e.g. 89%">
</div>
<div class="col-md-5">
<input type="text" class="form-control form-control-sm" data-field="label" value="${esc(item.label)}" placeholder="Label">
</div>
<div class="col-md-4 d-flex align-items-center gap-2">
<div class="form-check mb-0">
<input class="form-check-input" type="checkbox" data-field="isPrimary" ${item.isPrimary ? 'checked' : ''}>
<label class="form-check-label small">Primary</label>
</div>
<button type="button" class="btn btn-link text-danger btn-sm p-0" onclick="this.closest('.acc-stat-item').remove()"><i class="fas fa-trash"></i></button>
</div>
</div>
</div>
</div>`);
}
function populateAccStats(stats) {
document.getElementById('accStatsContainer').innerHTML = '';
stats.forEach(s => addAccStat(s));
}
// ── Success Stories ──────────────────────────────────────────────────────
function addStory(item = {}) {
const idx = Date.now();
const c = document.getElementById('storiesContainer');
c.insertAdjacentHTML('beforeend', `
<div class="card mb-3 story-item">
<div class="card-body p-3">
<div class="row g-2">
<div class="col-md-12">
<label class="form-label small">Quote</label>
<textarea class="form-control form-control-sm" data-field="quote" rows="2">${esc(item.quote)}</textarea>
</div>
<div class="col-md-3">
<label class="form-label small">Name</label>
<input type="text" class="form-control form-control-sm" data-field="name" value="${esc(item.name)}">
</div>
<div class="col-md-4">
<label class="form-label small">Program</label>
<input type="text" class="form-control form-control-sm" data-field="program" value="${esc(item.program)}">
</div>
<div class="col-md-5">
<label class="form-label small">Avatar URL</label>
<div class="input-group input-group-sm">
<input type="text" class="form-control" data-field="avatar" id="storyAvatar_${idx}" value="${esc(item.avatar)}">
<button class="btn btn-outline-primary btn-upload-image" type="button" data-target-input="storyAvatar_${idx}" data-image-type="about"><i class="fas fa-upload"></i></button>
</div>
</div>
</div>
<button type="button" class="btn btn-link text-danger btn-sm p-0 mt-2" onclick="this.closest('.story-item').remove()"><i class="fas fa-trash me-1"></i>Remove</button>
</div>
</div>`);
}
function populateStories(stories) {
document.getElementById('storiesContainer').innerHTML = '';
stories.forEach(s => addStory(s));
}
// ── Utilities ────────────────────────────────────────────────────────────
function esc(val) {
if (!val) return '';
return String(val).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function resetForm() {
if (confirm('Reset all changes to last saved state?')) {
populateAll(originalFormData);
}
}
function updateImagePreview(inputId, imagePath) {
if (!imagePath) return;
const input = document.getElementById(inputId);
if (!input) return;
let preview = input.closest('.card')?.querySelector('.uploaded-preview');
if (preview) {
preview.src = imagePath;
} else {
const img = document.createElement('img');
img.src = imagePath;
img.className = 'img-thumbnail uploaded-preview mt-2';
img.style.maxHeight = '180px';
input.closest('.input-group')?.insertAdjacentElement('afterend', img);
}
}
function openImageUploader(targetInput, imageType) {
// Dùng persistent hidden file input để tránh browser block programmatic click
let fileInput = document.getElementById('__globalFileInput');
if (!fileInput) {
fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.id = '__globalFileInput';
fileInput.style.cssText = 'position:fixed;top:-9999px;left:-9999px;opacity:0;width:1px;height:1px;';
document.body.appendChild(fileInput);
}
// Reset để có thể chọn lại cùng file
fileInput.value = '';
// Gán handler mới mỗi lần gọi
fileInput.onchange = async function (e) {
const file = e.target.files[0];
if (!file) return;
const uploadBtn = document.querySelector(`[onclick*="${targetInput}"]`) ||
document.querySelector(`[data-target-input="${targetInput}"]`);
if (uploadBtn) uploadBtn.disabled = true;
try {
const formData = new FormData();
formData.append('image', file);
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);
if (input) {
input.value = result.path;
const previewUrl = result.path.startsWith('http') ? result.path : window.location.origin + result.path;
let preview = input.closest('.col-md-12, .col-md-4, .col-md-5')?.querySelector('.uploaded-preview');
if (preview) {
preview.src = previewUrl;
} else {
const img = document.createElement('img');
img.src = previewUrl;
img.className = 'img-thumbnail uploaded-preview mt-2';
img.style.maxHeight = '150px';
input.closest('.input-group')?.insertAdjacentElement('afterend', img);
}
}
} catch (err) {
console.error('Upload error:', err);
alert('Upload failed: ' + err.message);
} finally {
if (uploadBtn) uploadBtn.disabled = false;
fileInput.value = '';
}
};
fileInput.click();
}
</script>