forked from UKSOURCE/cms.hailearning.edu.vn
feat: enhance about us and footer CMS admin panels
- Improve aboutUs controller with better field handling - Update footer controller with expanded content management - Refine about admin view templates - Update appointment and footer admin views - Add about contract repair migration script - Update about.json seed data
This commit is contained in:
@@ -4,11 +4,11 @@
|
||||
<h1 class="h3 mb-0" style="color: var(--primary-dark);">
|
||||
<%= title %>
|
||||
</h1>
|
||||
<p class="text-muted mb-0">Edit content displayed on About Us page</p>
|
||||
<p class="text-muted mb-0">Edit content displayed on the About page</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="<%= frontendUrl %>/about-us/" class="btn btn-outline-primary" target="_blank">
|
||||
<i class="fas fa-external-link-alt me-2"></i>View About Us Page
|
||||
<a href="<%= frontendUrl %>/about" class="btn btn-outline-primary" target="_blank">
|
||||
<i class="fas fa-external-link-alt me-2"></i>View About Page
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,6 +89,7 @@
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted d-block mt-1">Recommended: 1920x640px wide banner (3:1). Keep the focal subject centered because the image is cropped responsively.</small>
|
||||
<% if (data.hero?.backgroundImage) { %>
|
||||
<img src="<%= data.hero.backgroundImage %>"
|
||||
class="img-thumbnail uploaded-preview mt-2"
|
||||
@@ -134,6 +135,7 @@
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted d-block mt-1">Recommended: 1600x900px landscape image (16:9). This section uses a full-width cover frame.</small>
|
||||
<% if (data.intro?.image) { %>
|
||||
<img src="<%= data.intro.image %>"
|
||||
class="img-thumbnail uploaded-preview mt-2"
|
||||
@@ -180,15 +182,20 @@
|
||||
|
||||
<h6 class="mt-4 mb-3">Images</h6>
|
||||
<div class="row g-3">
|
||||
<% ['main', 'secondary', 'bgShape', 'planeShape', 'topShape', 'globeShape'].forEach(imgKey => { %>
|
||||
<% ['main', 'secondary'].forEach(imgKey => { %>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label"><%= imgKey.charAt(0).toUpperCase() + imgKey.slice(1) %></label>
|
||||
<label class="form-label"><%= imgKey.charAt(0).toUpperCase() + imgKey.slice(1) %> Image</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="missionImg_<%= imgKey %>" value="<%= data.mission?.images?.[imgKey] || '' %>">
|
||||
<button class="btn btn-outline-primary btn-upload-image btn-sm" type="button" data-target-input="missionImg_<%= imgKey %>" data-image-type="about">
|
||||
<i class="fas fa-upload"></i>
|
||||
</button>
|
||||
</div>
|
||||
<% if (imgKey === 'main') { %>
|
||||
<small class="text-muted d-block mt-1">Recommended: 1200x1500px portrait image (4:5). This is the primary mission visual.</small>
|
||||
<% } else { %>
|
||||
<small class="text-muted d-block mt-1">Recommended: 1200x900px image (4:3). This image sits as the smaller overlapping card.</small>
|
||||
<% } %>
|
||||
</div>
|
||||
<% }) %>
|
||||
</div>
|
||||
@@ -196,11 +203,12 @@
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<label class="form-label mb-0">Items (Icons & Labels)</label>
|
||||
<label class="form-label mb-0">Items</label>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" onclick="addMissionItem()">
|
||||
<i class="fas fa-plus me-1"></i>Add
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted d-block mb-2">Mission item icons are fixed by the frontend design and are not editable in CMS.</small>
|
||||
<div id="missionItemsContainer"></div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
@@ -245,6 +253,7 @@
|
||||
<i class="fas fa-upload"></i>
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted d-block mt-1">Recommended: 1920x1080px background image (16:9). Darker imagery works best because the section adds a dark overlay.</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Side Image</label>
|
||||
@@ -254,6 +263,7 @@
|
||||
<i class="fas fa-upload"></i>
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted d-block mt-1">Recommended: 1200x1500px portrait image (4:5). The image fills a tall cover frame on desktop.</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">CTA Button Label</label>
|
||||
@@ -272,6 +282,7 @@
|
||||
<i class="fas fa-plus me-1"></i>Add Item
|
||||
</button>
|
||||
</div>
|
||||
<small class="text-muted d-block mb-2">Custom icons render at about 28x28px inside a 60x60 card. Use SVG or a square PNG/WebP at 64x64px or 128x128px.</small>
|
||||
<div id="featureItemsContainer"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -320,7 +331,7 @@
|
||||
<input class="form-check-input about-blog-checkbox" type="checkbox" value="<%= blog._id %>" <%= isSelected ? 'checked' : '' %> onclick="event.stopPropagation(); handleAboutCheckboxChange(this)">
|
||||
</div>
|
||||
</div>
|
||||
<img src="<%= blog.featuredImage ? (blog.featuredImage.startsWith('http') ? blog.featuredImage : backendUrl + blog.featuredImage) : '/assets/img/placeholder.jpg' %>" class="card-img-top" style="height: 210px; object-fit: cover;">
|
||||
<img src="<%= blog.featuredImage ? (blog.featuredImage.startsWith('http') ? blog.featuredImage : (blog.featuredImage.startsWith('/') ? backendUrl + blog.featuredImage : backendUrl + '/' + blog.featuredImage)) : '/assets/img/placeholder.jpg' %>" class="card-img-top" style="height: 210px; object-fit: cover;">
|
||||
<div class="card-body p-2">
|
||||
<h6 class="card-title small fw-bold mb-1" title="<%= blog.title %>" style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; height: 2.6em; line-height: 1.3em;">
|
||||
<%= blog.title %>
|
||||
@@ -403,7 +414,7 @@
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
showToast('Success', 'About Us updated successfully', 'success');
|
||||
showToast('Success', 'About page updated successfully', 'success');
|
||||
|
||||
// Update the local state with returned data from server
|
||||
// This ensures the UI is in sync with what was actually saved
|
||||
@@ -635,7 +646,7 @@
|
||||
document.getElementById('missionCtaLabel').value = mission.ctaButton?.label || '';
|
||||
document.getElementById('missionCtaHref').value = mission.ctaButton?.href || '';
|
||||
|
||||
['main', 'secondary', 'bgShape', 'planeShape', 'topShape', 'globeShape'].forEach(k => {
|
||||
['main', 'secondary'].forEach(k => {
|
||||
const el = document.getElementById('missionImg_' + k);
|
||||
const val = mission.images?.[k] || '';
|
||||
if (el) {
|
||||
@@ -706,18 +717,10 @@
|
||||
<div class="card mb-2 mission-item">
|
||||
<div class="card-body p-2">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" class="form-control" name="missionItemIcon_${idx}" placeholder="Icon path">
|
||||
<button class="btn btn-outline-primary btn-upload-image" type="button" data-target-input="missionItemIcon_${idx}" data-image-type="about">
|
||||
<i class="fas fa-upload"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-5">
|
||||
<input type="text" class="form-control form-control-sm" name="missionItemLabel_${idx}" placeholder="Label">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-7">
|
||||
<input type="text" class="form-control form-control-sm" name="missionItemDesc_${idx}" placeholder="Description">
|
||||
</div>
|
||||
</div>
|
||||
@@ -730,12 +733,11 @@
|
||||
function populateMissionItems(items) {
|
||||
const container = document.getElementById('missionItemsContainer');
|
||||
container.innerHTML = '';
|
||||
items.forEach((item, i) => {
|
||||
items.forEach((item) => {
|
||||
addMissionItem();
|
||||
const last = container.lastElementChild;
|
||||
last.querySelector(`[name="missionItemIcon_${i}"]`).value = item.icon || '';
|
||||
last.querySelector(`[name="missionItemLabel_${i}"]`).value = item.label || '';
|
||||
last.querySelector(`[name="missionItemDesc_${i}"]`).value = item.description || '';
|
||||
last.querySelector(`[name^="missionItemLabel_"]`).value = item.label || item.title || '';
|
||||
last.querySelector(`[name^="missionItemDesc_"]`).value = item.description || '';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -890,14 +892,10 @@
|
||||
description: document.getElementById('missionDescription').value.trim(),
|
||||
images: {
|
||||
main: document.getElementById('missionImg_main').value.trim(),
|
||||
secondary: document.getElementById('missionImg_secondary').value.trim(),
|
||||
bgShape: document.getElementById('missionImg_bgShape').value.trim(),
|
||||
planeShape: document.getElementById('missionImg_planeShape').value.trim(),
|
||||
topShape: document.getElementById('missionImg_topShape').value.trim(),
|
||||
globeShape: document.getElementById('missionImg_globeShape').value.trim()
|
||||
secondary: document.getElementById('missionImg_secondary').value.trim()
|
||||
},
|
||||
items: Array.from(document.querySelectorAll('.mission-item')).map(item => ({
|
||||
icon: item.querySelector('[name^="missionItemIcon_"]').value.trim(),
|
||||
icon: '/assets/img/home-1/icon/01.svg',
|
||||
label: item.querySelector('[name^="missionItemLabel_"]').value.trim(),
|
||||
description: item.querySelector('[name^="missionItemDesc_"]').value.trim()
|
||||
})).filter(i => i.label !== ''),
|
||||
@@ -964,4 +962,4 @@
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user