- Full-stack Node.js/Express application with PostgreSQL - Modern ES modules architecture - AdminJS admin panel with Sequelize ORM - Tourism routes, guides, articles, bookings management - Responsive Bootstrap 5 frontend - Docker containerization with docker-compose - Complete database schema with migrations - Authentication system for admin panel - Dynamic placeholder images for tour categories
288 lines
8.1 KiB
JavaScript
288 lines
8.1 KiB
JavaScript
/* Korea Tourism Agency Admin Panel Custom Scripts */
|
|
|
|
$(document).ready(function() {
|
|
// Initialize tooltips
|
|
$('[data-toggle="tooltip"]').tooltip();
|
|
|
|
// Initialize popovers
|
|
$('[data-toggle="popover"]').popover();
|
|
|
|
// Auto-hide alerts after 5 seconds
|
|
setTimeout(function() {
|
|
$('.alert').fadeOut('slow');
|
|
}, 5000);
|
|
|
|
// Confirm delete actions
|
|
$('.btn-delete').on('click', function(e) {
|
|
e.preventDefault();
|
|
const item = $(this).data('item') || 'item';
|
|
const url = $(this).attr('href') || $(this).data('url');
|
|
|
|
if (confirm(`Are you sure you want to delete this ${item}?`)) {
|
|
if ($(this).data('method') === 'DELETE') {
|
|
// AJAX delete
|
|
$.ajax({
|
|
url: url,
|
|
method: 'DELETE',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Error: ' + response.message);
|
|
}
|
|
},
|
|
error: function() {
|
|
alert('An error occurred while deleting.');
|
|
}
|
|
});
|
|
} else {
|
|
// Regular form submission or redirect
|
|
window.location.href = url;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Form validation enhancement
|
|
$('form').on('submit', function() {
|
|
const submitBtn = $(this).find('button[type="submit"]');
|
|
submitBtn.prop('disabled', true);
|
|
submitBtn.html('<i class="fas fa-spinner fa-spin"></i> Processing...');
|
|
});
|
|
|
|
// Image preview functionality
|
|
function readURL(input, target) {
|
|
if (input.files && input.files[0]) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
$(target).attr('src', e.target.result).show();
|
|
};
|
|
reader.readAsDataURL(input.files[0]);
|
|
}
|
|
}
|
|
|
|
$('input[type="file"]').on('change', function() {
|
|
const targetImg = $(this).closest('.form-group').find('.img-preview');
|
|
if (targetImg.length) {
|
|
readURL(this, targetImg);
|
|
}
|
|
});
|
|
|
|
// Auto-save draft functionality for forms
|
|
let autoSaveTimer;
|
|
$('textarea, input[type="text"]').on('input', function() {
|
|
clearTimeout(autoSaveTimer);
|
|
autoSaveTimer = setTimeout(function() {
|
|
// Auto-save logic here
|
|
console.log('Auto-saving draft...');
|
|
}, 2000);
|
|
});
|
|
|
|
// Enhanced DataTables configuration
|
|
if (typeof $.fn.dataTable !== 'undefined') {
|
|
$('.data-table').each(function() {
|
|
$(this).DataTable({
|
|
responsive: true,
|
|
lengthChange: false,
|
|
autoWidth: false,
|
|
pageLength: 25,
|
|
language: {
|
|
search: "Search:",
|
|
lengthMenu: "Show _MENU_ entries",
|
|
info: "Showing _START_ to _END_ of _TOTAL_ entries",
|
|
paginate: {
|
|
first: "First",
|
|
last: "Last",
|
|
next: "Next",
|
|
previous: "Previous"
|
|
}
|
|
},
|
|
dom: '<"row"<"col-sm-6"l><"col-sm-6"f>>' +
|
|
'<"row"<"col-sm-12"tr>>' +
|
|
'<"row"<"col-sm-5"i><"col-sm-7"p>>',
|
|
});
|
|
});
|
|
}
|
|
|
|
// Status toggle functionality
|
|
$('.status-toggle').on('change', function() {
|
|
const checkbox = $(this);
|
|
const id = checkbox.data('id');
|
|
const type = checkbox.data('type');
|
|
const field = checkbox.data('field');
|
|
const isChecked = checkbox.is(':checked');
|
|
|
|
$.ajax({
|
|
url: `/admin/${type}/${id}/toggle`,
|
|
method: 'POST',
|
|
data: {
|
|
field: field,
|
|
value: isChecked
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
showNotification('Status updated successfully!', 'success');
|
|
} else {
|
|
checkbox.prop('checked', !isChecked);
|
|
showNotification('Error updating status: ' + response.message, 'error');
|
|
}
|
|
},
|
|
error: function() {
|
|
checkbox.prop('checked', !isChecked);
|
|
showNotification('Error updating status', 'error');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Notification system
|
|
function showNotification(message, type = 'info') {
|
|
const alertClass = type === 'success' ? 'alert-success' :
|
|
type === 'error' ? 'alert-danger' :
|
|
type === 'warning' ? 'alert-warning' : 'alert-info';
|
|
|
|
const notification = `
|
|
<div class="alert ${alertClass} alert-dismissible fade show" role="alert" style="position: fixed; top: 20px; right: 20px; z-index: 1050; min-width: 300px;">
|
|
${message}
|
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
$('body').append(notification);
|
|
|
|
// Auto-hide after 3 seconds
|
|
setTimeout(function() {
|
|
$('.alert').last().fadeOut('slow', function() {
|
|
$(this).remove();
|
|
});
|
|
}, 3000);
|
|
}
|
|
|
|
// Make notification function globally available
|
|
window.showNotification = showNotification;
|
|
|
|
// Quick search functionality
|
|
$('#quick-search').on('input', function() {
|
|
const searchTerm = $(this).val().toLowerCase();
|
|
const searchableElements = $('.searchable');
|
|
|
|
if (searchTerm === '') {
|
|
searchableElements.show();
|
|
} else {
|
|
searchableElements.each(function() {
|
|
const text = $(this).text().toLowerCase();
|
|
if (text.includes(searchTerm)) {
|
|
$(this).show();
|
|
} else {
|
|
$(this).hide();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Bulk actions functionality
|
|
$('#select-all').on('change', function() {
|
|
$('.item-checkbox').prop('checked', $(this).is(':checked'));
|
|
updateBulkActionButtons();
|
|
});
|
|
|
|
$('.item-checkbox').on('change', function() {
|
|
updateBulkActionButtons();
|
|
|
|
// Update select-all checkbox state
|
|
const totalCheckboxes = $('.item-checkbox').length;
|
|
const checkedCheckboxes = $('.item-checkbox:checked').length;
|
|
|
|
if (checkedCheckboxes === 0) {
|
|
$('#select-all').prop('indeterminate', false).prop('checked', false);
|
|
} else if (checkedCheckboxes === totalCheckboxes) {
|
|
$('#select-all').prop('indeterminate', false).prop('checked', true);
|
|
} else {
|
|
$('#select-all').prop('indeterminate', true);
|
|
}
|
|
});
|
|
|
|
function updateBulkActionButtons() {
|
|
const checkedItems = $('.item-checkbox:checked').length;
|
|
if (checkedItems > 0) {
|
|
$('.bulk-actions').show();
|
|
$('.bulk-count').text(checkedItems);
|
|
} else {
|
|
$('.bulk-actions').hide();
|
|
}
|
|
}
|
|
|
|
// Image upload preview
|
|
$('.image-upload').on('change', function() {
|
|
const file = this.files[0];
|
|
const preview = $(this).siblings('.image-preview');
|
|
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
preview.html(`<img src="${e.target.result}" class="img-thumbnail" style="max-width: 200px; max-height: 200px;">`);
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
});
|
|
|
|
// Chart initialization (if Chart.js is available)
|
|
if (typeof Chart !== 'undefined' && $('#dashboard-chart').length) {
|
|
const ctx = document.getElementById('dashboard-chart').getContext('2d');
|
|
new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
|
|
datasets: [{
|
|
label: 'Bookings',
|
|
data: [12, 19, 3, 5, 2, 3],
|
|
borderColor: '#3b82f6',
|
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
tension: 0.4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
legend: {
|
|
position: 'top',
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Global utility functions
|
|
window.AdminUtils = {
|
|
// Format currency
|
|
formatCurrency: function(amount) {
|
|
return '₩' + new Intl.NumberFormat('ko-KR').format(amount);
|
|
},
|
|
|
|
// Format date
|
|
formatDate: function(date) {
|
|
return new Date(date).toLocaleDateString('ko-KR');
|
|
},
|
|
|
|
// Validate email
|
|
isValidEmail: function(email) {
|
|
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return re.test(email);
|
|
},
|
|
|
|
// Show loading spinner
|
|
showLoading: function(element) {
|
|
$(element).addClass('loading').append('<div class="spinner"></div>');
|
|
},
|
|
|
|
// Hide loading spinner
|
|
hideLoading: function(element) {
|
|
$(element).removeClass('loading').find('.spinner').remove();
|
|
}
|
|
}; |