fix: admin empty thùng rác
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { X, User, Trash2, Shield, ShieldAlert, Lock, Unlock, Loader2, Image as ImageIcon, Map, FileText, CheckCircle, Star, Settings } from 'lucide-react';
|
||||
import { useConfirm } from '../hooks/useConfirm';
|
||||
import { useNotification } from '../hooks/useNotification';
|
||||
|
||||
interface UserManagementModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -9,6 +10,7 @@ interface UserManagementModalProps {
|
||||
|
||||
export const UserManagementModal: React.FC<UserManagementModalProps> = ({ isOpen, onClose }) => {
|
||||
const confirm = useConfirm();
|
||||
const notify = useNotification();
|
||||
const [activeTab, setActiveTab] = useState<'users' | 'tours' | 'photos' | 'notes' | 'recommendations' | 'trash' | 'filters' | 'reports'>('users');
|
||||
const [users, setUsers] = useState<any[]>([]);
|
||||
const [photos, setPhotos] = useState<any[]>([]);
|
||||
@@ -116,7 +118,10 @@ export const UserManagementModal: React.FC<UserManagementModalProps> = ({ isOpen
|
||||
};
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
if (!confirm('Bạn có chắc chắn muốn xóa người dùng này?')) return;
|
||||
if (!await confirm({
|
||||
title: 'Xóa người dùng',
|
||||
message: 'Bạn có chắc chắn muốn xóa người dùng này?'
|
||||
})) return;
|
||||
try {
|
||||
const res = await fetch(`/api/v1/users/${id}`, {
|
||||
method: 'DELETE',
|
||||
@@ -469,9 +474,18 @@ export const UserManagementModal: React.FC<UserManagementModalProps> = ({ isOpen
|
||||
|
||||
const handleRestoreTrash = async () => {
|
||||
if (selectedTrashIds.length === 0) return;
|
||||
if (!confirm(`Bạn có chắc muốn khôi phục ${selectedTrashIds.length} mục đã chọn?`)) return;
|
||||
if (!await confirm({
|
||||
title: 'Khôi phục các mục',
|
||||
message: `Bạn có chắc muốn khôi phục ${selectedTrashIds.length} mục đã chọn?`
|
||||
})) return;
|
||||
|
||||
setTrashLoading(true);
|
||||
const idsToRestore = [...selectedTrashIds];
|
||||
// Convert plural to singular: 'photos' -> 'photo', 'tours' -> 'tour', 'notes' -> 'note'
|
||||
const itemType = trashSubTab === 'photos' ? 'photo' : trashSubTab === 'tours' ? 'tour' : 'note';
|
||||
|
||||
try {
|
||||
console.log(`[Trash] Starting restore for ${idsToRestore.length} ${itemType} items`);
|
||||
const res = await fetch('/api/v1/admin/trash/restore', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -479,31 +493,49 @@ export const UserManagementModal: React.FC<UserManagementModalProps> = ({ isOpen
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: trashSubTab,
|
||||
ids: selectedTrashIds
|
||||
type: itemType,
|
||||
ids: idsToRestore
|
||||
})
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
console.log('[Trash] Restore successful');
|
||||
setSelectedTrashIds([]);
|
||||
fetchTrashData();
|
||||
notify({ title: '✓ Thành công', message: `Đã khôi phục ${idsToRestore.length} mục.`, type: 'success' });
|
||||
setTimeout(() => {
|
||||
fetchTrashData();
|
||||
setTrashLoading(false);
|
||||
}, 500);
|
||||
} else {
|
||||
const errorData = await res.json().catch(() => ({}));
|
||||
const errorMsg = errorData?.message || `Lỗi HTTP ${res.status}`;
|
||||
console.error('[Trash] Restore failed:', errorMsg);
|
||||
notify({ title: '✗ Khôi phục thất bại', message: errorMsg, type: 'error' });
|
||||
setTrashLoading(false);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
} catch (e: any) {
|
||||
console.error('[Trash] Restore error:', e);
|
||||
notify({ title: '✗ Lỗi', message: e?.message || 'Lỗi khi khôi phục các mục', type: 'error' });
|
||||
setTrashLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeletePermanentTrash = async () => {
|
||||
if (selectedTrashIds.length === 0) return;
|
||||
if (!confirm(`CẢNH BÁO: Bạn có chắc muốn xóa VĨNH VIỄN ${selectedTrashIds.length} mục đã chọn? Thao tác này không thể hoàn tác.`)) return;
|
||||
if (!await confirm({
|
||||
title: '⚠️ XÓA VĨNH VIỄN',
|
||||
message: `Cảnh báo: Bạn có chắc muốn XÓA VĨNH VIỄN ${selectedTrashIds.length} mục đã chọn? Thao tác này KHÔNG THỂ hoàn tác!`
|
||||
})) return;
|
||||
|
||||
setTrashLoading(true);
|
||||
const idsToDelete = [...selectedTrashIds];
|
||||
// Convert plural to singular: 'photos' -> 'photo', 'tours' -> 'tour', 'notes' -> 'note'
|
||||
const itemType = trashSubTab === 'photos' ? 'photo' : trashSubTab === 'tours' ? 'tour' : 'note';
|
||||
setSelectedTrashIds([]);
|
||||
|
||||
try {
|
||||
console.log('[Trash] Starting permanent delete for', idsToDelete.length, 'items');
|
||||
console.log(`[Trash] Starting permanent delete for ${idsToDelete.length} ${itemType} items:`, idsToDelete);
|
||||
|
||||
const res = await fetch('/api/v1/admin/trash/delete-permanent', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -511,7 +543,7 @@ export const UserManagementModal: React.FC<UserManagementModalProps> = ({ isOpen
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: trashSubTab,
|
||||
type: itemType,
|
||||
ids: idsToDelete
|
||||
})
|
||||
});
|
||||
@@ -519,36 +551,117 @@ export const UserManagementModal: React.FC<UserManagementModalProps> = ({ isOpen
|
||||
console.log('[Trash] Delete response status:', res.status);
|
||||
|
||||
if (res.ok) {
|
||||
console.log('[Trash] Delete successful, clearing state immediately');
|
||||
const responseData = await res.json();
|
||||
console.log('[Trash] Delete successful, response:', responseData);
|
||||
setError('');
|
||||
alert('Đã xóa vĩnh viễn thành công!');
|
||||
|
||||
// Small delay to let alert close, then refresh data
|
||||
// Build success message
|
||||
const { deleted = 0, failed = 0, errors = [] } = responseData;
|
||||
if (failed === 0) {
|
||||
notify({ title: '✓ Xóa thành công', message: `Đã xóa vĩnh viễn ${deleted} mục.`, type: 'success' });
|
||||
} else {
|
||||
let errorMsg = `Xóa thành công ${deleted} mục, thất bại ${failed} mục.`;
|
||||
if (errors && errors.length > 0) {
|
||||
errorMsg += ` Lỗi: ${errors[0]}`;
|
||||
}
|
||||
notify({ title: '⚠️ Xóa một phần', message: errorMsg, type: 'error' });
|
||||
}
|
||||
|
||||
// Refresh trash data after delete
|
||||
setTimeout(() => {
|
||||
console.log('[Trash] Refreshing trash data after delete');
|
||||
fetchTrashData();
|
||||
setTrashLoading(false);
|
||||
}, 500);
|
||||
return; // Important: exit early so finally doesn't run again
|
||||
}, 800);
|
||||
} else {
|
||||
const errorData = await res.json().catch(() => ({}));
|
||||
const errorMsg = errorData.message || `HTTP ${res.status}`;
|
||||
console.error('[Trash] Delete failed:', errorMsg);
|
||||
const errorMsg = errorData?.message || `Lỗi HTTP ${res.status}`;
|
||||
console.error('[Trash] Delete failed with status', res.status, ':', errorMsg);
|
||||
setError(errorMsg);
|
||||
alert(`Xóa thất bại: ${errorMsg}`);
|
||||
notify({ title: '✗ Xóa thất bại', message: errorMsg, type: 'error' });
|
||||
|
||||
// Refresh on error
|
||||
fetchTrashData();
|
||||
setTrashLoading(false);
|
||||
// Refresh to see current state
|
||||
setTimeout(() => {
|
||||
fetchTrashData();
|
||||
setTrashLoading(false);
|
||||
}, 500);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('[Trash] Delete error:', e);
|
||||
setError(e.message || 'Lỗi khi xóa các mục');
|
||||
alert(`Lỗi: ${e.message}`);
|
||||
const errorMsg = e?.message || 'Lỗi mạng khi xóa các mục';
|
||||
setError(errorMsg);
|
||||
notify({ title: '✗ Lỗi', message: errorMsg, type: 'error' });
|
||||
|
||||
// Refresh on error
|
||||
fetchTrashData();
|
||||
setTrashLoading(false);
|
||||
setTimeout(() => {
|
||||
fetchTrashData();
|
||||
setTrashLoading(false);
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEmptyAllTrash = async () => {
|
||||
if (!await confirm({
|
||||
title: '⚠️ LÀMTRỐNG THÙNG RÁC',
|
||||
message: `Cảnh báo: Thao tác này sẽ XÓA VĨNH VIỄN TẤT CẢ mục trong thùng rác! KHÔNG THỂ hoàn tác!`
|
||||
})) return;
|
||||
|
||||
setTrashLoading(true);
|
||||
|
||||
try {
|
||||
console.log('[Trash] Starting to empty ALL trash');
|
||||
|
||||
const res = await fetch('/api/v1/admin/trash/empty-all', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[Trash] Empty all response status:', res.status);
|
||||
|
||||
if (res.ok) {
|
||||
const responseData = await res.json();
|
||||
console.log('[Trash] Empty all successful, response:', responseData);
|
||||
setError('');
|
||||
setSelectedTrashIds([]);
|
||||
|
||||
const { totalDeleted = 0 } = responseData;
|
||||
notify({
|
||||
title: '✓ Thùng rác đã được làm trống',
|
||||
message: `Đã xóa vĩnh viễn ${totalDeleted} mục.`,
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
// Refresh trash data
|
||||
setTimeout(() => {
|
||||
console.log('[Trash] Refreshing trash data after empty all');
|
||||
fetchTrashData();
|
||||
setTrashLoading(false);
|
||||
}, 800);
|
||||
} else {
|
||||
const errorData = await res.json().catch(() => ({}));
|
||||
const errorMsg = errorData?.message || `Lỗi HTTP ${res.status}`;
|
||||
console.error('[Trash] Empty all failed with status', res.status, ':', errorMsg);
|
||||
setError(errorMsg);
|
||||
notify({ title: '✗ Làm trống thất bại', message: errorMsg, type: 'error' });
|
||||
|
||||
setTimeout(() => {
|
||||
fetchTrashData();
|
||||
setTrashLoading(false);
|
||||
}, 500);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('[Trash] Empty all error:', e);
|
||||
const errorMsg = e?.message || 'Lỗi mạng khi làm trống thùng rác';
|
||||
setError(errorMsg);
|
||||
notify({ title: '✗ Lỗi', message: errorMsg, type: 'error' });
|
||||
|
||||
setTimeout(() => {
|
||||
fetchTrashData();
|
||||
setTrashLoading(false);
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -563,11 +676,11 @@ export const UserManagementModal: React.FC<UserManagementModalProps> = ({ isOpen
|
||||
body: JSON.stringify({ days: retentionDaysInput })
|
||||
});
|
||||
if (res.ok) {
|
||||
alert('Đã cập nhật số ngày lưu trữ trong thùng rác.');
|
||||
notify({ title: '✓ Cập nhật thành công', message: 'Số ngày lưu trữ đã được cập nhật.', type: 'success' });
|
||||
fetchTrashData();
|
||||
} else {
|
||||
const data = await res.json();
|
||||
alert(data.message || 'Lỗi khi cập nhật số ngày lưu trữ.');
|
||||
notify({ title: '✗ Lỗi', message: data.message || 'Lỗi khi cập nhật số ngày lưu trữ.', type: 'error' });
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -1263,6 +1376,20 @@ export const UserManagementModal: React.FC<UserManagementModalProps> = ({ isOpen
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Empty All Trash button */}
|
||||
{(trashData.tours?.length > 0 || trashData.photos?.length > 0 || trashData.notes?.length > 0) && (
|
||||
<div className="bg-red-50 p-4 rounded-2xl border border-red-100">
|
||||
<button
|
||||
onClick={handleEmptyAllTrash}
|
||||
disabled={trashLoading}
|
||||
className="w-full px-4 py-3 bg-red-600 hover:bg-red-700 disabled:bg-gray-400 text-white rounded-xl font-bold text-sm uppercase tracking-wide flex items-center justify-center gap-2 shadow-md transition-all active:scale-95 cursor-pointer"
|
||||
>
|
||||
{trashLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Trash2 className="w-4 h-4" />}
|
||||
Làm trống thùng rác ({(trashData.tours?.length || 0) + (trashData.photos?.length || 0) + (trashData.notes?.length || 0)} mục)
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sub-tab selection */}
|
||||
<div className="flex gap-2 border-b border-gray-100 pb-2">
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user