Sửa đổi giao diện của quản lí người dùng

This commit is contained in:
2026-06-12 10:41:33 +07:00
parent b3af752884
commit 1995b63474
4 changed files with 175 additions and 53 deletions
+4 -1
View File
@@ -97,13 +97,16 @@ router.get('/users', protect, async (req, res) => {
router.put('/users/:id', protect, async (req, res) => {
if (req.user.role !== 'admin' && req.user.role !== 'Chủ sở hữu') return res.status(403).json({ message: 'Forbidden' });
try {
const { fullName, email, role, password } = req.body;
const { fullName, email, role, password, quota } = req.body;
const user = await User.findById(req.params.id);
if (!user) return res.status(404).json({ message: 'User not found' });
if (fullName) user.fullName = fullName;
if (email) user.email = email;
if (role && user.role !== 'admin') user.role = role;
if (password) user.password = password;
if (quota !== undefined) {
user.storage = { ...user.storage, quota: parseInt(quota) * 1024 * 1024 };
}
await user.save();
res.json({ message: 'User updated' });
} catch (error) { res.status(500).json({ message: error.message }); }
+111 -11
View File
@@ -1173,8 +1173,9 @@ html, body {
.admin-table th:nth-child(1) { min-width: 160px; } /* Họ tên */
.admin-table th:nth-child(2) { min-width: 120px; } /* Username */
.admin-table th:nth-child(3) { min-width: 200px; } /* Email */
.admin-table th:nth-child(4) { min-width: 130px; } /* Quyền hạn */
.admin-table th:nth-child(5) { min-width: 140px; } /* Reset Password */
.admin-table th:nth-child(4) { min-width: 120px; } /* Quyền hạn */
.admin-table th:nth-child(5) { min-width: 100px; } /* Dung lượng */
.admin-table th:nth-child(6) { min-width: 140px; } /* Reset Password */
.admin-table th:nth-child(6) { min-width: 140px; } /* Thao tác */
.admin-table td input, .admin-table td select {
@@ -1204,28 +1205,127 @@ html, body {
}
/* Admin User Management Header */
.admin-management-header {
.admin-management-controls {
display: flex;
align-items: center;
justify-content: flex-end;
flex-direction: column;
gap: 15px;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.cleanup-row {
display: flex;
justify-content: flex-end;
}
.cleanup-btn {
background: transparent; /* Nền trùng màu dashboard */
color: #fff;
padding: 6px 12px;
font-size: 13px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: #444444 !important;
color: #222222 !important;
padding: 0 20px !important;
height: 36px;
border: none !important;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: background 0.2s;
white-space: nowrap;
}
.cleanup-btn:hover {
background: rgba(255, 255, 255, 0.1); /* Màu xám active của dashboard */
background: #555555 !important;
}
.admin-search-container {
display: flex;
gap: 0; /* Sát cạnh nhau */
width: 100%;
}
.admin-search-container input {
flex: 1;
background: #262626 !important;
border: 1px solid #000000 !important;
color: #ffffff !important;
padding: 8px 15px !important;
border-radius: 6px 0 0 6px !important;
height: 38px;
}
.admin-search-btn {
background: #444444 !important;
color: #ffffff !important;
border: 1px solid #000000 !important;
padding: 0 25px !important;
border-radius: 0 6px 6px 0 !important;
cursor: pointer;
font-size: 13px;
transition: background 0.2s;
}
/* Card layout styles */
.admin-user-card {
background: #262626 !important;
border: 1px solid #404040 !important;
border-radius: 12px !important;
padding: 16px;
display: grid;
grid-template-columns: 1.5fr 1fr 2fr 1fr 1fr 1fr 1fr;
align-items: center;
gap: 15px;
margin-bottom: 12px;
}
.admin-users-header-row {
display: grid;
grid-template-columns: 1.5fr 1fr 2fr 1fr 1fr 1fr 1fr;
gap: 15px;
padding: 0 16px 12px 16px;
color: #a3a3a3;
font-size: 11px;
text-transform: uppercase;
font-weight: 600;
}
.card-field input:not(:disabled), .card-field select:not(:disabled) {
background: #ffffff !important;
color: #000000 !important;
border: 1px solid #404040;
border-radius: 4px;
padding: 6px 10px;
width: 100%;
}
.card-field input:disabled, .card-field select:disabled {
background: #404040 !important;
color: #a3a3a3 !important;
border: 1px solid #525252;
border-radius: 4px;
padding: 6px 10px;
width: 100%;
}
/* Màu sắc linh hoạt cho Input theo trạng thái */
.card-field input[type="text"], .card-field input[type="email"], .card-field input[type="number"] {
background: #ffffff !important;
color: #000000 !important;
}
.card-field input:disabled, .card-field select:disabled {
background: #404040 !important; /* Nền xám tối */
color: #a3a3a3 !important; /* Chữ xám mờ */
border-color: #525252 !important;
cursor: not-allowed;
}
.card-field .edit-btn-small {
background: #28a745 !important; /* Nền xanh lá */
width: 100%;
height: 34px;
border-radius: 4px;
color: #fff;
border: none;
cursor: pointer;
}
.admin-search-container {
+1 -11
View File
@@ -140,17 +140,7 @@
</div>
</div>
<div id="tab-user-management" class="dashboard-tab-pane admin-only">
<div class="admin-management-header">
<button class="cleanup-btn" onclick="openManualCleanupConfirm()">
🧹 Dọn dẹp dữ liệu
</button>
</div>
<div class="admin-search-container">
<input type="text" id="admin-user-search-input" placeholder="Tìm kiếm theo tên, email, username..." onkeydown="if(event.key === 'Enter') loadAdminUsers(1)">
<button onclick="loadAdminUsers(1)" class="admin-search-btn">Tìm kiếm</button>
</div>
<div id="admin-users-list" class="dashboard-list"></div>
<div id="admin-users-list"></div>
<div id="admin-users-pagination" class="pagination-container"></div>
</div>
<div id="tab-system-settings" class="dashboard-tab-pane admin-only">
+59 -30
View File
@@ -1986,49 +1986,78 @@ async function loadAdminUsers(page = 1) {
const { users, totalPages, currentPage, totalUsers } = data;
let html = `
<table class="admin-table">
<thead>
<tr>
<th>Họ tên</th>
<th>Username</th>
<th>Email</th>
<th>Quyền hạn</th>
<th>Reset Password</th>
<th>Thao tác</th>
</tr>
</thead>
<tbody>
<div class="admin-management-controls" style="display: flex; flex-direction: column; gap: 12px; margin-bottom: 24px; width: 100%;">
<div class="cleanup-row" style="display: flex; justify-content: flex-end; width: 100%;">
<button class="cleanup-btn" onclick="openManualCleanupConfirm()" style="background: #1a1a1a; color: #fff; padding: 6px 14px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer;">
<i class="fas fa-broom"></i> Dn dp d liu
</button>
</div>
<div class="admin-search-container" style="display: flex; width: 100%; gap: 8px;">
<input type="text" id="admin-user-search-input" placeholder="Tìm kiếm theo tên, email, username..." onkeydown="if(event.key === 'Enter') loadAdminUsers(1)" style="flex-grow: 1; background: #1a1a1a; color: #fff; padding: 8px 12px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; outline: none;">
<button class="admin-search-btn" onclick="loadAdminUsers(1)" style="background: #1a1a1a; color: #fff; padding: 8px 20px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; white-space: nowrap;">Tìm kiếm</button>
</div>
</div>
<div class="admin-users-box-list" style="width: 100%; display: flex; flex-direction: column; gap: 12px;">
<div class="admin-table-header" style="display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 16px; align-items: center; text-align: center; padding: 0 16px; color: #a3a3a3; font-size: 14px; font-weight: 600; margin-bottom: 4px;">
<div>Họ tên</div>
<div>Username</div>
<div>Email</div>
<div>Quyền hạn</div>
<div>Dung lượng</div>
<div>Reset Password</div>
<div>Thao tác</div>
</div>
`;
users.forEach(user => {
const isRootAdmin = user.role === 'admin';
const isRootAdmin = user.role === 'admin' || user.role === 'Chủ sở hữu';
const quotaMB = user.storage?.quota ? Math.floor(user.storage.quota / (1024 * 1024)) : 0;
const usedMB = user.storage?.used ? (user.storage.used / (1024 * 1024)).toFixed(1) : 0;
html += `
<tr>
<td><input type="text" id="adm-fn-${user._id}" value="${user.fullName || ''}"></td>
<td><strong>${user.username}</strong></td>
<td><input type="email" id="adm-em-${user._id}" value="${user.email || ''}"></td>
<td>
<select id="adm-role-${user._id}" ${isRootAdmin ? 'disabled' : ''}>
<div class="admin-user-box" style="display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 16px; align-items: center; text-align: center; background: #1a1a1a; border: 1px solid #262626; padding: 16px; border-radius: 12px;">
<div class="card-field">
<input type="text" id="adm-fn-${user._id}" value="${user.fullName || ''}" style="width: 100%; background: #111111; color: #fff; padding: 8px 10px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; outline: none;">
</div>
<div class="card-field" style="font-weight: bold; font-size: 14px; color: #fff;">
${user.username}
</div>
<div class="card-field">
<input type="email" id="adm-em-${user._id}" value="${user.email || ''}" style="width: 100%; background: #111111; color: #fff; padding: 8px 10px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; outline: none;">
</div>
<div class="card-field">
<select id="adm-role-${user._id}" ${isRootAdmin ? 'disabled' : ''} style="width: 100%; background: #111111; color: #fff; padding: 8px 10px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; cursor: ${isRootAdmin ? 'not-allowed' : 'pointer'}; outline: none;">
<option value="user" ${user.role === 'user' ? 'selected' : ''}>User</option>
<option value="editor" ${user.role === 'editor' ? 'selected' : ''}>Editor</option>
<option value="moderator" ${user.role === 'moderator' ? 'selected' : ''}>Moderator</option>
<option value="admin" ${user.role === 'admin' ? 'selected' : ''}>Admin</option>
</select>
</td>
<td><input type="password" id="adm-pw-${user._id}" placeholder="${isRootAdmin ? 'N/A' : 'Mật khẩu mới'}" ${isRootAdmin ? 'disabled' : ''}></td>
<td>
<button class="edit-btn-small" onclick="updateUserByAdmin('${user._id}')">Lưu</button>
${isRootAdmin ? '' : `<button class="delete-btn-small" onclick="deleteUserByAdmin('${user._id}')">Xóa</button>`}
</td>
</tr>
</div>
<div class="card-field" style="display: flex; flex-direction: column; align-items: center; gap: 4px;">
<input type="number" id="adm-quota-${user._id}" value="${quotaMB}" min="0" style="width: 100%; background: #111111; color: #fff; padding: 8px 10px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; text-align: center; outline: none;">
<small style="color: #737373; font-size: 11px;">Đã dùng: ${usedMB} MB</small>
</div>
<div class="card-field">
<input type="text" id="adm-pw-${user._id}" placeholder="N/A" disabled style="width: 100%; background: #1a1a1a; color: #525252; padding: 8px 10px; border: 1px solid #262626; border-radius: 6px; font-size: 14px; text-align: center; cursor: not-allowed;">
</div>
<div class="card-field">
<button class="edit-btn-small" onclick="updateUserByAdmin('${user._id}')" style="width: 100%; background: #1a1a1a; color: #fff; padding: 8px 12px; border: 1px solid #333333; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s;">Lưu</button>
</div>
</div>
`;
});
html += '</tbody></table>';
html += '</div>';
container.innerHTML = html;
// Render Pagination UI
if (paginationContainer && totalPages > 1) {
paginationContainer.innerHTML = `
<button class="pagination-btn" ${currentPage === 1 ? 'disabled' : ''} onclick="loadAdminUsers(${currentPage - 1})">Trang trước</button>
@@ -2040,14 +2069,14 @@ async function loadAdminUsers(page = 1) {
container.innerHTML = `<p style="color:red">Lỗi: ${e.message}</p>`;
}
}
window.updateUserByAdmin = async function(userId) {
const token = localStorage.getItem('jwt');
const payload = {
fullName: document.getElementById(`adm-fn-${userId}`).value,
email: document.getElementById(`adm-em-${userId}`).value,
role: document.getElementById(`adm-role-${userId}`).value,
password: document.getElementById(`adm-pw-${userId}`).value
password: document.getElementById(`adm-pw-${userId}`).value,
quota: document.getElementById(`adm-quota-${userId}`).value
};
try {