×
diff --git a/frontend/js/main_map.js b/frontend/js/main_map.js
index a335e19..b73151f 100644
--- a/frontend/js/main_map.js
+++ b/frontend/js/main_map.js
@@ -711,7 +711,11 @@ function openCreateTourModal(lat, lng) {
* Đóng Modal tạo Tour
*/
function closeTourModal() {
- document.getElementById('create-tour-modal').style.display = 'none';
+ const modal = document.getElementById('create-tour-modal');
+ if (modal) {
+ modal.style.display = 'none';
+ modal.removeAttribute('data-original-display');
+ }
if (tempMarker) {
map.removeLayer(tempMarker);
tempMarker = null;
@@ -733,9 +737,14 @@ function openEditTourModal(tour) {
const token = localStorage.getItem('jwt');
if (!token) return;
- dashboardReturnTab = 'my-scenes';
- returnToDashboardAfterEdit = true;
- closeDashboard();
+ const isDashboardOpen = document.getElementById('dashboard-overlay').style.display === 'flex';
+ if (isDashboardOpen) {
+ dashboardReturnTab = 'my-scenes';
+ returnToDashboardAfterEdit = true;
+ closeDashboard();
+ } else {
+ returnToDashboardAfterEdit = false;
+ }
const tourIdInput = document.getElementById('tour-id');
if (tourIdInput) tourIdInput.value = tour._id;
@@ -833,8 +842,16 @@ function openCreateSceneModal(lat, lng, tourId = null) {
* Closes the Create Scene Modal and removes temporary marker
*/
function closeModal() {
- document.getElementById('create-tour-modal').style.display = 'none'; // Đảm bảo đóng cả modal Tour
- document.getElementById('create-scene-modal').style.display = 'none';
+ const tourModal = document.getElementById('create-tour-modal');
+ if (tourModal) {
+ tourModal.style.display = 'none';
+ tourModal.removeAttribute('data-original-display');
+ }
+ const sceneModal = document.getElementById('create-scene-modal');
+ if (sceneModal) {
+ sceneModal.style.display = 'none';
+ sceneModal.removeAttribute('data-original-display');
+ }
if (tempMarker) {
map.removeLayer(tempMarker);
tempMarker = null;
@@ -1144,12 +1161,12 @@ async function handleEditDeleteScene(scene) {
// Cập nhật nhãn và sự kiện cho nút Xóa
deleteBtn.innerHTML = tour ? '🗑️ Xóa vĩnh viễn Tour' : '🗑️ Xóa vĩnh viễn';
- deleteBtn.onclick = () => {
+ deleteBtn.onclick = async () => {
returnToDashboardAfterEdit = false; // Đảm bảo không mở dashboard nếu xóa từ map
closeActionModal();
if (tour) {
// Tái sử dụng logic xóa tour từ dashboard
- if (confirm(`Bạn có chắc muốn xóa Tour "${tour.name}" và toàn bộ cảnh bên trong?`)) {
+ if (await window.showConfirmModal(`Bạn có chắc muốn xóa Tour "${tour.name}" và toàn bộ cảnh bên trong?`)) {
confirmDeleteTourFromMap(tour._id);
}
} else {
@@ -1187,10 +1204,15 @@ async function confirmDeleteTourFromMap(tourId) {
*/
window.deleteScene = async function(sceneId, sceneData = null) { // Thêm sceneData để tránh fetch lại
sceneIdToDelete = sceneId;
- // Tạm thời đóng dashboard và lưu trạng thái để mở lại sau
- dashboardReturnTab = 'my-scenes';
- returnToDashboardAfterEdit = true;
- closeDashboard();
+ // Tạm thời đóng dashboard nếu nó đang mở
+ const isDashboardOpen = document.getElementById('dashboard-overlay').style.display === 'flex';
+ if (isDashboardOpen) {
+ dashboardReturnTab = 'my-scenes';
+ returnToDashboardAfterEdit = true;
+ closeDashboard();
+ } else {
+ returnToDashboardAfterEdit = false;
+ }
const confirmModal = document.getElementById('delete-scene-confirm-modal');
const confirmMessageElem = document.getElementById('delete-scene-confirm-message'); // Giả định có element này trong HTML
@@ -1546,7 +1568,7 @@ window.handleHotspotCreation = async function(pitch, yaw, existingHotspot = null
return;
}
await saveHotspotToDB(pitch, yaw, formData.get('title'), formData.get('description'), finalTargetId, existingHotspot?._id);
- modal.style.display = 'none';
+ closeHotspotModal();
};
};
@@ -1554,7 +1576,11 @@ window.handleHotspotCreation = async function(pitch, yaw, existingHotspot = null
* Đóng Modal biên tập Hotspot
*/
function closeHotspotModal() {
- document.getElementById('hotspot-modal').style.display = 'none';
+ const modal = document.getElementById('hotspot-modal');
+ if (modal) {
+ modal.style.display = 'none';
+ modal.removeAttribute('data-original-display');
+ }
}
/**
@@ -1680,7 +1706,7 @@ async function saveHotspotToDB(pitch, yaw, title, description, targetSceneId, ho
* Gọi lệnh: systemReset() từ trình duyệt
*/
window.systemReset = async function() {
- if (!confirm("CẢNH BÁO: Thao tác này sẽ xóa sạch TOÀN BỘ scene và ảnh trên server. Bạn có chắc chắn?")) return;
+ if (!await window.showConfirmModal("CẢNH BÁO: Thao tác này sẽ xóa sạch TOÀN BỘ scene và ảnh trên server. Bạn có chắc chắn?")) return;
const token = localStorage.getItem('jwt');
try {
@@ -1720,7 +1746,7 @@ window.openHotspotMenu = function(hotspot) {
// Hành động Xóa: Xác nhận và gọi API xóa
deleteBtn.onclick = async () => {
- if (confirm('Bạn có chắc chắn muốn xóa điểm điều hướng này?')) {
+ if (await window.showConfirmModal('Bạn có chắc chắn muốn xóa điểm điều hướng này?')) {
closeHotspotActionModal();
await deleteHotspot(hotspot._id);
}
@@ -1831,6 +1857,10 @@ async function loadMyTours() {
card.style.borderLeft = `5px solid ${tour.privacy === 'public' ? '#28a745' : '#ffc107'}`;
+ const currentUserId = localStorage.getItem('userId');
+ const userRole = localStorage.getItem('role');
+ const isTourOwner = (tour.createdBy?._id === currentUserId || tour.createdBy === currentUserId || userRole === 'admin' || userRole === 'Chủ sở hữu');
+
card.innerHTML = `
@@ -1844,8 +1874,10 @@ async function loadMyTours() {
-
-
+ ${isTourOwner ? `
+
+
+ ` : ''}
@@ -1853,26 +1885,32 @@ async function loadMyTours() {
listContainer.appendChild(card);
// Nút Sửa Tour
- document.getElementById(`edit-tour-${tour._id}`).onclick = () => {
- openEditTourModal(tour);
- };
+ const editBtn = document.getElementById(`edit-tour-${tour._id}`);
+ if (editBtn) {
+ editBtn.onclick = () => {
+ openEditTourModal(tour);
+ };
+ }
// Nút Xóa Tour: Gọi API xóa Tour (bao gồm xóa cascade các scene bên trong)
- document.getElementById(`delete-tour-${tour._id}`).onclick = async () => {
- if (confirm(`Bạn có chắc muốn xóa Tour "${tour.name}" và toàn bộ ${tour.scenes?.length || 0} cảnh bên trong?`)) {
- try {
- const res = await fetch(`${API_BASE_URL}/tours/${tour._id}`, {
- method: 'DELETE',
- headers: { 'Authorization': `Bearer ${token}` }
- });
- if (res.ok) {
- showNotification("Đã xóa Tour thành công", "success");
- loadMyTours();
- loadScenes();
- }
- } catch (e) { showNotification("Lỗi xóa tour", "error"); }
- }
- };
+ const deleteBtn = document.getElementById(`delete-tour-${tour._id}`);
+ if (deleteBtn) {
+ deleteBtn.onclick = async () => {
+ if (await window.showConfirmModal(`Bạn có chắc muốn xóa Tour "${tour.name}" và toàn bộ ${tour.scenes?.length || 0} cảnh bên trong?`)) {
+ try {
+ const res = await fetch(`${API_BASE_URL}/tours/${tour._id}`, {
+ method: 'DELETE',
+ headers: { 'Authorization': `Bearer ${token}` }
+ });
+ if (res.ok) {
+ showNotification("Đã xóa Tour thành công", "success");
+ loadMyTours();
+ loadScenes();
+ }
+ } catch (e) { showNotification("Lỗi xóa tour", "error"); }
+ }
+ };
+ }
// Nút Xem Tour: Bay tới vị trí và mở cảnh khởi đầu
document.getElementById(`view-tour-${tour._id}`).onclick = () => {
@@ -1990,7 +2028,7 @@ window.updateUserByAdmin = async function(userId) {
};
window.deleteUserByAdmin = async function(userId) {
- if (!confirm('Xóa vĩnh viễn người dùng này?')) return;
+ if (!await window.showConfirmModal('Xóa vĩnh viễn người dùng này?')) return;
const token = localStorage.getItem('jwt');
try {
const res = await fetch(`${API_BASE_URL}/admin/users/${userId}`, {
@@ -2170,12 +2208,26 @@ async function loadMyAssets() {
*/
window.deleteAsset = function(assetId) {
assetIdToDelete = assetId;
+ const isDashboardOpen = document.getElementById('dashboard-overlay').style.display === 'flex';
+ if (isDashboardOpen) {
+ dashboardReturnTab = 'media-library';
+ returnToDashboardAfterEdit = true;
+ closeDashboard();
+ } else {
+ returnToDashboardAfterEdit = false;
+ }
document.getElementById('delete-asset-confirm-modal').style.display = 'flex';
};
window.closeDeleteAssetModal = function() {
document.getElementById('delete-asset-confirm-modal').style.display = 'none';
assetIdToDelete = null;
+ if (returnToDashboardAfterEdit) {
+ const targetTab = dashboardReturnTab;
+ returnToDashboardAfterEdit = false;
+ openDashboard();
+ openDashboardTab(targetTab);
+ }
};
window.confirmDeleteAsset = async function() {
@@ -2207,6 +2259,40 @@ window.confirmDeleteAsset = async function() {
}
};
+window.hiddenModalsStack = window.hiddenModalsStack || [];
+
+window.tempHideActiveModals = function(exceptModalIds = []) {
+ const modals = document.querySelectorAll('.modal, .modal-overlay');
+ const hiddenInThisTurn = [];
+ modals.forEach(modal => {
+ if (exceptModalIds.includes(modal.id)) return;
+
+ const style = window.getComputedStyle(modal);
+ if (style.display !== 'none' && modal.style.display !== 'none') {
+ const originalDisplay = modal.style.display || style.display;
+ modal.setAttribute('data-original-display', originalDisplay);
+ modal.style.display = 'none';
+ hiddenInThisTurn.push(modal);
+ }
+ });
+ if (hiddenInThisTurn.length > 0) {
+ window.hiddenModalsStack.push(hiddenInThisTurn);
+ }
+};
+
+window.restorePreviousModals = function() {
+ if (window.hiddenModalsStack && window.hiddenModalsStack.length > 0) {
+ const lastHiddenGroup = window.hiddenModalsStack.pop();
+ lastHiddenGroup.forEach(modal => {
+ if (modal.hasAttribute('data-original-display')) {
+ const originalDisplay = modal.getAttribute('data-original-display') || 'flex';
+ modal.style.display = originalDisplay;
+ modal.removeAttribute('data-original-display');
+ }
+ });
+ }
+};
+
/**
* Hiển thị Modal thông báo thành công
*/
@@ -2215,6 +2301,7 @@ window.showSuccessModal = function(message, icon = '✓') {
const msgElem = document.getElementById('success-modal-message');
const iconElem = document.getElementById('success-modal-icon');
if (modal && msgElem && iconElem) {
+ window.tempHideActiveModals(['success-modal']);
msgElem.innerText = message;
iconElem.innerText = icon;
modal.style.display = 'flex';
@@ -2231,7 +2318,10 @@ window.closeSuccessModal = function(e) {
if (!modal) return;
// Nếu nhấn từ code (không có e) hoặc click trúng overlay thì đóng
if (e && e.target !== modal) return;
- modal.style.display = 'none';
+ if (modal.style.display !== 'none') {
+ modal.style.display = 'none';
+ window.restorePreviousModals();
+ }
};
/**
@@ -2243,6 +2333,7 @@ window.showErrorModal = function(message, title = "Thông báo", icon = '⚠️'
const titleElem = document.getElementById('error-modal-title');
const iconElem = document.getElementById('error-modal-icon');
if (modal && msgElem && iconElem) {
+ window.tempHideActiveModals(['error-modal']);
msgElem.innerText = message;
iconElem.innerText = icon;
if (titleElem) titleElem.innerText = title;
@@ -2258,7 +2349,10 @@ window.closeErrorModal = function(e) {
if (!modal) return;
// Nếu nhấn từ code (không có e) hoặc click trúng overlay thì đóng
if (e && e.target !== modal) return;
- modal.style.display = 'none';
+ if (modal.style.display !== 'none') {
+ modal.style.display = 'none';
+ window.restorePreviousModals();
+ }
};
/**
@@ -2274,6 +2368,51 @@ window.showNotification = function(message, type = 'success') {
}
};
+// Đè hàm alert mặc định của trình duyệt để gọi modal tương ứng
+window.alert = function(message) {
+ window.showErrorModal(message, 'Thông báo', '⚠️');
+};
+
+// Hàm hiển thị confirm dạng modal bất đồng bộ
+window.showConfirmModal = function(message) {
+ return new Promise((resolve) => {
+ // Tạm ẩn các modal đang mở
+ window.tempHideActiveModals(['generic-confirm-modal']);
+
+ let modal = document.getElementById('generic-confirm-modal');
+ if (!modal) {
+ modal = document.createElement('div');
+ modal.id = 'generic-confirm-modal';
+ modal.className = 'modal-overlay';
+ modal.style.zIndex = '9999';
+ modal.innerHTML = `
+
+
⚠️
+
Xác nhận
+
+
+
+
+
+
+ `;
+ document.body.appendChild(modal);
+ }
+
+ document.getElementById('generic-confirm-message').innerText = message;
+ modal.style.display = 'flex';
+
+ const handleResolve = (value) => {
+ modal.style.display = 'none';
+ window.restorePreviousModals();
+ resolve(value);
+ };
+
+ document.getElementById('generic-confirm-ok-btn').onclick = () => handleResolve(true);
+ document.getElementById('generic-confirm-cancel-btn').onclick = () => handleResolve(false);
+ });
+};
+
window.openEditFromMedia = function(scene, isChild = false) {
if (!scene || !scene._id) {
showNotification("Không thể chỉnh sửa: Ảnh này không được gắn với một Scene hợp lệ.", 'error');
@@ -2347,7 +2486,11 @@ window.openEditMetadataModal = function(scene, isChildArg = null) {
};
function closeEditMetadataModal() {
- document.getElementById('edit-scene-metadata-modal').style.display = 'none';
+ const modal = document.getElementById('edit-scene-metadata-modal');
+ if (modal) {
+ modal.style.display = 'none';
+ modal.removeAttribute('data-original-display');
+ }
if (returnToDashboardAfterEdit) {
const targetTab = dashboardReturnTab;
returnToDashboardAfterEdit = false;
@@ -2586,7 +2729,7 @@ async function handleBackup() {
*/
async function handleRestore(input) {
if (!input.files || !input.files[0]) return;
- if (!confirm("CẢNH BÁO: Khôi phục dữ liệu sẽ xóa sạch dữ liệu hiện tại và thay thế bằng dữ liệu từ bản sao lưu. Bạn có chắc chắn?")) {
+ if (!await window.showConfirmModal("CẢNH BÁO: Khôi phục dữ liệu sẽ xóa sạch dữ liệu hiện tại và thay thế bằng dữ liệu từ bản sao lưu. Bạn có chắc chắn?")) {
input.value = ''; return;
}
const token = localStorage.getItem('jwt');
@@ -2643,7 +2786,7 @@ async function updateSystemSettings(e) {
*/
window.recalculateAllTourCenters = async function() {
const token = localStorage.getItem('jwt');
- if (!confirm("Bạn có chắc chắn muốn tính toán lại tọa độ trung tâm cho TOÀN BỘ Tour trong hệ thống? Việc này có thể mất một chút thời gian nếu dữ liệu lớn.")) return;
+ if (!await window.showConfirmModal("Bạn có chắc chắn muốn tính toán lại tọa độ trung tâm cho TOÀN BỘ Tour trong hệ thống? Việc này có thể mất một chút thời gian nếu dữ liệu lớn.")) return;
try {
showNotification("Đang xử lý tính toán lại...", "success");
diff --git a/uploads/processed_1781103291447_9b5bb55d.jpg.jpg b/uploads/processed_1781139560171_f8226407.jpg.jpg
similarity index 99%
rename from uploads/processed_1781103291447_9b5bb55d.jpg.jpg
rename to uploads/processed_1781139560171_f8226407.jpg.jpg
index bd7b2a1..68318b3 100644
Binary files a/uploads/processed_1781103291447_9b5bb55d.jpg.jpg and b/uploads/processed_1781139560171_f8226407.jpg.jpg differ
diff --git a/uploads/processed_1781103856812_7d988385.jpg.jpg b/uploads/processed_1781139589281_fb8070bc.jpg.jpg
similarity index 99%
rename from uploads/processed_1781103856812_7d988385.jpg.jpg
rename to uploads/processed_1781139589281_fb8070bc.jpg.jpg
index e43981f..7e0151e 100644
Binary files a/uploads/processed_1781103856812_7d988385.jpg.jpg and b/uploads/processed_1781139589281_fb8070bc.jpg.jpg differ
diff --git a/uploads/processed_1781103810966_7acb1aea.jpg.jpg b/uploads/processed_1781139839363_996f1ac9.jpg.jpg
similarity index 99%
rename from uploads/processed_1781103810966_7acb1aea.jpg.jpg
rename to uploads/processed_1781139839363_996f1ac9.jpg.jpg
index 29e6c31..1aa67f3 100644
Binary files a/uploads/processed_1781103810966_7acb1aea.jpg.jpg and b/uploads/processed_1781139839363_996f1ac9.jpg.jpg differ
diff --git a/uploads/processed_1781142968008_7b2ac4d4.jpg.jpg b/uploads/processed_1781142968008_7b2ac4d4.jpg.jpg
new file mode 100644
index 0000000..72a48d0
Binary files /dev/null and b/uploads/processed_1781142968008_7b2ac4d4.jpg.jpg differ