feat:Add blog management page and enhance admin layout

This commit is contained in:
Wini_Fy
2026-02-03 17:04:28 +07:00
parent bb539ef213
commit 538317eade
14 changed files with 3096 additions and 277 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+300
View File
@@ -0,0 +1,300 @@
<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">Manage blog posts and articles</p>
</div>
<div class="d-flex gap-2">
<% if (typeof frontendUrl !=='undefined' ) { %>
<a href="<%= frontendUrl %>/blog" class="btn btn-outline-primary" target="_blank">
<i class="fas fa-external-link-alt me-2"></i>View Blog Page
</a>
<% } %>
<a href="/admin/blog/create" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>Create New Post
</a>
</div>
</div>
<!-- Filters -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<form method="GET" action="/admin/blog" class="row g-3">
<div class="col-md-3">
<label class="form-label">Search</label>
<input type="text" class="form-control" name="search" value="<%= query.search || '' %>"
placeholder="Search title or excerpt...">
</div>
<div class="col-md-2">
<label class="form-label">Status</label>
<select class="form-select" name="status">
<option value="">All</option>
<option value="published" <%=query.status==='published' ? 'selected' : '' %>>Published</option>
<option value="draft" <%=query.status==='draft' ? 'selected' : '' %>>Draft</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Category</label>
<select class="form-select" name="category">
<option value="">All Categories</option>
<% categories.forEach(cat=> { %>
<option value="<%= cat.name %>" <%=query.category===cat.name ? 'selected' : '' %>>
<%= cat.name %>
</option>
<% }); %>
</select>
</div>
<div class="col-md-2">
<label class="form-label">&nbsp;</label>
<button type="submit" class="btn btn-primary w-100">
<i class="fas fa-search me-1"></i>Filter
</button>
</div>
<div class="col-md-2">
<label class="form-label">&nbsp;</label>
<a href="/admin/blog" class="btn btn-outline-secondary w-100">
<i class="fas fa-times me-1"></i>Clear
</a>
</div>
</form>
</div>
</div>
<!-- Blog List -->
<div class="card border-0 shadow-sm">
<div class="card-body p-4">
<% if (blogs && blogs.length> 0) { %>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th scope="col" style="width: 60px">Image</th>
<th scope="col">Title</th>
<th scope="col">Category</th>
<th scope="col">Status</th>
<th scope="col">Author</th>
<th scope="col">Published</th>
<th scope="col" style="width: 200px">Actions</th>
</tr>
</thead>
<tbody>
<% blogs.forEach((blog, index)=> { %>
<tr>
<td>
<% if (blog.featuredImage) { %>
<img src="<%= blog.featuredImage.startsWith('http') ? blog.featuredImage : (typeof frontendUrl !== 'undefined' ? frontendUrl : '') + blog.featuredImage %>"
alt="<%= blog.title %>" class="img-thumbnail"
style="width: 50px; height: 50px; object-fit: cover;">
<% } else { %>
<div class="bg-light d-flex align-items-center justify-content-center"
style="width: 50px; height: 50px;">
<i class="fas fa-image text-muted"></i>
</div>
<% } %>
</td>
<td>
<div>
<span class="fw-medium">
<%= blog.title %>
</span>
<% if (blog.isFeatured) { %>
<span class="badge bg-warning text-dark ms-2">Featured</span>
<% } %>
</div>
<small class="text-muted">
<%= blog.excerpt.substring(0, 60) %>...
</small>
</td>
<td>
<% if (blog.category && blog.category.length> 0) { %>
<% blog.category.slice(0, 2).forEach(cat=> { %>
<span class="badge bg-secondary me-1">
<%= cat %>
</span>
<% }); %>
<% if (blog.category.length> 2) { %>
<span class="text-muted">+<%= blog.category.length - 2 %></span>
<% } %>
<% } else { %>
<span class="text-muted">-</span>
<% } %>
</td>
<td>
<% if (blog.status==='published' ) { %>
<span class="badge bg-success">Published</span>
<% } else { %>
<span class="badge bg-secondary">Draft</span>
<% } %>
</td>
<td>
<%= blog.author || 'Admin' %>
</td>
<td>
<%= blog.publishedAt || '-' %>
</td>
<td>
<div class="btn-group" role="group">
<% if (typeof frontendUrl !== 'undefined') { %>
<a href="<%= frontendUrl %>/blog/<%= blog.slug %>" target="_blank"
class="btn btn-sm btn-outline-secondary">
<i class="fas fa-external-link-alt me-1"></i>View
</a>
<% } %>
<a href="/admin/blog/<%= blog._id %>/edit"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-edit me-1"></i>Edit
</a>
<button type="button" class="btn btn-sm btn-outline-danger"
data-custom-modal="open" data-id="<%= blog._id %>"
data-title="<%= blog.title %>">
<i class="fas fa-trash-alt me-1"></i>Delete
</button>
</div>
</td>
</tr>
<% }); %>
</tbody>
</table>
</div>
<!-- Pagination -->
<% if (pagination && pagination.total> 1) { %>
<nav aria-label="Blog pagination" class="mt-4">
<ul class="pagination justify-content-center">
<% if (pagination.current> 1) { %>
<li class="page-item">
<a class="page-link"
href="?page=<%= pagination.current - 1 %><%= query.search ? '&search=' + query.search : '' %><%= query.status ? '&status=' + query.status : '' %><%= query.category ? '&category=' + query.category : '' %>">Previous</a>
</li>
<% } %>
<% for (let i=1; i <=pagination.total; i++) { %>
<% if (i===pagination.current) { %>
<li class="page-item active">
<span class="page-link">
<%= i %>
</span>
</li>
<% } else if (i===1 || i===pagination.total || (i>= pagination.current - 2
&& i <= pagination.current + 2)) { %>
<li class="page-item">
<a class="page-link"
href="?page=<%= i %><%= query.search ? '&search=' + query.search : '' %><%= query.status ? '&status=' + query.status : '' %><%= query.category ? '&category=' + query.category : '' %>">
<%= i %>
</a>
</li>
<% } else if (i===pagination.current - 3 || i===pagination.current +
3) { %>
<li class="page-item disabled">
<span class="page-link">...</span>
</li>
<% } %>
<% } %>
<% if (pagination.current < pagination.total) { %>
<li class="page-item">
<a class="page-link"
href="?page=<%= pagination.current + 1 %><%= query.search ? '&search=' + query.search : '' %><%= query.status ? '&status=' + query.status : '' %><%= query.category ? '&category=' + query.category : '' %>">Next</a>
</li>
<% } %>
</ul>
</nav>
<% } %>
<% } else { %>
<div class="text-center py-5">
<i class="fas fa-blog text-muted mb-3" style="font-size: 3rem;"></i>
<h5 class="text-muted mb-3">No Blog Posts Found</h5>
<a href="/admin/blog/create" class="btn btn-primary">
<i class="fas fa-plus-circle me-1"></i>Create First Blog Post
</a>
</div>
<% } %>
</div>
</div>
</div>
<!-- Delete Blog Confirmation Modal -->
<div class="modal fade" id="deleteBlogModal" tabindex="-1" aria-labelledby="deleteBlogModalLabel" aria-hidden="true"
data-bs-backdrop="true" data-bs-keyboard="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="deleteBlogModalLabel">
<i class="fas fa-trash me-2"></i>Confirm Delete
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete the blog post "<span id="deleteBlogTitle" class="fw-bold"></span>"?
</p>
<p class="text-danger mb-0">
<small>
<i class="fas fa-exclamation-triangle me-1"></i>This action cannot be
undone.</small>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<form id="deleteBlogForm" method="POST" style="display: inline;">
<button type="submit" class="btn btn-danger">
<i class="fas fa-trash me-1"></i>Delete
</button>
</form>
</div>
</div>
</div>
</div>
<style>
/* Fix modal z-index - must be higher than everything else */
#deleteBlogModal {
z-index: 2050 !important;
}
#deleteBlogModal .modal-dialog {
z-index: 2060 !important;
position: relative;
}
#deleteBlogModal .modal-content {
z-index: 2070 !important;
position: relative;
}
/* Ensure modal is clickable */
#deleteBlogModal.show {
display: block !important;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function () { // Initialize modal instance once
const deleteModalElement = document.getElementById('deleteBlogModal');
const deleteModal = new bootstrap.Modal(deleteModalElement, {
backdrop: false,
keyboard: true,
focus: true
});
// Handle delete buttons
document.querySelectorAll('[data-custom-modal="open"]').forEach(button => {
button.addEventListener('click', function (e) {
e.preventDefault();
const blogId = this.getAttribute('data-id');
const blogTitle = this.getAttribute('data-title');
// Set blog title in modal
document.getElementById('deleteBlogTitle').textContent = blogTitle;
// Set form action
document.getElementById('deleteBlogForm').action = `/admin/blog/${blogId}/delete`;
// Show Bootstrap modal
deleteModal.show();
});
});
});
</script>
+107 -112
View File
@@ -1,122 +1,117 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" type="image/png" href="/uploads/layout/favicon.png" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>
<%= typeof title !=='undefined' ? title + ' | ' : '' %>CMS-GGCamp
</title>
<!-- Bootstrap CSS -->
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<!-- Font Awesome -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
<style>
:root {
--primary-color: #b8b76a;
--primary-light: #c9c88a;
--primary-dark: #9a994a;
}
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-dark);
border-color: var(--primary-dark);
}
.nav-link:hover {
color: var(--primary-color);
}
.nav-link.active {
color: var(--primary-color);
font-weight: 600;
}
</style>
<%- style %>
</head>
<body>
<div class="container-fluid">
<div class="row">
<nav class="col-md-2 d-none d-md-block bg-light sidebar">
<div class="position-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" href="/admin/dashboard">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/home">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/header">Header & Menu</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/footer">Footer</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/about">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/about-us">About Us</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/contact">Contact</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/faq">FAQ</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/terms-conditions">Terms & Conditions</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/safety">Safety</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/camp-location">Camp Location</a>
</li>
<li class="nav-item">
<li class="nav-item">
<a class="nav-link" href="/admin/form">Form</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/upload">Upload</a>
</li>
</ul>
</div>
</nav>
<head>
<link rel="icon" type="image/png" href="/uploads/layout/favicon.png" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>
<%= typeof title !=='undefined' ? title + ' | ' : '' %>CMS-GGCamp
</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
<style>
:root {
--primary-color: #b8b76a;
--primary-light: #c9c88a;
--primary-dark: #9a994a;
}
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<!-- Flash Messages Data (Hidden) -->
<% if(typeof success_msg !=='undefined' || typeof error_msg
!=='undefined' || typeof error !=='undefined' ) { %>
.btn-primary {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-dark);
border-color: var(--primary-dark);
}
.nav-link:hover {
color: var(--primary-color);
}
.nav-link.active {
color: var(--primary-color);
font-weight: 600;
}
</style>
<%- style %>
</head>
<body>
<div class="container-fluid">
<div class="row">
<nav class="col-md-2 d-none d-md-block bg-light sidebar">
<div class="position-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" href="/admin/dashboard">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/home">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/header">Header & Menu</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/footer">Footer</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/about">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/about-us">About Us</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/contact">Contact</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/faq">FAQ</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/terms-conditions">Terms & Conditions</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/safety">Safety</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/camp-location">Camp Location</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/blog">Blog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/form">Form</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/upload">Upload</a>
</li>
</ul>
</div>
</nav>
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<!-- Flash Messages Data (Hidden) -->
<% if(typeof success_msg !=='undefined' || typeof error_msg !=='undefined' || typeof error !=='undefined' ) { %>
<div id="flash-messages-data" style="display: none">
<%- JSON.stringify({ success_msg: typeof success_msg !=='undefined'
&& success_msg ? success_msg : null, error_msg: typeof error_msg
!=='undefined' && error_msg ? error_msg : null, error: typeof error
!=='undefined' && error ? error : null }) %>
<%- JSON.stringify({ success_msg: typeof success_msg !=='undefined' && success_msg ? success_msg : null,
error_msg: typeof error_msg !=='undefined' && error_msg ? error_msg : null, error: typeof error
!=='undefined' && error ? error : null }) %>
</div>
<% } %>
<div class="py-3"><%- body %></div>
</main>
</div>
<div class="py-3"><%- body %></div>
</main>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
<%- script %>
</body>
</html>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
<%- script %>
</body>
</html>
+71 -95
View File
@@ -727,91 +727,67 @@
About
</a>
<ul class="dropdown-menu">
<li>
<a
class="dropdown-item <%= currentPath === '/admin/about-us' ? 'active' : '' %>"
href="/admin/about-us"
>About us</a
>
</li>
<li>
<a
class="dropdown-item <%= currentPath === '/admin/safety' ? 'active' : '' %>"
href="/admin/safety"
>Safety</a
>
</li>
<li>
<a
class="dropdown-item <%= currentPath === '/admin/FAQ' ? 'active' : '' %>"
href="/admin/faq"
>FAQ</a
>
</li>
<li>
<a
class="dropdown-item <%= currentPath === '/admin/insurance' ? 'active' : '' %>"
href="/admin/insurance"
>Insurance</a
>
</li>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item <%= currentPath === '/admin/about-us' ? 'active' : '' %>"
href="/admin/about-us">About us</a>
</li>
<li>
<a class="dropdown-item <%= currentPath === '/admin/safety' ? 'active' : '' %>"
href="/admin/safety">Safety</a>
</li>
<li>
<a class="dropdown-item <%= currentPath === '/admin/FAQ' ? 'active' : '' %>" href="/admin/faq">FAQ</a>
</li>
<li>
<a class="dropdown-item <%= currentPath === '/admin/insurance' ? 'active' : '' %>"
href="/admin/insurance">Insurance</a>
</li>
<li>
<a class="dropdown-item <%= currentPath === '/admin/travel' ? 'active' : '' %>"
href="/admin/travel">Travel</a>
</li>
<li>
<a class="dropdown-item <%= currentPath === '/admin/terms-conditions' ? 'active' : '' %>"
href="/admin/terms-conditions">Terms & Conditions</a>
</li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link <%= currentPath === '/admin/contact' ? 'active' : '' %>" href="/admin/contact">Contact
Us</a>
</li>
<li class="nav-item">
<a class="nav-link <%= currentPath === '/admin/camp-location' ? 'active' : '' %>"
href="/admin/camp-location">Camp Location</a>
</li>
<li class="nav-item">
<a class="nav-link <%= currentPath === '/admin/activity' ? 'active' : '' %>" href="/admin/activity">Activity
& Booking</a>
</li>
<li class="nav-item">
<a class="nav-link <%= currentPath === '/admin/blog' || currentPath.startsWith('/admin/blog') ? 'active' : '' %>"
href="/admin/blog">Blog</a>
</li>
</ul>
<li>
<a
class="dropdown-item <%= currentPath === '/admin/travel' ? 'active' : '' %>"
href="/admin/travel"
>Travel</a
>
</li>
<li>
<a
class="dropdown-item <%= currentPath === '/admin/terms-conditions' ? 'active' : '' %>"
href="/admin/terms-conditions"
>Terms & Conditions</a
>
</li>
</ul>
</li>
<li class="nav-item">
<a
class="nav-link <%= currentPath === '/admin/contact' ? 'active' : '' %>"
href="/admin/contact"
>Contact Us</a
>
</li>
<li class="nav-item">
<a
class="nav-link <%= currentPath === '/admin/camp-location' ? 'active' : '' %>"
href="/admin/camp-location"
>Camp Location</a
>
</li>
<li class="nav-item">
<a
class="nav-link <%= currentPath === '/admin/activity' ? 'active' : '' %>"
href="/admin/activity"
>Activity & Booking</a
>
</li>
</ul>
</ul>
<div class="d-flex align-items-center">
<% if (locals.user) { %>
<a href="/admin/dashboard" class="btn btn-primary me-2">
<i class="fas fa-tachometer-alt me-1"></i>Dashboard
</a>
<a href="/auth/logout" class="btn btn-outline-danger">
<i class="fas fa-sign-out-alt me-1"></i>Logout
</a>
<% } else { %>
<a href="/auth/login" class="btn btn-outline-primary">
<i class="fas fa-sign-in-alt me-1"></i>Login
</a>
<% } %>
<a href="/admin/dashboard" class="btn btn-primary me-2">
<i class="fas fa-tachometer-alt me-1"></i>Dashboard
</a>
<a href="/auth/logout" class="btn btn-outline-danger">
<i class="fas fa-sign-out-alt me-1"></i>Logout
</a>
<% } else { %>
<a href="/auth/login" class="btn btn-outline-primary">
<i class="fas fa-sign-in-alt me-1"></i>Login
</a>
<% } %>
</div>
</div>
</div>
@@ -821,12 +797,12 @@
<main>
<!-- Flash Messages Data (Hidden) -->
<% if(typeof success_msg !=='undefined' || typeof error_msg !=='undefined' || typeof error !=='undefined' ) { %>
<div id="flash-messages-data" style="display: none">
<%- JSON.stringify({ success_msg: typeof success_msg !=='undefined' && success_msg ? success_msg : null,
<div id="flash-messages-data" style="display: none">
<%- JSON.stringify({ success_msg: typeof success_msg !=='undefined' && success_msg ? success_msg : null,
error_msg: typeof error_msg !=='undefined' && error_msg ? error_msg : null, error: typeof error !=='undefined'
&& error ? error : null }) %>
</div>
<% } %> <%- body %>
</div>
<% } %> <%- body %>
</main>
<!-- Footer -->
@@ -841,14 +817,14 @@
<h5 class="mb-3">Links</h5>
<ul class="list-unstyled">
<% if (locals.user) { %>
<li class="mb-2">
<a href="/admin/dashboard" class="text-decoration-none hover-opacity">Dashboard</a>
</li>
<% } else { %>
<li class="mb-2">
<a href="/auth/login" class="text-decoration-none hover-opacity">Login</a>
</li>
<% } %>
<li class="mb-2">
<a href="/admin/dashboard" class="text-decoration-none hover-opacity">Dashboard</a>
</li>
<% } else { %>
<li class="mb-2">
<a href="/auth/login" class="text-decoration-none hover-opacity">Login</a>
</li>
<% } %>
</ul>
</div>
<div class="col-md-4">
@@ -865,7 +841,7 @@
<div class="text-center">
<p class="mb-0">
&copy; <%= new Date().getFullYear() %> CMS-GGCamp. All rights
reserved.
reserved.
</p>
</div>
</div>
@@ -928,7 +904,7 @@
formattedType + '</span><i class="fas fa-chevron-right nav-submenu-indicator"></i>';
if (window.location.pathname.includes("/admin/level") && window.location.search.includes(
"type=" + type)) {
"type=" + type)) {
link.classList.add("active");
}