Files
cms.uldp.edu.vn/views/admin/footer/index.ejs

764 lines
33 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)">
Footer Management
</h1>
<p class="text-muted mb-0">Edit footer content and structure</p>
</div>
</div>
<div class="row">
<div class="col-12">
<form action="/admin/footer/update" method="POST" class="content-with-fixed-buttons" id="footerForm">
<!-- Hidden consolidated JSON for footer (used for single-payload submit) -->
<input type="hidden" name="footerJson" id="footerJson">
<input type="hidden" name="activeTab" id="activeTabInput" value="about">
<!-- Navigation 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" role="tablist">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#about" role="tab">
<i class="fas fa-info-circle me-2"></i>
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#logo" role="tab">
<i class="fas fa-image me-2"></i>Logo
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#contact" role="tab">
<i class="fas fa-address-book me-2"></i>Contact & Address
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#columns" role="tab">
<i class="fas fa-columns me-2"></i>Footer Columns
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#social" role="tab">
<i class="fas fa-share-alt me-2"></i>Social Links
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#copyright" role="tab">
<i class="fas fa-copyright me-2"></i>Copyright
</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<!-- About GGC Tab -->
<div class="tab-pane fade show active" id="about" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-light">
<h6 class="mb-0">About GGC Section</h6>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-medium">Title</label>
<input type="text" class="form-control" name="about[title]"
value="<%= (data.about && data.about.title) ? data.about.title : 'About GGC' %>" />
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<label class="form-label fw-medium">Description</label>
<textarea class="form-control" name="about[description]"
rows="4"><%= (data.about && data.about.description) ? data.about.description : '' %></textarea>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="form-label fw-medium">Map Link Text</label>
<input type="text" class="form-control" name="about[mapLink][text]"
value="<%= (data.about && data.about.mapLink && data.about.mapLink.text) ? data.about.mapLink.text : 'Check on google map' %>" />
</div>
<div class="col-md-6">
<label class="form-label fw-medium">Map Link URL</label>
<input type="url" class="form-control" name="about[mapLink][url]"
value="<%= (data.about && data.about.mapLink && data.about.mapLink.url) ? data.about.mapLink.url : '' %>" />
</div>
</div>
</div>
</div>
</div>
<!-- Logo Tab -->
<div class="tab-pane fade" id="logo" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-light">
<h6 class="mb-0">Logo Configuration</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<label class="form-label fw-medium">Logo Image URL</label>
<div class="input-group mb-2">
<input type="text" class="form-control" id="logoImage" name="logo[src]"
value="<%= (data.logo && data.logo.src) ? data.logo.src : '' %>" />
<button type="button" class="btn btn-outline-primary btn-upload-image"
data-target-input="logoImage" data-image-type="layout">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
<small class="form-text text-muted">Recommended size: 200x60px</small>
</div>
<div class="col-md-6">
<label class="form-label fw-medium">Logo Alt Text</label>
<input type="text" class="form-control" id="logoAlt" name="logo[alt]"
value="<%= (data.logo && data.logo.alt) ? data.logo.alt : '' %>" />
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<% if (data.logo && data.logo.src) { %>
<img src="<%= data.logo.src %>" class="img-thumbnail" style="
max-height: 100px;
max-width: 300px;
object-fit: contain;
background: var(--primary-color);
" alt="Logo preview" />
<% } %>
</div>
</div>
</div>
</div>
</div>
<!-- Contact & Address Tab -->
<div class="tab-pane fade" id="contact" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-light">
<h6 class="mb-0">Contact & Address Information</h6>
</div>
<div class="card-body">
<!-- Address -->
<div class="row mb-4">
<div class="col-12">
<h6 class="fw-medium mb-3">Address</h6>
</div>
<div class="col-md-6">
<label class="form-label fw-medium">Address Text</label>
<input type="text" class="form-control" name="address[text]" value="<%= data.address.text %>" />
</div>
<div class="col-md-6">
<label class="form-label fw-medium">Address Line 2</label>
<input type="text" class="form-control" name="address[address2]"
value="<%= (data.address && data.address.address2) ? data.address.address2 : '' %>" />
</div>
<div class="col-md-6">
<label class="form-label fw-medium">Map URL</label>
<input type="url" class="form-control" name="address[mapUrl]"
value="<%= data.address.mapUrl %>" />
</div>
<div class="col-md-6">
<label class="form-label fw-medium">Map/Link 2 (optional)</label>
<input type="url" class="form-control" name="address[link2]"
value="<%= (data.address && data.address.link2) ? data.address.link2 : '' %>" />
</div>
</div>
<!-- Contact Info -->
<div class="row">
<div class="col-12">
<h6 class="fw-medium mb-3">Contact Information</h6>
</div>
<div class="col-md-4">
<label class="form-label fw-medium">Phone Number</label>
<input type="text" class="form-control" name="contact[phone]"
value="<%= data.contact.phone %>" />
</div>
<div class="col-md-4">
<label class="form-label fw-medium">Working Hours</label>
<input type="text" class="form-control" name="contact[hours]"
value="<%= data.contact.hours %>" />
</div>
<div class="col-md-4">
<label class="form-label fw-medium">Email</label>
<input type="email" class="form-control" name="contact[email]"
value="<%= data.contact.email %>" />
</div>
</div>
</div>
</div>
</div>
<!-- Footer Columns Tab -->
<div class="tab-pane fade" id="columns" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h6 class="mb-0">Footer Columns</h6>
<button type="button" class="btn btn-primary btn-sm" id="addColumn">
<i class="fas fa-plus me-1"></i>Add Column
</button>
</div>
<div class="card-body">
<div id="columnsContainer">
<% data.columns.forEach((column, columnIndex)=> { %>
<div class="card mb-3 border" data-column-index="<%= columnIndex %>">
<div class="card-header bg-light">
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-0">Column <%= columnIndex + 1 %>
</h6>
<div class="btn-group-action">
<button type="button" class="btn btn-sm remove-column" data-column-index="<%= columnIndex %>">
<i class="fas fa-trash text-action-delete"></i>
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label fw-medium">Column Title</label>
<input type="text" class="form-control" name="columns[<%= columnIndex %>][title]"
value="<%= column.title %>" />
</div>
</div>
<div class="row">
<div class="col-12">
<h6 class="fw-medium mb-3">Links</h6>
<div class="column-links-container" data-column-index="<%= columnIndex %>">
<% column.links.forEach((link, linkIndex)=> {
%>
<div class="card mb-2 border" data-link-index="<%= linkIndex %>">
<div class="card-body">
<div class="row g-2">
<div class="col-md-5">
<label class="form-label form-label-sm">Link Title</label>
<input type="text" class="form-control form-control-sm"
name="columns[<%= columnIndex %>][links][<%= linkIndex %>][title]"
value="<%= link.title %>" />
</div>
<div class="col-md-6">
<label class="form-label form-label-sm">URL</label>
<input type="text" class="form-control form-control-sm"
name="columns[<%= columnIndex %>][links][<%= linkIndex %>][url]"
value="<%= link.url %>" placeholder="/about-us/" />
</div>
<div class="col-md-1 d-flex justify-content-end align-items-end">
<div class="btn-group-action">
<button type="button" class="btn btn-sm remove-link"
data-column-index="<%= columnIndex %>" data-link-index="<%= linkIndex %>">
<i class="fas fa-trash text-action-delete"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<% }); %>
</div>
<button type="button" class="btn btn-primary btn-sm add-link"
data-column-index="<%= columnIndex %>">
<i class="fas fa-plus me-1"></i>Add Link
</button>
</div>
</div>
</div>
</div>
<% }); %>
</div>
</div>
</div>
</div>
<!-- Social Links Tab -->
<div class="tab-pane fade" id="social" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-light d-flex justify-content-between align-items-center">
<h6 class="mb-0">Social Media Links</h6>
<button type="button" class="btn btn-primary btn-sm" id="addSocialLink">
<i class="fas fa-plus me-1"></i>Add Social Link
</button>
</div>
<div class="card-body">
<div id="socialLinksContainer">
<% data.social.links.forEach((link, index)=> { %>
<div class="card mb-3 border" data-social-index="<%= index %>">
<div class="card-body">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label fw-medium">Platform</label>
<input type="text" class="form-control" name="social[links][<%= index %>][platform]"
value="<%= link.platform %>" />
</div>
<div class="col-md-4">
<label class="form-label fw-medium">URL</label>
<input type="text" class="form-control" name="social[links][<%= index %>][url]"
value="<%= link.url %>" placeholder="https://facebook.com/..." />
</div>
<div class="col-md-4">
<label class="form-label fw-medium">Icon Class</label>
<input type="text" class="form-control" name="social[links][<%= index %>][icon]"
value="<%= link.icon %>" />
</div>
<div class="col-md-1 d-flex justify-content-end align-items-end">
<div class="btn-group-action">
<button type="button" class="btn btn-sm remove-social-link"
data-social-index="<%= index %>">
<i class="fas fa-trash text-action-delete"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<% }); %>
</div>
</div>
</div>
</div>
<!-- Copyright Tab -->
<div class="tab-pane fade" id="copyright" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-light">
<h6 class="mb-0">Copyright Information</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-12">
<label class="form-label fw-medium">Copyright Text</label>
<textarea class="form-control" name="copyright[text]" rows="3">
<%= data.copyright.text %></textarea>
<small class="form-text text-muted">You can use HTML tags for formatting</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Move buttons to fixed bottom -->
<div class="fixed-bottom-buttons">
<button type="reset" class="btn btn-secondary px-4">
<i class="fas fa-undo me-1"></i>Reset
</button>
<button type="submit" class="btn btn-primary px-4">
<i class="fas fa-save me-1"></i>Save Changes
</button>
</div>
</form>
</div>
</div>
</div>
<style>
.content-with-fixed-buttons {
padding-bottom: 80px;
}
.fixed-bottom-buttons {
position: fixed;
bottom: 0;
right: 0;
padding: 1rem;
z-index: 1000;
width: 25%;
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
.fixed-bottom-buttons .btn {
min-width: 120px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.form-label-sm {
font-size: 0.75rem;
font-weight: 500;
margin-bottom: 0.25rem;
}
.card-body .form-control-sm {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function () {
// Derive current counts from DOM to avoid template injection issues
let columnIndex =
document.querySelectorAll("#columnsContainer [data-column-index]")
.length || 0;
let socialLinkIndex =
document.querySelectorAll("#socialLinksContainer [data-social-index]")
.length || 0;
// Handle Active Tab Persistence
const urlParams = new URLSearchParams(window.location.search);
const activeTabObj = urlParams.get('activeTab');
if (activeTabObj) {
const tabTrigger = document.querySelector(`a[href="#${activeTabObj}"]`);
if (tabTrigger) {
new bootstrap.Tab(tabTrigger).show();
// Update hidden input to match restored tab
document.getElementById('activeTabInput').value = activeTabObj;
}
}
// Update hidden input on tab change
document.querySelectorAll('a[data-bs-toggle="tab"]').forEach(tab => {
tab.addEventListener('shown.bs.tab', function (event) {
const targetId = event.target.getAttribute('href').substring(1); // remove #
document.getElementById('activeTabInput').value = targetId;
});
});
// Add Column
document.getElementById("addColumn").addEventListener("click", function () {
const container = document.getElementById("columnsContainer");
const newColumn = document.createElement("div");
newColumn.className = "card mb-3 border";
newColumn.dataset.columnIndex = columnIndex;
newColumn.innerHTML = `
<div class="card-header bg-light">
<div class="d-flex justify-content-between align-items-center">
<h6 class="mb-0">Column ${columnIndex + 1}</h6>
<div class="btn-group-action">
<button type="button" class="btn btn-sm remove-column" data-column-index="${columnIndex}">
<i class="fas fa-trash text-action-delete"></i>
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label fw-medium">Column Title</label>
<input type="text" class="form-control" name="columns[${columnIndex}][title]" value="">
</div>
</div>
<div class="row">
<div class="col-12">
<h6 class="fw-medium mb-3">Links</h6>
<div class="column-links-container" data-column-index="${columnIndex}">
</div>
<button type="button" class="btn btn-primary btn-sm add-link" data-column-index="${columnIndex}">
<i class="fas fa-plus me-1"></i>Add Link
</button>
</div>
</div>
</div>
`;
container.appendChild(newColumn);
columnIndex++;
});
// Add Social Link
document
.getElementById("addSocialLink")
.addEventListener("click", function () {
const container = document.getElementById("socialLinksContainer");
const newLink = document.createElement("div");
newLink.className = "card mb-3 border";
newLink.dataset.socialIndex = socialLinkIndex;
newLink.innerHTML = `
<div class="card-body">
<div class="row g-3">
<div class="col-md-3">
<label class="form-label fw-medium">Platform</label>
<input type="text" class="form-control" name="social[links][${socialLinkIndex}][platform]" value="">
</div>
<div class="col-md-4">
<label class="form-label fw-medium">URL</label>
<input type="text" class="form-control" name="social[links][${socialLinkIndex}][url]" value="" placeholder="https://facebook.com/...">
</div>
<div class="col-md-4">
<label class="form-label fw-medium">Icon Class</label>
<input type="text" class="form-control" name="social[links][${socialLinkIndex}][icon]" value="">
</div>
<div class="col-md-1 d-flex justify-content-end align-items-end">
<div class="btn-group-action">
<button type="button" class="btn btn-sm remove-social-link" data-social-index="${socialLinkIndex}">
<i class="fas fa-trash text-action-delete"></i>
</button>
</div>
</div>
</div>
</div>
`;
container.appendChild(newLink);
socialLinkIndex++;
});
// Remove Column
document.addEventListener("click", function (e) {
if (e.target.closest(".remove-column")) {
e.target.closest(".card").remove();
}
});
// Remove Social Link
document.addEventListener("click", function (e) {
if (e.target.closest(".remove-social-link")) {
e.target.closest(".card").remove();
}
});
// Add Link to Column
document.addEventListener("click", function (e) {
if (e.target.closest(".add-link")) {
const columnIndex = e.target.closest(".add-link").dataset.columnIndex;
const container = e.target
.closest(".card-body")
.querySelector(".column-links-container");
const linkIndex = container.children.length;
const newLink = document.createElement("div");
newLink.className = "card mb-2 border";
newLink.dataset.linkIndex = linkIndex;
newLink.innerHTML = `
<div class="card-body">
<div class="row g-2">
<div class="col-md-5">
<label class="form-label form-label-sm">Link Title</label>
<input type="text" class="form-control form-control-sm" name="columns[${columnIndex}][links][${linkIndex}][title]" value="">
</div>
<div class="col-md-6">
<label class="form-label form-label-sm">URL</label>
<input type="text" class="form-control form-control-sm" name="columns[${columnIndex}][links][${linkIndex}][url]" value="" placeholder="/about-us/">
</div>
<div class="col-md-1 d-flex justify-content-end align-items-end">
<div class="btn-group-action">
<button type="button" class="btn btn-sm remove-link" data-column-index="${columnIndex}" data-link-index="${linkIndex}">
<i class="fas fa-trash text-action-delete"></i>
</button>
</div>
</div>
</div>
</div>
`;
container.appendChild(newLink);
}
});
// Remove Link from Column
document.addEventListener("click", function (e) {
if (e.target.closest(".remove-link")) {
e.target.closest(".card").remove();
}
});
// Initialize image upload buttons
document.querySelectorAll(".btn-upload-image").forEach((button) => {
button.addEventListener("click", function () {
const targetInput = this.dataset.targetInput;
const imageType = this.dataset.imageType;
openImageUploader(targetInput, imageType);
});
});
// Form submission
document
.getElementById("footerForm")
.addEventListener("submit", function (e) {
// Build consolidated JSON payload for footer and set hidden input
try {
const buildFooterData = () => {
const getVal = (selector) => {
const el = document.querySelector(selector);
return el ? el.value : '';
};
const about = {
title: getVal('input[name="about[title]"]'),
description: getVal('textarea[name="about[description]"]'),
mapLink: {
text: getVal('input[name="about[mapLink][text]"]'),
url: getVal('input[name="about[mapLink][url]"]')
}
};
const logo = {
src: getVal('input[name="logo[src]"]'),
alt: getVal('input[name="logo[alt]"]')
};
const address = {
text: getVal('input[name="address[text]"]'),
address2: getVal('input[name="address[address2]"]'),
mapUrl: getVal('input[name="address[mapUrl]"]'),
link2: getVal('input[name="address[link2]"]')
};
const contact = {
phone: getVal('input[name="contact[phone]"]'),
hours: getVal('input[name="contact[hours]"]'),
email: getVal('input[name="contact[email]"]')
};
// Columns
const columns = [];
document.querySelectorAll('#columnsContainer > .card[data-column-index]').forEach(colCard => {
const colIndex = colCard.dataset.columnIndex;
const titleEl = colCard.querySelector(`input[name="columns[${colIndex}][title]"]`);
const column = { title: titleEl ? titleEl.value : '', links: [] };
const linksContainer = colCard.querySelector('.column-links-container');
if (linksContainer) {
linksContainer.querySelectorAll('.card[data-link-index]').forEach(linkCard => {
const linkTitle = linkCard.querySelector('input[name^="columns"][name$="[title]"]');
const linkUrl = linkCard.querySelector('input[name^="columns"][name$="[url]"]');
if (linkTitle && linkUrl) {
column.links.push({ title: linkTitle.value, url: linkUrl.value });
}
});
}
columns.push(column);
});
// Social links
const socialLinks = [];
document.querySelectorAll('#socialLinksContainer > .card[data-social-index]').forEach(sCard => {
const platform = sCard.querySelector('input[name^="social"][name$="[platform]"]')?.value || '';
const url = sCard.querySelector('input[name^="social"][name$="[url]"]')?.value || '';
const icon = sCard.querySelector('input[name^="social"][name$="[icon]"]')?.value || '';
if (platform || url || icon) socialLinks.push({ platform, url, icon });
});
const copyright = { text: getVal('textarea[name="copyright[text]"]') };
return { about, logo, address, contact, columns, social: { links: socialLinks }, copyright };
};
const payload = buildFooterData();
document.getElementById('footerJson').value = JSON.stringify(payload);
} catch (err) {
console.error('Error building footerJson payload:', err);
}
// Show loading state
const submitBtn = this.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Saving...';
submitBtn.disabled = true;
});
});
function openImageUploader(targetInput, imageType) {
// Tạo input file ẩn
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.accept = "image/*";
fileInput.style.display = "none";
document.body.appendChild(fileInput);
// Xử lý khi chọn file
fileInput.onchange = async function (e) {
const file = e.target.files[0];
if (!file) return;
try {
// Tạo FormData
const formData = new FormData();
formData.append("image", file);
// Disable nút upload và hiển thị loading
const uploadBtn = document.querySelector(
`[data-target-input="${targetInput}"]`
);
const originalBtnHtml = uploadBtn.innerHTML;
uploadBtn.disabled = true;
uploadBtn.innerHTML =
'<i class="fas fa-spinner fa-spin me-1"></i>Uploading...';
// Gửi request upload
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");
}
// Cập nhật đường dẫn ảnh vào input (support name or id)
const input = document.querySelector(`input[name="${targetInput}"]`) || document.getElementById(targetInput) || document.querySelector(`#${targetInput}`);
if (!input) {
throw new Error("Target input not found");
}
input.value = result.path;
// Tìm hoặc tạo preview container
let imgPreview = input.parentElement.nextElementSibling;
while (imgPreview && !imgPreview.classList.contains("mt-3")) {
imgPreview = imgPreview.nextElementSibling;
}
if (!imgPreview) {
// Tạo mới phần tử preview nếu chưa có
imgPreview = document.createElement("div");
imgPreview.className = "mt-3";
const img = document.createElement("img");
img.className = "img-thumbnail";
img.style.maxHeight = "100px";
img.style.maxWidth = "300px";
img.style.objectFit = "contain";
img.style.background = "var(--primary-color)";
img.alt = "Image preview";
imgPreview.appendChild(img);
input.parentElement.parentElement.appendChild(imgPreview);
}
// Cập nhật ảnh preview
const img = imgPreview.querySelector("img");
if (img) {
img.src = result.path;
}
// Restore nút upload
uploadBtn.disabled = false;
uploadBtn.innerHTML = originalBtnHtml;
// Cleanup
document.body.removeChild(fileInput);
} catch (error) {
console.error("Upload error:", error);
alert("Upload failed: " + error.message);
// Restore nút upload
const uploadBtn = document.querySelector(
`[data-target-input="${targetInput}"]`
);
uploadBtn.disabled = false;
uploadBtn.innerHTML = originalBtnHtml;
// Cleanup
if (document.body.contains(fileInput)) {
document.body.removeChild(fileInput);
}
}
};
// Trigger file selection
fileInput.click();
}
</script>