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

1215 lines
44 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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)">
Header Management
</h1>
<p class="text-muted mb-0">Edit header content and menu structure</p>
</div>
</div>
<div class="row">
<div class="col-12">
<form
action="/admin/header/update"
method="POST"
class="content-with-fixed-buttons"
id="headerForm"
>
<!-- Hidden inputs for JSON data -->
<input type="hidden" name="topbarJson" id="topbarJson" />
<input type="hidden" name="logo" id="logoInput" />
<input type="hidden" name="activeTab" id="activeTabInput" value="topbar" />
<input type="hidden" name="menuUpdates" id="menuUpdates" />
<!-- 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="#topbar"
role="tab"
>
<i class="fas fa-bars me-2"></i>Topbar
</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="#menu"
role="tab"
>
<i class="fas fa-sitemap me-2"></i>Menu Structure
</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<!-- Topbar Tab -->
<div class="tab-pane fade show active" id="topbar" role="tabpanel">
<div class="row g-4">
<!-- Contact Information -->
<div class="col-md-12">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0">
<i class="fas fa-phone me-2"></i>Contact Information
</h6>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label fw-medium">Phone Number</label>
<input
type="text"
class="form-control"
id="contactPhone"
value="<%= data.topbar.contactInfo.phone %>"
placeholder="+1 234 567 890"
/>
</div>
<div class="col-md-6">
<label class="form-label fw-medium">Email Address</label>
<input
type="email"
class="form-control"
id="contactEmail"
value="<%= data.topbar.contactInfo.email || '' %>"
placeholder="info@example.com"
/>
</div>
<div class="col-md-12">
<label class="form-label fw-medium">Location</label>
<input
type="text"
class="form-control"
id="contactLocation"
value="<%= data.topbar.contactInfo.location || '' %>"
placeholder="69 Street, 5th Avenue LA, United States"
/>
</div>
</div>
</div>
</div>
</div>
<!-- Social Links -->
<div class="col-md-12">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0">
<i class="fas fa-share-alt me-2"></i>Social Media Links
<small class="text-muted ms-2">(Drag to reorder)</small>
</h6>
</div>
<div class="card-body">
<div id="socialLinksContainer" class="social-links-sortable">
<!-- Social links will be populated here -->
</div>
<button
type="button"
class="btn btn-outline-primary btn-sm"
id="addSocialLink"
>
<i class="fas fa-plus me-1"></i>Add Social Link
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Logo Tab -->
<div class="tab-pane fade" id="logo" role="tabpanel">
<div class="row g-4">
<div class="col-md-12">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0">
<i class="fas fa-image me-2"></i>Logo Configuration
</h6>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label fw-medium">Logo Image</label>
<div class="input-group mb-2">
<input
type="text"
class="form-control"
id="logoImage"
value="<%= data.logo %>"
placeholder="/path/to/logo.png"
/>
<button
type="button"
class="btn btn-outline-primary btn-upload-image"
data-target-input="logoImage"
data-image-type="header"
>
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
<small class="text-muted">Recommended size: 200x60px</small>
</div>
<div class="col-md-6" id="logoPreviewContainer">
<% if (data.logo) { %>
<img
src="<%= data.logo %>"
class="img-thumbnail"
style="max-height: 100px; max-width: 300px; object-fit: contain; background: #b8b76a;"
alt="Logo preview"
/>
<% } %>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Menu Structure Tab -->
<div class="tab-pane fade" id="menu" role="tabpanel">
<div class="row g-4">
<div class="col-md-12">
<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-sitemap me-2"></i>Menu Structure
</h6>
<div>
<button
type="button"
class="btn btn-outline-success btn-sm me-2"
id="saveMenuChanges"
style="display: none"
>
<i class="fas fa-save me-1"></i>Save Changes
</button>
<button
type="button"
class="btn btn-outline-primary btn-sm"
id="refreshMenuTree"
>
<i class="fas fa-sync-alt me-1"></i>Refresh
</button>
</div>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
Click "Save Changes" to apply menu structure updates.
</div>
<div id="menuTreeContainer">
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2">Loading menu structure...</p>
</div>
</div>
</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">
<i class="fas fa-undo"></i>
<span>Reset</span>
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i>
<span>Save Changes</span>
</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
let socialLinkIndex = 0;
// Initialize social links from data
initializeSocialLinks();
updateHiddenInputs();
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();
document.getElementById('activeTabInput').value = activeTabObj;
}
}
document.querySelectorAll('a[data-bs-toggle="tab"]').forEach(tab => {
tab.addEventListener('shown.bs.tab', function (event) {
const targetId = event.target.getAttribute('href').substring(1);
document.getElementById('activeTabInput').value = targetId;
});
});
document.querySelector('a[href="#menu"]').addEventListener('shown.bs.tab', function () {
loadMenuTree();
});
document.getElementById('refreshMenuTree').addEventListener('click', function () {
loadMenuTree();
});
document.getElementById('saveMenuChanges').addEventListener('click', function () {
saveMenuChanges();
});
document.querySelectorAll('.btn-upload-image').forEach(button => {
button.addEventListener('click', function () {
const targetInput = this.dataset.targetInput;
const imageType = this.dataset.imageType;
openImageUploader(targetInput, imageType);
});
});
document.getElementById('headerForm').addEventListener('submit', function (e) {
e.preventDefault();
updateHiddenInputs();
const submitBtn = this.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Đang lưu...';
submitBtn.disabled = true;
// Collect form data as JSON
const formData = {
topbarJson: document.getElementById('topbarJson').value,
logo: document.getElementById('logoInput').value,
menuUpdates: document.getElementById('menuUpdates').value,
activeTab: document.getElementById('activeTabInput').value
};
console.log('=== FORM SUBMISSION ===');
console.log('Form data being sent:', formData);
console.log('topbarJson parsed:', JSON.parse(formData.topbarJson || '{}'));
// Send via AJAX with JSON
fetch('/admin/header/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => {
console.log('Response status:', response.status);
return response.json();
})
.then(result => {
console.log('Response result:', result);
if (result.success) {
showNotification('Changes saved successfully', 'success');
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
} else {
showNotification('Error: ' + (result.message || 'Unable to save changes'), 'error');
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
}
})
.catch(error => {
console.error('Error:', error);
showNotification('Lỗi: ' + error.message, 'error');
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
});
});
function updateHiddenInputs() {
const topbarData = {
contactInfo: {
phone: document.getElementById('contactPhone').value || '',
email: document.getElementById('contactEmail').value || '',
location: document.getElementById('contactLocation').value || ''
},
socialLinks: []
};
// Collect social links
document.querySelectorAll('.social-platform').forEach((input) => {
const platform = input.value.trim();
const urlInput = document.querySelector(`.social-url[data-index="${input.dataset.index}"]`);
const iconInput = document.querySelector(`.social-icon[data-index="${input.dataset.index}"]`);
const url = urlInput ? urlInput.value.trim() : '';
const icon = iconInput ? iconInput.value.trim() : '';
if (platform && url) {
topbarData.socialLinks.push({
platform: platform,
url: url,
icon: icon
});
}
});
document.getElementById('topbarJson').value = JSON.stringify(topbarData);
document.getElementById('logoInput').value = document.getElementById('logoImage').value || '';
try {
const menuUpdates = collectMenuUpdates();
document.getElementById('menuUpdates').value = JSON.stringify(menuUpdates);
} catch (e) {
console.error('Error collecting menu updates:', e);
document.getElementById('menuUpdates').value = '[]';
}
}
/**
* Collect menu updates from the menu tree
* Returns an empty array if no menu tree exists
*/
function collectMenuUpdates() {
// For now, return empty array as menu structure management is coming soon
// This function can be expanded when menu tree functionality is implemented
return [];
}
function initializeSocialLinks() {
const container = document.getElementById('socialLinksContainer');
const socialLinks = <%- JSON.stringify(data.topbar.socialLinks || []) %>;
if (socialLinks.length === 0) {
// Add default platforms if no social links exist
const platforms = ['linkedin', 'twitter', 'instagram', 'youtube'];
platforms.forEach((platform, index) => {
addSocialLinkRow(platform, '', `fa-brands fa-${platform}`, index);
});
socialLinkIndex = platforms.length;
} else {
// Load existing social links
socialLinks.forEach((social, index) => {
const platform = social.platform || '';
const url = social.url || '';
const icon = social.icon || '';
addSocialLinkRow(platform, url, icon, index);
});
socialLinkIndex = socialLinks.length;
}
}
function addSocialLinkRow(platform, url, icon, index) {
const container = document.getElementById('socialLinksContainer');
const socialLink = document.createElement('div');
socialLink.className = 'card mb-3 border social-link-item';
socialLink.draggable = true;
socialLink.dataset.platform = platform;
// Escape HTML to prevent XSS
const escapedPlatform = (platform || '').replace(/[&<>"']/g, char => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}[char]));
const escapedUrl = (url || '').replace(/[&<>"']/g, char => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}[char]));
const escapedIcon = (icon || '').replace(/[&<>"']/g, char => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}[char]));
socialLink.innerHTML = `
<div class="card-body">
<div class="row g-3 align-items-end">
<div class="col-md-1 d-flex justify-content-center align-items-center w-auto">
<label class="form-label fw-medium">&nbsp;</label>
<div class="drag-handle" title="Drag to reorder" style="cursor: grab; font-size: 1.2rem; color: #999; user-select: none;">
<i class="fas fa-grip-vertical"></i>
</div>
</div>
<div class="col-md-2">
<label class="form-label fw-medium">Platform</label>
<input type="text" class="form-control social-platform" value="${escapedPlatform}" data-index="${index}" disabled />
</div>
<div class="col-md-4">
<label class="form-label fw-medium">URL</label>
<input type="text" class="form-control social-url" value="${escapedUrl}" data-index="${index}" placeholder="https://..." />
</div>
<div class="col-md-3">
<label class="form-label fw-medium">Icon Class</label>
<input type="text" class="form-control social-icon" value="${escapedIcon}" data-index="${index}" />
</div>
<div class="col-md-2">
<label class="form-label">&nbsp;</label>
<div class="btn-group w-100" role="group">
<button type="button" class="btn btn-outline-primary btn-sm edit-social-link mx-3 rounded" style="transform: none" data-index="${index}" title="Edit">
<i class="fas fa-edit"></i>
</button>
<button type="button" class="btn btn-outline-danger btn-sm remove-social-link rounded" data-index="${index}" title="Delete">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
`;
container.appendChild(socialLink);
}
/* ============================================
DRAG & DROP STYLING
============================================ */
const dragDropStyles = document.createElement('style');
dragDropStyles.textContent = `
.social-link-item {
transition: opacity 0.2s ease, background-color 0.2s ease;
}
.social-link-item[draggable="true"] {
cursor: grab;
}
.social-link-item[draggable="true"]:active {
cursor: grabbing;
}
.drag-handle {
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
border-radius: 4px;
transition: all 0.2s ease;
}
.drag-handle:hover {
background-color: #f0f0f0;
color: #0d6efd;
}
.social-link-item.dragging {
opacity: 0.5;
background-color: #f8f9fa;
}
.social-link-item.drag-over {
border-top: 3px solid #0d6efd;
padding-top: 10px;
}
`;
document.head.appendChild(dragDropStyles);
document.addEventListener('click', function (e) {
if (e.target.closest('.remove-social-link')) {
e.preventDefault();
const btn = e.target.closest('.remove-social-link');
const index = btn.dataset.index;
const platformInput = document.querySelector(`.social-platform[data-index="${index}"]`);
const platform = platformInput.value;
if (confirm(`Delete ${platform} social link?`)) {
deleteSocialLink(platform, btn.closest('.card'));
}
}
if (e.target.closest('.edit-social-link')) {
e.preventDefault();
const btn = e.target.closest('.edit-social-link');
const index = btn.dataset.index;
const platformInput = document.querySelector(`.social-platform[data-index="${index}"]`);
const urlInput = document.querySelector(`.social-url[data-index="${index}"]`);
const iconInput = document.querySelector(`.social-icon[data-index="${index}"]`);
const platform = platformInput.value;
const url = urlInput.value;
const icon = iconInput.value;
showEditSocialLinkModal(platform, url, icon);
}
if (e.target.closest('#addSocialLink')) {
e.preventDefault();
showAddSocialLinkModal();
}
});
function deleteSocialLink(platform, cardElement) {
// Convert platform to lowercase
platform = platform.toLowerCase().trim();
console.log("Deleting social link:", platform);
fetch(`/api/social-links/${platform}`, {
method: 'DELETE'
})
.then(response => {
console.log("Delete response status:", response.status);
return response.json();
})
.then(result => {
console.log("Delete API response:", result);
if (result.success) {
cardElement.remove();
updateHiddenInputs();
showNotification('Xóa liên kết mạng xã hội thành công', 'success');
} else {
showNotification(result.message || 'Không thể xóa liên kết mạng xã hội', 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('Lỗi xóa liên kết mạng xã hội: ' + error.message, 'error');
});
}
function showAddSocialLinkModal() {
const modal = document.createElement('div');
modal.className = 'modal fade';
modal.id = 'addSocialLinkModal';
modal.setAttribute('tabindex', '-1');
modal.innerHTML = `
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add New Social Link</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label fw-medium">Platform <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="newSocialPlatform" placeholder="e.g., linkedin, twitter, instagram, youtube, facebook" />
</div>
<div class="mb-3">
<label class="form-label fw-medium">URL <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="newSocialUrl" placeholder="https://..." />
</div>
<div class="mb-3">
<label class="form-label fw-medium">Icon Class</label>
<input type="text" class="form-control" id="newSocialIcon" placeholder="e.g., fa-brands fa-linkedin" />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="saveSocialLink">Save</button>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
const bsModal = new bootstrap.Modal(modal);
document.getElementById('saveSocialLink').addEventListener('click', function() {
const platform = document.getElementById('newSocialPlatform').value.trim();
const url = document.getElementById('newSocialUrl').value.trim();
const icon = document.getElementById('newSocialIcon').value.trim();
if (!platform) {
alert('Vui lòng nhập tên nền tảng');
return;
}
if (!url) {
alert('Vui lòng nhập URL');
return;
}
// Check if platform already exists
const existingPlatforms = Array.from(document.querySelectorAll('.social-platform')).map(el => el.value);
if (existingPlatforms.includes(platform)) {
alert(`${platform} đã tồn tại`);
return;
}
addSocialLinkViaAPI(platform, url, icon || `fa-brands fa-${platform}`, bsModal, modal);
});
bsModal.show();
// Focus on platform input
setTimeout(() => {
document.getElementById('newSocialPlatform').focus();
}, 500);
}
function showEditSocialLinkModal(platform, url, icon) {
const modal = document.createElement('div');
modal.className = 'modal fade';
modal.id = 'editSocialLinkModal';
modal.setAttribute('tabindex', '-1');
const platformDisplay = platform ? platform.charAt(0).toUpperCase() + platform.slice(1) : 'Social';
modal.innerHTML = `
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit ${platformDisplay} Link</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label fw-medium">Platform <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="editSocialPlatform" value="${platform}" placeholder="e.g., linkedin, twitter, instagram" />
</div>
<div class="mb-3">
<label class="form-label fw-medium">URL <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="editSocialUrl" value="${url}" placeholder="https://www.example.com" />
</div>
<div class="mb-3">
<label class="form-label fw-medium">Icon Class</label>
<input type="text" class="form-control" id="editSocialIcon" value="${icon}" />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="updateSocialLink">Update</button>
</div>
</div>
</div>
`;
document.body.appendChild(modal);
const bsModal = new bootstrap.Modal(modal);
document.getElementById('updateSocialLink').addEventListener('click', function() {
const newPlatform = document.getElementById('editSocialPlatform').value.trim();
const newUrl = document.getElementById('editSocialUrl').value.trim();
const newIcon = document.getElementById('editSocialIcon').value.trim();
if (!newPlatform) {
alert('Vui lòng nhập tên nền tảng');
return;
}
if (!newUrl) {
alert('Vui lòng nhập URL');
return;
}
updateSocialLinkViaAPIModal(platform, newPlatform, newUrl, newIcon, bsModal, modal);
});
bsModal.show();
// Focus on platform input
setTimeout(() => {
document.getElementById('editSocialPlatform').focus();
}, 500);
}
function addSocialLinkViaAPI(platform, url, icon, modal, modalElement) {
console.log("Adding social link:", { platform, url, icon });
fetch('/api/social-links', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
platform,
url,
icon
})
})
.then(response => {
console.log("Response status:", response.status);
return response.json();
})
.then(result => {
console.log("API response:", result);
if (result.success) {
modal.hide();
modalElement.remove();
addSocialLinkRow(platform, url, icon, Date.now());
updateHiddenInputs();
showNotification('Thêm liên kết mạng xã hội thành công', 'success');
} else {
showNotification(result.message || 'Không thể thêm liên kết mạng xã hội', 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('Lỗi thêm liên kết mạng xã hội: ' + error.message, 'error');
});
}
function updateSocialLinkViaAPIModal(oldPlatform, newPlatform, url, icon, modal, modalElement) {
// Convert to lowercase
oldPlatform = oldPlatform.toLowerCase().trim();
newPlatform = newPlatform.toLowerCase().trim();
url = url.trim();
icon = icon ? icon.trim() : null;
console.log("Updating social link:", { oldPlatform, newPlatform, url, icon });
// If platform changed, need to delete old and create new
if (oldPlatform !== newPlatform) {
console.log("Platform changed, deleting old and creating new");
// Check if new platform already exists
const existingPlatforms = Array.from(document.querySelectorAll('.social-platform')).map(el => el.value.toLowerCase());
if (existingPlatforms.includes(newPlatform)) {
showNotification(`Nền tảng ${newPlatform} đã tồn tại`, 'error');
return;
}
// First delete old platform
fetch(`/api/social-links/${oldPlatform}`, {
method: 'DELETE'
})
.then(response => response.json())
.then(result => {
if (result.success) {
console.log("Old platform deleted, creating new");
// Then create new platform
return fetch('/api/social-links', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
platform: newPlatform,
url,
icon
})
});
} else {
throw new Error(result.message);
}
})
.then(response => response.json())
.then(result => {
console.log("Update API response:", result);
if (result.success) {
modal.hide();
modalElement.remove();
// Update the input fields
const urlInputs = document.querySelectorAll(`.social-url`);
const platformInputs = document.querySelectorAll(`.social-platform`);
for (let i = 0; i < platformInputs.length; i++) {
if (platformInputs[i].value.toLowerCase() === oldPlatform) {
platformInputs[i].value = newPlatform;
urlInputs[i].value = url;
const iconInput = document.querySelector(`.social-icon[data-index="${platformInputs[i].dataset.index}"]`);
if (iconInput) {
iconInput.value = icon;
}
break;
}
}
updateHiddenInputs();
showNotification('Cập nhật liên kết mạng xã hội thành công', 'success');
} else {
showNotification(result.message || 'Không thể cập nhật liên kết mạng xã hội', 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('Lỗi cập nhật liên kết mạng xã hội: ' + error.message, 'error');
});
} else {
// Platform not changed, just update URL and icon
console.log("Platform not changed, updating URL and icon only");
fetch(`/api/social-links/${oldPlatform}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
url,
icon
})
})
.then(response => {
console.log("Update response status:", response.status);
return response.json();
})
.then(result => {
console.log("Update API response:", result);
if (result.success) {
modal.hide();
modalElement.remove();
// Update the input fields
const urlInputs = document.querySelectorAll(`.social-url`);
const platformInputs = document.querySelectorAll(`.social-platform`);
for (let i = 0; i < platformInputs.length; i++) {
if (platformInputs[i].value.toLowerCase() === oldPlatform) {
urlInputs[i].value = url;
const iconInput = document.querySelector(`.social-icon[data-index="${platformInputs[i].dataset.index}"]`);
if (iconInput) {
iconInput.value = icon;
}
break;
}
}
updateHiddenInputs();
showNotification('Cập nhật liên kết mạng xã hội thành công', 'success');
} else {
showNotification(result.message || 'Không thể cập nhật liên kết mạng xã hội', 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('Lỗi cập nhật liên kết mạng xã hội: ' + error.message, 'error');
});
}
}
/**
* Show toast notification at top of page
* Auto-hides after 3 seconds
*/
function showNotification(message, type = 'info') {
// Create toast container if it doesn't exist
let toastContainer = document.getElementById('toastContainer');
if (!toastContainer) {
toastContainer = document.createElement('div');
toastContainer.id = 'toastContainer';
toastContainer.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 10px;
pointer-events: none;
`;
document.body.appendChild(toastContainer);
}
// Create toast element
const toast = document.createElement('div');
const bgColor = type === 'success' ? '#28a745' : type === 'error' ? '#dc3545' : '#17a2b8';
const icon = type === 'success' ? '✓' : type === 'error' ? '✕' : '';
toast.style.cssText = `
background-color: ${bgColor};
color: white;
padding: 12px 16px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
animation: slideIn 0.3s ease-out;
pointer-events: auto;
cursor: pointer;
`;
toast.innerHTML = `
<span style="font-weight: bold; font-size: 16px;">${icon}</span>
<span>${message}</span>
`;
toastContainer.appendChild(toast);
// Auto-hide after 3 seconds
setTimeout(() => {
toast.style.animation = 'slideOut 0.3s ease-out';
setTimeout(() => toast.remove(), 300);
}, 3000);
// Click to dismiss
toast.addEventListener('click', () => {
toast.style.animation = 'slideOut 0.3s ease-out';
setTimeout(() => toast.remove(), 300);
});
}
// Add CSS animations for toast
const toastStyles = document.createElement('style');
toastStyles.textContent = `
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(400px);
opacity: 0;
}
}
`;
document.head.appendChild(toastStyles);
function loadMenuTree() {
const container = document.getElementById('menuTreeContainer');
container.innerHTML = '<div class="alert alert-info"><i class="fas fa-info-circle me-2"></i>Menu structure management coming soon.</div>';
}
function openImageUploader(targetInput, imageType) {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.style.display = 'none';
document.body.appendChild(fileInput);
fileInput.onchange = async function (e) {
const file = e.target.files[0];
if (!file) return;
if (!file.type.startsWith('image/')) {
alert('Please select a valid image file');
document.body.removeChild(fileInput);
return;
}
try {
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...';
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(`HTTP ${response.status}: Upload failed`);
}
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Upload failed');
}
const input = document.getElementById(targetInput);
if (!input) {
throw new Error(`Input field #${targetInput} not found`);
}
input.value = result.path;
updateImagePreview(targetInput, result.url);
uploadBtn.disabled = false;
uploadBtn.innerHTML = originalBtnHtml;
} catch (error) {
console.error('Upload error:', error);
alert('Upload failed: ' + error.message);
const uploadBtn = document.querySelector(`[data-target-input="${targetInput}"]`);
uploadBtn.disabled = false;
uploadBtn.innerHTML = originalBtnHtml;
} finally {
if (document.body.contains(fileInput)) {
document.body.removeChild(fileInput);
}
}
};
fileInput.click();
}
function updateImagePreview(inputId, imageUrl) {
const previewContainer = document.getElementById('logoPreviewContainer');
if (!previewContainer) {
return;
}
let img = previewContainer.querySelector('img');
if (img) {
img.src = imageUrl;
} else {
img = document.createElement('img');
img.src = imageUrl;
img.className = 'img-thumbnail';
img.style.maxHeight = '100px';
img.style.maxWidth = '300px';
img.style.objectFit = 'contain';
img.style.backgroundColor = '#b8b76a';
img.alt = 'Logo preview';
previewContainer.appendChild(img);
}
}
// ============================================
// DRAG & DROP REORDERING FUNCTIONALITY
// ============================================
let draggedElement = null;
// Handle drag start - user begins dragging a social link item
document.addEventListener('dragstart', function (e) {
if (e.target.closest('.social-link-item')) {
draggedElement = e.target.closest('.social-link-item');
draggedElement.style.opacity = '0.5';
draggedElement.style.cursor = 'grabbing';
e.dataTransfer.effectAllowed = 'move';
}
});
// Handle drag over - provide visual feedback as item is dragged over other items
document.addEventListener('dragover', function (e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
const socialItem = e.target.closest('.social-link-item');
if (socialItem && socialItem !== draggedElement) {
// Add visual feedback to show drop zone
socialItem.style.borderTop = '3px solid #0d6efd';
socialItem.style.paddingTop = '10px';
}
});
// Handle drag leave - remove visual feedback when leaving an item
document.addEventListener('dragleave', function (e) {
const socialItem = e.target.closest('.social-link-item');
if (socialItem) {
socialItem.style.borderTop = '';
socialItem.style.paddingTop = '';
}
});
// Handle drop - reorder items and persist to backend
document.addEventListener('drop', function (e) {
e.preventDefault();
const socialItem = e.target.closest('.social-link-item');
if (socialItem && socialItem !== draggedElement) {
// Reorder DOM elements
const container = document.getElementById('socialLinksContainer');
const allItems = Array.from(container.querySelectorAll('.social-link-item'));
const draggedIndex = allItems.indexOf(draggedElement);
const targetIndex = allItems.indexOf(socialItem);
if (draggedIndex < targetIndex) {
socialItem.parentNode.insertBefore(draggedElement, socialItem.nextSibling);
} else {
socialItem.parentNode.insertBefore(draggedElement, socialItem);
}
// Clear visual feedback
socialItem.style.borderTop = '';
socialItem.style.paddingTop = '';
// Persist the new order to backend
persistSocialLinksOrder();
}
});
// Handle drag end - cleanup and reset styles
document.addEventListener('dragend', function (e) {
if (draggedElement) {
draggedElement.style.opacity = '1';
draggedElement.style.cursor = 'grab';
draggedElement = null;
}
// Clear all visual feedback from all items
document.querySelectorAll('.social-link-item').forEach(item => {
item.style.borderTop = '';
item.style.paddingTop = '';
});
});
/**
* Persist the reordered social links to the backend
* Collects current order from DOM and sends to API
* Updates the order field for each social link based on new position
*/
function persistSocialLinksOrder() {
const container = document.getElementById('socialLinksContainer');
const items = container.querySelectorAll('.social-link-item');
// Build order array with platform and new order position
const socialLinks = Array.from(items).map((item, index) => {
const platform = item.dataset.platform;
const urlInput = item.querySelector('.social-url');
const iconInput = item.querySelector('.social-icon');
return {
platform: platform,
url: urlInput.value,
icon: iconInput.value,
order: index + 1 // Order starts from 1 (top to bottom)
};
});
console.log('Persisting social links order:', socialLinks);
// Send to backend via bulk-update endpoint
fetch('/api/social-links/bulk-update', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ socialLinks })
})
.then(response => response.json())
.then(result => {
if (result.success) {
console.log('Social links order updated successfully');
showNotification('Cập nhật thứ tự thành công', 'success');
updateHiddenInputs();
} else {
console.error('Failed to update order:', result.message);
showNotification('Không thể cập nhật thứ tự: ' + result.message, 'error');
}
})
.catch(error => {
console.error('Error persisting social links order:', error);
showNotification('Lỗi cập nhật thứ tự: ' + error.message, 'error');
});
}
});
</script>