fix: enhance FAQ management and add delete confirmation modal

This commit is contained in:
nguyenvanbao
2026-02-04 11:48:04 +07:00
parent a1984189bd
commit 37123976c6
5 changed files with 383 additions and 72 deletions

View File

@@ -283,6 +283,43 @@
</div>
</div>
<!-- Custom Delete Confirmation Modal -->
<div id="customDeleteModal" class="custom-modal" style="display: none;">
<div class="custom-modal-backdrop"></div>
<div class="custom-modal-dialog">
<div class="custom-modal-content">
<div class="custom-modal-header">
<h5 class="custom-modal-title">
<i class="fas fa-exclamation-triangle text-warning me-2"></i>Confirm Delete
</h5>
<button type="button" class="custom-modal-close" onclick="closeDeleteModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="custom-modal-body">
<div class="text-center mb-3">
<div class="delete-icon">
<i class="fas fa-trash fa-2x text-danger"></i>
</div>
</div>
<h6 class="text-center mb-3">Delete Service</h6>
<p class="text-center text-muted mb-0">
Are you sure you want to delete "<strong id="deleteServiceName"></strong>"?
<br><small class="text-danger">This action cannot be undone.</small>
</p>
</div>
<div class="custom-modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeDeleteModal()">
<i class="fas fa-times me-2"></i>Cancel
</button>
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">
<i class="fas fa-trash me-2"></i>Delete Service
</button>
</div>
</div>
</div>
</div>
<!-- Scripts -->
<script>
let originalFormData = null;
@@ -534,11 +571,110 @@ function addService() {
}
function deleteService(index) {
if (confirm('Are you sure you want to delete this service? This action cannot be undone.')) {
servicesData.splice(index, 1);
updateServicesTable();
updateStatistics();
showSuccess('Service deleted successfully!');
const service = servicesData[index];
if (!service) return;
// Set service name in modal
document.getElementById('deleteServiceName').textContent = service.name || 'Unnamed Service';
// Show custom modal
showDeleteModal();
// Handle confirm delete
const confirmBtn = document.getElementById('confirmDeleteBtn');
const newConfirmBtn = confirmBtn.cloneNode(true);
confirmBtn.parentNode.replaceChild(newConfirmBtn, confirmBtn);
newConfirmBtn.addEventListener('click', async function() {
// Show loading state
this.disabled = true;
this.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Deleting...';
// Disable cancel button and close button during delete
const cancelBtn = document.querySelector('.custom-modal .btn-secondary');
const closeBtn = document.querySelector('.custom-modal-close');
cancelBtn.disabled = true;
closeBtn.disabled = true;
// Add loading overlay to modal
showModalLoading();
try {
// Simulate API call delay for better UX
await new Promise(resolve => setTimeout(resolve, 1000));
// Perform delete
servicesData.splice(index, 1);
updateServicesTable();
updateStatistics();
// Hide modal
closeDeleteModal();
// Show success message
showSuccess(`Service "${service.name}" deleted successfully!`);
} catch (error) {
console.error('Error deleting service:', error);
showError('Failed to delete service. Please try again.');
} finally {
// Reset button states
this.disabled = false;
this.innerHTML = '<i class="fas fa-trash me-2"></i>Delete Service';
cancelBtn.disabled = false;
closeBtn.disabled = false;
// Hide loading overlay
hideModalLoading();
}
});
}
function showModalLoading() {
const modal = document.getElementById('customDeleteModal');
const loadingOverlay = document.createElement('div');
loadingOverlay.className = 'modal-loading-overlay';
loadingOverlay.innerHTML = `
<div class="loading-spinner">
<i class="fas fa-spinner fa-spin fa-2x text-primary"></i>
<p class="mt-2 mb-0">Deleting service...</p>
</div>
`;
modal.appendChild(loadingOverlay);
}
function hideModalLoading() {
const loadingOverlay = document.querySelector('.modal-loading-overlay');
if (loadingOverlay) {
loadingOverlay.remove();
}
}
function showDeleteModal() {
const modal = document.getElementById('customDeleteModal');
modal.style.display = 'flex';
document.body.style.overflow = 'hidden';
// Add animation
setTimeout(() => {
modal.classList.add('show');
}, 10);
// Close modal when clicking backdrop (only if not loading)
const backdrop = modal.querySelector('.custom-modal-backdrop');
backdrop.onclick = function() {
if (!document.querySelector('.modal-loading-overlay')) {
closeDeleteModal();
}
};
// Close modal with ESC key (only if not loading)
document.addEventListener('keydown', handleEscKey);
}
function handleEscKey(event) {
if (event.key === 'Escape' && !document.querySelector('.modal-loading-overlay')) {
closeDeleteModal();
}
}
@@ -779,4 +915,188 @@ function showError(message) {
.btn-group .btn:last-child {
margin-right: 0;
}
/* Custom Delete Modal Styles */
.custom-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.custom-modal.show {
opacity: 1;
}
.custom-modal-backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(2px);
}
.custom-modal-dialog {
position: relative;
z-index: 10001;
max-width: 400px;
width: 90%;
transform: scale(0.8);
transition: transform 0.3s ease;
}
.custom-modal.show .custom-modal-dialog {
transform: scale(1);
}
.custom-modal-content {
background: white;
border-radius: 15px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.custom-modal-header {
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
padding: 20px 25px 15px;
border-bottom: 1px solid #dee2e6;
display: flex;
justify-content: between;
align-items: center;
}
.custom-modal-title {
color: var(--primary-dark);
font-weight: 600;
margin: 0;
flex: 1;
}
.custom-modal-close {
background: none;
border: none;
font-size: 18px;
color: #6c757d;
cursor: pointer;
padding: 5px;
border-radius: 50%;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.custom-modal-close:hover {
background: rgba(0, 0, 0, 0.1);
color: #495057;
}
.custom-modal-body {
padding: 25px;
text-align: center;
}
.delete-icon {
background: rgba(220, 53, 69, 0.1);
border-radius: 50%;
width: 80px;
height: 80px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-bottom: 15px;
}
.custom-modal-footer {
padding: 15px 25px 25px;
display: flex;
gap: 10px;
justify-content: center;
}
.custom-modal-footer .btn {
min-width: 120px;
}
.custom-modal .btn-danger {
background: linear-gradient(135deg, #dc3545, #c82333);
border: none;
transition: all 0.3s ease;
}
.custom-modal .btn-danger:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3);
}
.custom-modal .btn-secondary {
background: #6c757d;
border: none;
transition: all 0.3s ease;
}
.custom-modal .btn-secondary:hover {
background: #5a6268;
transform: translateY(-1px);
}
/* Modal Loading Overlay */
.modal-loading-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.95);
display: flex;
align-items: center;
justify-content: center;
z-index: 10002;
border-radius: 15px;
backdrop-filter: blur(2px);
}
.loading-spinner {
text-align: center;
color: var(--primary-color);
}
.loading-spinner p {
font-size: 14px;
color: #6c757d;
font-weight: 500;
}
.loading-spinner i {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Disable buttons during loading */
.custom-modal .btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none !important;
}
.custom-modal-close:disabled {
opacity: 0.6;
cursor: not-allowed;
pointer-events: none;
}
</style>