refactor(about): remove blog section and decouple from blog module

This commit is contained in:
2026-02-07 22:11:59 +07:00
parent 954c52fc5f
commit 8401ea2057
37 changed files with 438 additions and 249 deletions

View File

@@ -281,36 +281,65 @@
<!-- News Tab -->
<div class="tab-pane fade <%= locals.activeTab === 'news' ? 'show active' : '' %>" id="news" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="fas fa-newspaper me-2"></i>News Section</h6>
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h6 class="mb-0"><i class="fas fa-newspaper me-2"></i>News Section (Blog Preview)</h6>
<span class="badge bg-info text-dark">System will automatically fetch the 3 latest posts if no specific blog is selected.</span>
</div>
<div class="card-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label">Subheading</label>
<input type="text" class="form-control" id="newsSubheading" value="<%= data.news?.subheading || '' %>">
<label class="form-label fw-medium">Subheading</label>
<input type="text" class="form-control" id="newsSubheading" value="<%= data.news?.subheading || '' %>" placeholder="e.g., Visa Tips & Guides">
</div>
<div class="col-md-6">
<label class="form-label">Heading</label>
<input type="text" class="form-control" id="newsHeading" value="<%= data.news?.heading || '' %>">
<label class="form-label fw-medium">Heading</label>
<input type="text" class="form-control" id="newsHeading" value="<%= data.news?.heading || '' %>" placeholder="e.g., Latest Insights & Updates">
</div>
<div class="col-md-6">
<label class="form-label">CTA Button Label</label>
<input type="text" class="form-control" id="newsCtaLabel" value="<%= data.news?.ctaButton?.label || '' %>">
<label class="form-label fw-medium">CTA Button Label</label>
<input type="text" class="form-control" id="newsCtaLabel" value="<%= data.news?.ctaButton?.label || '' %>" placeholder="e.g., View All Articles">
</div>
<div class="col-md-6">
<label class="form-label">CTA Button Link</label>
<input type="text" class="form-control" id="newsCtaHref" value="<%= data.news?.ctaButton?.href || '' %>">
<label class="form-label fw-medium">CTA Button Link</label>
<input type="text" class="form-control" id="newsCtaHref" value="<%= data.news?.ctaButton?.href || '' %>" placeholder="/blog">
</div>
</div>
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">News Articles</h6>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="addNewsItem()">
<i class="fas fa-plus me-1"></i>Add Article
</button>
<div class="col-md-12 mt-4">
<label class="form-label fw-bold"><i class="fas fa-check-square me-2"></i>Select Featured Blogs (Direct from Blog Module)</label>
<p class="text-muted small mb-3">Select blog posts to display on About page. If none are selected, the system will use the 3 latest posts.</p>
<div class="row g-3 blog-selector-container" style="max-height: 400px; overflow-y: auto; border: 1px solid #eee; padding: 15px; border-radius: 8px;">
<% if (allBlogs && allBlogs.length > 0) { %>
<% allBlogs.forEach(blog => {
const isSelected = data.news?.selectedBlogIds && data.news.selectedBlogIds.some(id => id.toString() === blog._id.toString());
%>
<div class="col-md-4">
<div class="card h-100 blog-select-card <%= isSelected ? 'border-primary bg-light' : '' %>" onclick="toggleAboutBlogSelection(this, '<%= blog._id %>')" style="cursor: pointer; transition: all 0.2s;">
<div class="position-absolute top-0 end-0 m-2">
<div class="form-check">
<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;">
<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 %>
</h6>
<p class="card-text tiny text-muted mb-0">
<%= blog.publishedAt ? new Date(blog.publishedAt).toLocaleDateString('vi-VN') : '' %>
</p>
</div>
</div>
</div>
<% }) %>
<% } else { %>
<div class="col-12 text-center py-4">
<p class="text-muted">No published blogs found. Please create some blogs first.</p>
<a href="/admin/blog/create" class="btn btn-sm btn-outline-primary">Create Blog</a>
</div>
<% } %>
</div>
</div>
<div id="newsItemsContainer"></div>
</div>
</div>
</div>
@@ -638,7 +667,16 @@
document.getElementById('newsHeading').value = news.heading || '';
document.getElementById('newsCtaLabel').value = news.ctaButton?.label || '';
document.getElementById('newsCtaHref').value = news.ctaButton?.href || '';
populateNewsItems(news.items || []);
// Update blog selection checkboxes
document.querySelectorAll('.about-blog-checkbox').forEach(cb => {
const isSelected = news.selectedBlogIds && news.selectedBlogIds.some(id => id.toString() === cb.value);
cb.checked = isSelected;
const card = cb.closest('.blog-select-card');
if (card) {
handleAboutCheckboxUpdate(card, isSelected);
}
});
}
function updateImagePreview(inputId, imagePath) {
@@ -760,77 +798,42 @@
});
}
function addNewsItem() {
const container = document.getElementById('newsItemsContainer');
const idx = container.children.length;
const html = `
<div class="card mb-3 news-item-row">
<div class="card-body p-3">
<div class="row g-2">
<div class="col-md-6">
<label class="small text-muted">Title</label>
<input type="text" class="form-control form-control-sm" name="newsItemTitle_${idx}">
</div>
<div class="col-md-3">
<label class="small text-muted">Category</label>
<input type="text" class="form-control form-control-sm" name="newsItemCategory_${idx}">
</div>
<div class="col-md-3">
<label class="small text-muted">Date</label>
<input type="text" class="form-control form-control-sm" name="newsItemDate_${idx}">
</div>
<div class="col-md-2">
<label class="small text-muted">Comments</label>
<input type="number" class="form-control form-control-sm" name="newsItemComments_${idx}">
</div>
<div class="col-md-5">
<label class="small text-muted">Author Name</label>
<input type="text" class="form-control form-control-sm" name="newsItemAuthorName_${idx}">
</div>
<div class="col-md-5">
<label class="small text-muted">Author Avatar</label>
<div class="input-group input-group-sm">
<input type="text" class="form-control" name="newsItemAuthorAvatar_${idx}">
<button class="btn btn-outline-primary btn-upload-image" type="button" data-target-input="newsItemAuthorAvatar_${idx}" data-image-type="about">
<i class="fas fa-upload"></i>
</button>
</div>
</div>
<div class="col-md-6">
<label class="small text-muted">Link</label>
<input type="text" class="form-control form-control-sm" name="newsItemLink_${idx}">
</div>
<div class="col-md-6">
<label class="small text-muted">Thumbnail</label>
<div class="input-group input-group-sm">
<input type="text" class="form-control" name="newsItemThumbnail_${idx}">
<button class="btn btn-outline-primary btn-upload-image" type="button" data-target-input="newsItemThumbnail_${idx}" data-image-type="about">
<i class="fas fa-upload"></i>
</button>
</div>
</div>
</div>
<button type="button" class="btn btn-outline-danger btn-sm mt-2" onclick="this.closest('.news-item-row').remove()">Remove Article</button>
</div>
</div>`;
container.insertAdjacentHTML('beforeend', html);
// Blog selection functions for About News section
function toggleAboutBlogSelection(card, blogId) {
const checkbox = card.querySelector('.about-blog-checkbox');
const isChecking = !checkbox.checked;
if (isChecking) {
const checkedCount = document.querySelectorAll('.about-blog-checkbox:checked').length;
if (checkedCount >= 3) {
alert('You can only select up to 3 blogs.');
return;
}
}
checkbox.checked = isChecking;
handleAboutCheckboxUpdate(card, checkbox.checked);
}
function populateNewsItems(items) {
const container = document.getElementById('newsItemsContainer');
container.innerHTML = '';
items.forEach((item, i) => {
addNewsItem();
const last = container.lastElementChild;
last.querySelector(`[name="newsItemTitle_${i}"]`).value = item.title || '';
last.querySelector(`[name="newsItemCategory_${i}"]`).value = item.category || '';
last.querySelector(`[name="newsItemDate_${i}"]`).value = item.date || '';
last.querySelector(`[name="newsItemComments_${i}"]`).value = item.comments || 0;
last.querySelector(`[name="newsItemAuthorName_${i}"]`).value = item.author?.name || '';
last.querySelector(`[name="newsItemAuthorAvatar_${i}"]`).value = item.author?.avatar || '';
last.querySelector(`[name="newsItemLink_${i}"]`).value = item.link || '';
last.querySelector(`[name="newsItemThumbnail_${i}"]`).value = item.thumbnail || '';
});
function handleAboutCheckboxChange(checkbox) {
if (checkbox.checked) {
const checkedCount = document.querySelectorAll('.about-blog-checkbox:checked').length;
if (checkedCount > 3) {
checkbox.checked = false;
alert('You can only select up to 3 blogs.');
return;
}
}
const card = checkbox.closest('.blog-select-card');
handleAboutCheckboxUpdate(card, checkbox.checked);
}
function handleAboutCheckboxUpdate(card, isChecked) {
if (isChecked) {
card.classList.add('border-primary', 'bg-light');
} else {
card.classList.remove('border-primary', 'bg-light');
}
}
@@ -926,6 +929,11 @@
document.getElementById('featuresJson').value = JSON.stringify(featuresData);
// News
const selectedIds = [];
document.querySelectorAll('.about-blog-checkbox:checked').forEach(cb => {
selectedIds.push(cb.value);
});
const newsData = {
subheading: document.getElementById('newsSubheading').value.trim(),
heading: document.getElementById('newsHeading').value.trim(),
@@ -933,18 +941,8 @@
label: document.getElementById('newsCtaLabel').value.trim(),
href: document.getElementById('newsCtaHref').value.trim()
},
items: Array.from(document.querySelectorAll('.news-item-row')).map(item => ({
title: item.querySelector('[name^="newsItemTitle_"]').value.trim(),
category: item.querySelector('[name^="newsItemCategory_"]').value.trim(),
date: item.querySelector('[name^="newsItemDate_"]').value.trim(),
comments: parseInt(item.querySelector('[name^="newsItemComments_"]').value) || 0,
author: {
name: item.querySelector('[name^="newsItemAuthorName_"]').value.trim(),
avatar: item.querySelector('[name^="newsItemAuthorAvatar_"]').value.trim()
},
link: item.querySelector('[name^="newsItemLink_"]').value.trim(),
thumbnail: item.querySelector('[name^="newsItemThumbnail_"]').value.trim()
})).filter(i => i.title !== '')
selectedBlogIds: selectedIds,
items: [] // Server will populate this from selectedBlogIds
};
document.getElementById('newsJson').value = JSON.stringify(newsData);
@@ -955,4 +953,15 @@
}
</script>
</script>
<style>
.tiny {
font-size: 0.75rem;
}
.blog-select-card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
</style>