Nguyendat92929 commited on
Commit
55ae194
·
verified ·
1 Parent(s): 89f041b

Update templates/admin_dashboard.html

Browse files
Files changed (1) hide show
  1. templates/admin_dashboard.html +364 -452
templates/admin_dashboard.html CHANGED
@@ -1,453 +1,365 @@
1
- <!DOCTYPE html>
2
- <html lang="vi">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Quản người dùng</title>
8
- <!-- Bootstrap CSS -->
9
- <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
10
- <!-- Font Awesome for icons -->
11
- <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
12
- <!-- Google Fonts -->
13
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
14
- <!-- DataTables CSS -->
15
- <link href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css" rel="stylesheet">
16
- <style>
17
- :root {
18
- --primary-color: #0c1a28;
19
- --sidebar-bg: #2c3e50;
20
- --text-color: #212529;
21
- --bg-color: #f8f9fa;
22
- }
23
-
24
- [data-theme="dark"] {
25
- --primary-color: #0c1a28;
26
- --sidebar-bg: #1a252f;
27
- --text-color: #f8f9fa;
28
- --bg-color: #212529;
29
- }
30
-
31
- body {
32
- background-color: var(--bg-color);
33
- color: var(--text-color);
34
- font-family: 'Inter', sans-serif;
35
- transition: all 0.3s;
36
- }
37
-
38
- .sidebar {
39
- width: 250px;
40
- position: fixed;
41
- top: 0;
42
- bottom: 0;
43
- left: 0;
44
- background-color: var(--sidebar-bg);
45
- color: #fff;
46
- padding-top: 20px;
47
- transition: all 0.3s;
48
- z-index: 1000;
49
- }
50
-
51
- .sidebar.collapsed {
52
- margin-left: -250px;
53
- }
54
-
55
- .sidebar .nav-link {
56
- color: #fff;
57
- padding: 12px 20px;
58
- border-radius: 5px;
59
- margin: 0 10px;
60
- transition: all 0.2s;
61
- }
62
-
63
- .sidebar .nav-link:hover {
64
- background-color: rgba(255, 255, 255, 0.1);
65
- }
66
-
67
- .sidebar .nav-link i {
68
- margin-right: 10px;
69
- }
70
-
71
- .main-content {
72
- margin-left: 250px;
73
- padding: 30px;
74
- transition: all 0.3s;
75
- }
76
-
77
- .main-content.shifted {
78
- margin-left: 0;
79
- }
80
-
81
- .navbar {
82
- background-color: var(--primary-color);
83
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
84
- }
85
-
86
- .navbar-brand,
87
- .welcome-text {
88
- color: #fff !important;
89
- }
90
-
91
- .table {
92
- background-color: #fff;
93
- border-radius: 8px;
94
- overflow: hidden;
95
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
96
- }
97
-
98
- [data-theme="dark"] .table {
99
- background-color: #343a40;
100
- }
101
-
102
- .table-image img {
103
- width: 40px;
104
- height: 40px;
105
- border-radius: 50%;
106
- object-fit: cover;
107
- border: 2px solid #fff;
108
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
109
- }
110
-
111
- .action-btn {
112
- padding: 6px 12px;
113
- font-size: 0.9rem;
114
- border-radius: 5px;
115
- margin-right: 5px;
116
- }
117
-
118
- .modal-content {
119
- border-radius: 10px;
120
- animation: slideIn 0.3s ease-in-out;
121
- }
122
-
123
- @keyframes slideIn {
124
- from {
125
- transform: translateY(-50px);
126
- opacity: 0;
127
- }
128
-
129
- to {
130
- transform: translateY(0);
131
- opacity: 1;
132
- }
133
- }
134
-
135
- .form-control,
136
- .form-select {
137
- border-radius: 6px;
138
- transition: border-color 0.2s;
139
- }
140
-
141
- .form-control:focus,
142
- .form-select:focus {
143
- border-color: var(--primary-color);
144
- box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
145
- }
146
-
147
- .btn-primary {
148
- background-color: var(--primary-color);
149
- border-color: var(--primary-color);
150
- }
151
-
152
- .btn-primary:hover {
153
- background-color: #218838;
154
- border-color: #218838;
155
- }
156
-
157
- @media (max-width: 768px) {
158
- .sidebar {
159
- margin-left: -250px;
160
- }
161
-
162
- .main-content {
163
- margin-left: 0;
164
- }
165
-
166
- .sidebar.collapsed {
167
- margin-left: 0;
168
- }
169
-
170
- .table-responsive {
171
- overflow-x: auto;
172
- }
173
- }
174
- </style>
175
- </head>
176
-
177
- <body>
178
- <!-- Navbar -->
179
- <nav class="navbar navbar-expand-lg navbar-dark">
180
- <div class="container-fluid">
181
- <button class="navbar-toggler me-3" id="sidebarToggle" aria-label="Toggle sidebar">
182
- <span class="navbar-toggler-icon"></span>
183
- </button>
184
- <a class="navbar-brand" href="#">Quản lý người dùng</a>
185
- <div class="ms-auto d-flex align-items-center">
186
- <span class="welcome-text me-3">Xin chào, {{ user_name | default('Admin') | e }}!</span>
187
- <!-- <i class="fas fa-moon theme-toggle me-3" id="themeToggle" data-bs-toggle="tooltip" title="Chuyển đổi giao diện"></i> -->
188
- <button class="btn btn-outline-light btn-sm" onclick="logout()">Đăng xuất</button>
189
- </div>
190
- </div>
191
- </nav>
192
-
193
- <!-- Sidebar -->
194
- <div class="sidebar" id="sidebar">
195
- <h5 class="text-center mb-4">Quản lý</h5>
196
- <nav class="nav flex-column">
197
- <a class="nav-link" href="#"><i class="fas fa-tachometer-alt"></i> Dashboard</a>
198
- <a class="nav-link active" href="#"><i class="fas fa-users"></i> Người dùng</a>
199
- <a class="nav-link" href="#"><i class="fas fa-cog"></i> Cài đặt</a>
200
- <a class="nav-link" href="#"><i class="fas fa-chart-bar"></i> Báo cáo</a>
201
- <a class="nav-link" href="#"><i class="fas fa-user"></i> Hồ sơ</a>
202
- <a class="nav-link" href="#" onclick="logout()"><i class="fas fa-sign-out-alt"></i> Đăng xuất</a>
203
- </nav>
204
- </div>
205
-
206
- <!-- Main Content -->
207
- <div class="main-content" id="mainContent">
208
- <div class="container table-container">
209
- <div class="d-flex justify-content-between align-items-center mb-4">
210
- <h1>Danh sách người dùng</h1>
211
- <button class="btn btn-primary"><i class="fas fa-plus me-2"></i>Thêm mới</button>
212
- </div>
213
- <div class="card">
214
- <div class="card-body">
215
- <div class="table-responsive">
216
- <table class="table table-striped table-bordered">
217
- <thead class="table-success">
218
- <tr>
219
- <th>ID</th>
220
- <th>Tên người dùng</th>
221
- <th>Email</th>
222
- <th>Số điện thoại</th>
223
- <th>Loại tài khoản</th>
224
- <th>Lượt hỏi đáp</th>
225
- <th>Hành động</th>
226
- </tr>
227
- </thead>
228
- <tbody>
229
- {% for user in users %}
230
- <tr>
231
- <td>{{ user.id }}</td>
232
- <td>{{ user.username | e }}</td>
233
- <td>{{ user.email | e }}</td>
234
- <td>{{ user.phone | e }}</td>
235
- <td>{{ user.account_type | e }}</td>
236
- <td>{{ user.query_count }}/{{ user.query_limit or 'Không giới hạn' }}</td>
237
- <td>
238
- <button class="btn btn-primary btn-sm update-btn"
239
- onclick="openUpdateModal('{{ user.id | e }}', '{{ user.account_type | e }}', {{ 'true' if user.is_admin else 'false' }}, '{{ user.query_limit | default('') | e }}')">Cập
240
- nhật</button>
241
- <button class="btn btn-danger btn-sm delete-btn"
242
- onclick="deleteUser('{{ user.id | e }}')">Xóa</button>
243
- <button class="btn btn-warning btn-sm reset-btn"
244
- onclick="resetQuery('{{ user.id | e }}')">Reset Lượt</button>
245
- </td>
246
- </tr>
247
- {% endfor %}
248
- </tbody>
249
- </table>
250
- </div>
251
- </div>
252
- </div>
253
- </div>
254
- </div>
255
-
256
- <!-- Update Modal -->
257
- <div class="modal fade" id="updateModal" tabindex="-1" aria-labelledby="updateModalLabel" aria-hidden="true">
258
- <div class="modal-dialog">
259
- <div class="modal-content">
260
- <div class="modal-header">
261
- <h5 class="modal-title" id="updateModalLabel">Cập nhật người dùng</h5>
262
- <button type="button" class="btn-close" onclick="closeUpdateModal()" aria-label="Close"></button>
263
- </div>
264
- <div class="modal-body">
265
- <form id="updateForm">
266
- <input type="hidden" id="updateUserId">
267
- <div class="mb-3">
268
- <label for="updateAccountType" class="form-label">Loại tài khoản:</label>
269
- <select id="updateAccountType" class="form-select" onchange="toggleQueryLimit()">
270
- <option value="limited">Limited</option>
271
- <option value="unlimited">Unlimited</option>
272
- </select>
273
- </div>
274
- <div class="mb-3 form-check">
275
- <input type="checkbox" class="form-check-input" id="updateIsAdmin">
276
- <label class="form-check-label" for="updateIsAdmin">Admin</label>
277
- </div>
278
- <div class="mb-3">
279
- <label for="updateQueryLimit" class="form-label">Giới hạn hỏi đáp (cho tài khoản
280
- limited):</label>
281
- <input type="number" class="form-control" id="updateQueryLimit" min="1"
282
- placeholder="Nhập giới hạn (ví dụ: 10)">
283
- </div>
284
- <div class="d-flex justify-content-end">
285
- <button type="submit" class="btn btn-success me-2">Lưu</button>
286
- <button type="button" class="btn btn-secondary cancel-btn"
287
- onclick="closeUpdateModal()">Hủy</button>
288
- </div>
289
- </form>
290
- </div>
291
- </div>
292
- </div>
293
- </div>
294
-
295
- <!-- jQuery (required for DataTables) -->
296
- <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
297
- <!-- Bootstrap JS -->
298
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
299
- <!-- DataTables JS -->
300
- <script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
301
- <script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
302
- <script>
303
- // Sidebar toggle
304
- document.getElementById('sidebarToggle').addEventListener('click', () => {
305
- const sidebar = document.getElementById('sidebar');
306
- const mainContent = document.getElementById('mainContent');
307
- sidebar.classList.toggle('collapsed');
308
- mainContent.classList.toggle('shifted');
309
- });
310
-
311
- // Theme toggle
312
- const themeToggle = document.getElementById('themeToggle');
313
- themeToggle.addEventListener('click', () => {
314
- const currentTheme = document.documentElement.getAttribute('data-theme');
315
- if (currentTheme === 'dark') {
316
- document.documentElement.removeAttribute('data-theme');
317
- themeToggle.classList.remove('fa-sun');
318
- themeToggle.classList.add('fa-moon');
319
- } else {
320
- document.documentElement.setAttribute('data-theme', 'dark');
321
- themeToggle.classList.remove('fa-moon');
322
- themeToggle.classList.add('fa-sun');
323
- }
324
- localStorage.setItem('theme', document.documentElement.getAttribute('data-theme') || 'light');
325
- });
326
-
327
- // Load saved theme
328
- const savedTheme = localStorage.getItem('theme');
329
- if (savedTheme === 'dark') {
330
- document.documentElement.setAttribute('data-theme', 'dark');
331
- themeToggle.classList.remove('fa-moon');
332
- themeToggle.classList.add('fa-sun');
333
- }
334
-
335
- // Initialize DataTables
336
- $(document).ready(function () {
337
- $('#userTable').DataTable({
338
- language: {
339
- url: '//cdn.datatables.net/plug-ins/1.13.6/i18n/vi.json' // Vietnamese translation
340
- },
341
- pageLength: 10,
342
- responsive: true,
343
- columnDefs: [
344
- { orderable: false, targets: 6 } // Disable sorting for action column
345
- ]
346
- });
347
- });
348
-
349
- // Initialize tooltips
350
- const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
351
- const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
352
-
353
- </script>
354
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
355
- <script>
356
- async function deleteUser(userId) {
357
- if (confirm('Bạn có chắc muốn xóa người dùng này?')) {
358
- const response = await fetch(`/admin/user/${userId}`, {
359
- method: 'DELETE',
360
- headers: { 'Content-Type': 'application/json' }
361
- });
362
- const result = await response.json();
363
- alert(result.message || result.error);
364
- location.reload();
365
- }
366
- }
367
-
368
- async function resetQuery(userId) {
369
- const response = await fetch(`/admin/user/${userId}/reset_query`, {
370
- method: 'POST',
371
- headers: { 'Content-Type': 'application/json' }
372
- });
373
- const result = await response.json();
374
- alert(result.message || result.error);
375
- location.reload();
376
- }
377
-
378
- function openUpdateModal(userId, accountType, isAdmin, queryLimit) {
379
- document.getElementById('updateUserId').value = userId;
380
- document.getElementById('updateAccountType').value = accountType;
381
- document.getElementById('updateIsAdmin').checked = isAdmin;
382
- document.getElementById('updateQueryLimit').value = queryLimit || '';
383
- toggleQueryLimit();
384
- const modal = new bootstrap.Modal(document.getElementById('updateModal'));
385
- modal.show();
386
- }
387
-
388
- function closeUpdateModal() {
389
- const modal = bootstrap.Modal.getInstance(document.getElementById('updateModal'));
390
- modal.hide();
391
- }
392
-
393
- function toggleQueryLimit() {
394
- const accountType = document.getElementById('updateAccountType').value;
395
- const queryLimitInput = document.getElementById('updateQueryLimit');
396
- queryLimitInput.disabled = accountType === 'unlimited';
397
- if (accountType === 'unlimited') {
398
- queryLimitInput.value = '';
399
- }
400
- }
401
-
402
- async function logout() {
403
- try {
404
- const response = await fetch('/logout', {
405
- method: 'POST',
406
- headers: { 'Content-Type': 'application/json' }
407
- });
408
- if (response.ok) {
409
- // Clear client-side state (adapted for dashboard context)
410
- // Variables like currentUser, conversations, etc., may not exist here
411
- // If needed, define them or remove irrelevant ones
412
- alert('Đăng xuất thành công');
413
- window.location.href = '/';
414
- } else {
415
- alert('Lỗi khi đăng xuất');
416
- }
417
- } catch (error) {
418
- alert('Lỗi khi đăng xuất: ' + error.message);
419
- }
420
- }
421
-
422
- document.getElementById('updateForm').addEventListener('submit', async (e) => {
423
- e.preventDefault();
424
- const userId = document.getElementById('updateUserId').value;
425
- const accountType = document.getElementById('updateAccountType').value;
426
- const isAdmin = document.getElementById('updateIsAdmin').checked;
427
- const queryLimit = document.getElementById('updateQueryLimit').value;
428
- if (accountType === 'limited' && queryLimit && !/^\d+$/.test(queryLimit)) {
429
- alert('Giới hạn hỏi đáp phải là một số nguyên dương.');
430
- return;
431
- }
432
- const updates = { account_type: accountType, is_admin: isAdmin };
433
- if (accountType === 'limited' && queryLimit) {
434
- updates.query_limit = parseInt(queryLimit);
435
- }
436
- try {
437
- const response = await fetch(`/admin/user/${userId}`, {
438
- method: 'PUT',
439
- headers: { 'Content-Type': 'application/json' },
440
- body: JSON.stringify(updates)
441
- });
442
- const result = await response.json();
443
- alert(result.message || result.error);
444
- closeUpdateModal();
445
- location.reload();
446
- } catch (error) {
447
- alert('Lỗi khi cập nhật người dùng: ' + error.message);
448
- }
449
- });
450
- </script>
451
- </body>
452
-
453
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="vi">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Quản người dùng</title>
7
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
9
+ <link rel="stylesheet" href="{{ url_for('static', filename='style_admin.css') }}">
10
+ </head>
11
+ <body>
12
+ <div class="sidebar" id="sidebar">
13
+ <div class="sidebar-header">Quản lý</div>
14
+ <hr>
15
+ <nav class="sidebar-nav">
16
+ <ul>
17
+ <li><a href="#" id="dashboardLink"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
18
+ <li><a href="#" id="usersLink" class="active"><i class="fas fa-users"></i> Người dùng</a></li>
19
+ <li><a href="#" id="pendingUsersLink"><i class="fas fa-user-clock"></i> Người dùng chờ xác thực</a></li>
20
+ <li><a href="#"><i class="fas fa-cog"></i> Cài đặt</a></li>
21
+ <li><a href="#"><i class="fas fa-chart-bar"></i> Báo cáo</a></li>
22
+ <li><a href="#"><i class="fas fa-user-circle"></i> Hồ sơ</a></li>
23
+ <li><a href="#" onclick="logout()"><i class="fas fa-sign-out-alt"></i> Đăng xuất</a></li>
24
+ </ul>
25
+ </nav>
26
+ </div>
27
+
28
+ <div class="main-content" id="mainContent">
29
+ <div class="top-bar">
30
+ <button class="menu-toggle" id="menuToggle"><i class="fas fa-bars"></i></button>
31
+ <span>Xin chào, {{ user_name | default('Admin') | e }}!</span>
32
+ <button onclick="logout()">Đăng xuất</button>
33
+ </div>
34
+
35
+ <div class="content-area" id="contentArea">
36
+ <!-- Users Tab -->
37
+ <div id="usersTab">
38
+ <div class="content-header">
39
+ <h1>Danh sách người dùng</h1>
40
+ <button class="add-new-btn"><i class="fas fa-plus"></i> Thêm mới</button>
41
+ </div>
42
+ <div class="search-bar">
43
+ <i class="fas fa-search"></i>
44
+ <input type="text" placeholder="Tìm kiếm người dùng...">
45
+ </div>
46
+ <div class="table-responsive">
47
+ <table class="table table-striped table-bordered" id="userTable">
48
+ <thead class="table-success">
49
+ <tr>
50
+ <th>ID</th>
51
+ <th>Tên người dùng</th>
52
+ <th>Vai trò</th>
53
+ <th>Email</th>
54
+ <th>Số điện thoại</th>
55
+ <th>Loại tài khoản</th>
56
+ <th>Lượt hỏi đáp</th>
57
+ <th>Hành động</th>
58
+ </tr>
59
+ </thead>
60
+ <tbody>
61
+ {% for user in users %}
62
+ <tr>
63
+ <td data-label="ID">{{ user.id }}</td>
64
+ <td data-label="Tên người dùng">{{ user.username | e }}</td>
65
+ <td data-label="Vai trò">{% if user.is_admin %}Admin{% else %}User{% endif %}</td>
66
+ <td data-label="Email">{{ user.email | e }}</td>
67
+ <td data-label="Số điện thoại">{{ user.phone | e }}</td>
68
+ <td data-label="Loại tài khoản">
69
+ {% if user.account_type == 'limited' %}
70
+ <span class="badge badge-pill badge-warning">Giới hạn</span>
71
+ {% else %}
72
+ <span class="badge badge-pill badge-success">Không giới hạn</span>
73
+ {% endif %}
74
+ </td>
75
+ <td data-label="Lượt hỏi đáp">
76
+ {% if user.account_type == 'unlimited' %}
77
+ <span class="query-normal">Không giới hạn</span>
78
+ {% elif user.query_limit %}
79
+ <span class="query-normal">{{ user.query_count }}/{{ user.query_limit | e }}</span>
80
+ {% else %}
81
+ <span class="query-normal">{{ user.query_count }}/{{ user.account_type | e }}</span>
82
+ {% endif %}
83
+ </td>
84
+ <td data-label="Hành động" class="action-buttons">
85
+ <button class="edit-btn update-btn" onclick="openUpdateModal('{{ user.id | e }}', '{{ user.account_type | e }}', {{ 'true' if user.is_admin else 'false' }}, '{{ user.query_limit | default('') | e }}')">
86
+ <i class="fas fa-pencil-alt"></i>
87
+ </button>
88
+ <button class="delete-btn" onclick="deleteUser('{{ user.id | e }}')">
89
+ <i class="fas fa-trash-alt"></i>
90
+ </button>
91
+ <button class="reset-btn" onclick="resetQuery('{{ user.id | e }}')">
92
+ <i class="fas fa-redo-alt"></i>
93
+ </button>
94
+ </td>
95
+ </tr>
96
+ {% endfor %}
97
+ </tbody>
98
+ </table>
99
+ </div>
100
+ </div>
101
+
102
+ <!-- Pending Users Tab -->
103
+ <div id="pendingUsersTab" style="display: none;">
104
+ <div class="content-header">
105
+ <h1>Danh sách người dùng chờ xác thực</h1>
106
+ </div>
107
+ <div class="table-responsive">
108
+ <table class="table table-striped table-bordered" id="pendingUserTable">
109
+ <thead class="table-success">
110
+ <tr>
111
+ <th>ID</th>
112
+ <th>Tên người dùng</th>
113
+ <th>Email</th>
114
+ <th>Số điện thoại</th>
115
+ <th>Loại tài khoản</th>
116
+ <th>Ngày đăng ký</th>
117
+ <th>Hành động</th>
118
+ </tr>
119
+ </thead>
120
+ <tbody id="pendingUsersBody">
121
+ <!-- Populated by JavaScript -->
122
+ </tbody>
123
+ </table>
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </div>
128
+
129
+ <!-- Update Modal (Existing) -->
130
+ <div class="modal fade" id="updateModal" tabindex="-1" aria-labelledby="updateModalLabel" aria-hidden="true">
131
+ <div class="modal-dialog">
132
+ <div class="modal-content">
133
+ <div class="modal-header">
134
+ <h5 class="modal-title" id="updateModalLabel">Cập nhật người dùng</h5>
135
+ <button type="button" class="btn-close" onclick="closeUpdateModal()" aria-label="Close"></button>
136
+ </div>
137
+ <div class="modal-body">
138
+ <form id="updateForm">
139
+ <input type="hidden" id="updateUserId">
140
+ <div class="mb-3">
141
+ <label for="updateAccountType" class="form-label">Loại tài khoản:</label>
142
+ <select id="updateAccountType" class="form-select" onchange="toggleQueryLimit()">
143
+ <option value="limited">Limited</option>
144
+ <option value="unlimited">Unlimited</option>
145
+ </select>
146
+ </div>
147
+ <div class="mb-3 form-check">
148
+ <input type="checkbox" class="form-check-input" id="updateIsAdmin">
149
+ <label class="form-check-label" for="updateIsAdmin">Admin</label>
150
+ </div>
151
+ <div class="mb-3">
152
+ <label for="updateQueryLimit class="form-label">Giới hạn hỏi đáp (cho tài khoản limited):</label>
153
+ <input type="number" class="form-control" id="updateQueryLimit" min="1" placeholder="Nhập giới hạn (ví dụ: 10)">
154
+ </div>
155
+ <div class="d-flex justify-content-end">
156
+ <button type="submit" class="btn btn-success me-2">Lưu</button>
157
+ <button type="button" class="btn btn-secondary cancel-btn" onclick="closeUpdateModal()">Hủy</button>
158
+ </div>
159
+ </form>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ </div>
164
+
165
+ <!-- jQuery and Bootstrap JS -->
166
+ <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
167
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
168
+ <script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
169
+ <script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
170
+ <script>
171
+ // Sidebar toggle
172
+ document.getElementById('menuToggle').addEventListener('click', () => {
173
+ const sidebar = document.getElementById('sidebar');
174
+ const mainContent = document.getElementById('mainContent');
175
+ sidebar.classList.toggle('collapsed');
176
+ mainContent.classList.toggle('shifted');
177
+ });
178
+
179
+ // Tab switching
180
+ document.getElementById('usersLink').addEventListener('click', (e) => {
181
+ e.preventDefault();
182
+ document.getElementById('usersTab').style.display = 'block';
183
+ document.getElementById('pendingUsersTab').style.display = 'none';
184
+ document.getElementById('usersLink').classList.add('active');
185
+ document.getElementById('pendingUsersLink').classList.remove('active');
186
+ });
187
+
188
+ document.getElementById('pendingUsersLink').addEventListener('click', (e) => {
189
+ e.preventDefault();
190
+ document.getElementById('usersTab').style.display = 'none';
191
+ document.getElementById('pendingUsersTab').style.display = 'block';
192
+ document.getElementById('usersLink').classList.remove('active');
193
+ document.getElementById('pendingUsersLink').classList.add('active');
194
+ loadPendingUsers();
195
+ });
196
+
197
+ // Initialize DataTables for users
198
+ $(document).ready(function () {
199
+ $('#userTable').DataTable({
200
+ language: { url: '//cdn.datatables.net/plug-ins/1.13.6/i18n/vi.json' },
201
+ pageLength: 10,
202
+ responsive: true,
203
+ columnDefs: [{ orderable: false, targets: 6 }]
204
+ });
205
+
206
+ $('#pendingUserTable').DataTable({
207
+ language: { url: '//cdn.datatables.net/plug-ins/1.13.6/i18n/vi.json' },
208
+ pageLength: 10,
209
+ responsive: true,
210
+ columnDefs: [{ orderable: false, targets: 6 }],
211
+ searching: false
212
+ });
213
+ });
214
+
215
+ // Load pending users
216
+ async function loadPendingUsers() {
217
+ try {
218
+ const response = await fetch('/admin/pending_users', {
219
+ method: 'GET',
220
+ headers: { 'Content-Type': 'application/json' }
221
+ });
222
+ const users = await response.json();
223
+ const tbody = document.getElementById('pendingUsersBody');
224
+ tbody.innerHTML = '';
225
+ users.forEach(user => {
226
+ const row = `
227
+ <tr>
228
+ <td data-label="ID">${user.id}</td>
229
+ <td data-label="Tên người dùng">${user.username}</td>
230
+ <td data-label="Email">${user.email}</td>
231
+ <td data-label="Số điện thoại">${user.phone}</td>
232
+ <td data-label="Loại tài khoản">
233
+ <span class="badge badge-pill ${user.account_type === 'limited' ? 'badge-warning' : 'badge-success'}">
234
+ ${user.account_type === 'limited' ? 'Giới hạn' : 'Không giới hạn'}
235
+ </span>
236
+ </td>
237
+ <td data-label="Ngày đăng ký">${user.created_at}</td>
238
+ <td data-label="Hành động" class="action-buttons">
239
+ <button class="btn btn-success btn-sm" onclick="verifyUser('${user.id}', 'approve')">
240
+ <i class="fas fa-check"></i> Chấp nhận
241
+ </button>
242
+ <button class="btn btn-danger btn-sm" onclick="verifyUser('${user.id}', 'reject')">
243
+ <i class="fas fa-times"></i> Từ chối
244
+ </button>
245
+ </td>
246
+ </tr>`;
247
+ tbody.insertAdjacentHTML('beforeend', row);
248
+ });
249
+ } catch (error) {
250
+ alert('Lỗi khi tải danh sách người dùng chờ xác thực: ' + error.message);
251
+ }
252
+ }
253
+
254
+ // Verify user (approve/reject)
255
+ async function verifyUser(userId, action) {
256
+ if (!confirm(`Bạn chắc muốn ${action === 'approve' ? 'chấp nhận' : 'từ chối'} người dùng này?`)) return;
257
+ try {
258
+ const response = await fetch(`/admin/user/${userId}/verify`, {
259
+ method: 'POST',
260
+ headers: { 'Content-Type': 'application/json' },
261
+ body: JSON.stringify({ action })
262
+ });
263
+ const result = await response.json();
264
+ alert(result.message || result.error);
265
+ loadPendingUsers(); // Refresh pending users list
266
+ } catch (error) {
267
+ alert('Lỗi khi xử lý: ' + error.message);
268
+ }
269
+ }
270
+
271
+ // Existing functions (deleteUser, resetQuery, openUpdateModal, etc.)
272
+ async function deleteUser(userId) {
273
+ if (confirm('Bạn có chắc muốn xóa người dùng này?')) {
274
+ const response = await fetch(`/admin/user/${userId}`, {
275
+ method: 'DELETE',
276
+ headers: { 'Content-Type': 'application/json' }
277
+ });
278
+ const result = await response.json();
279
+ alert(result.message || result.error);
280
+ location.reload();
281
+ }
282
+ }
283
+
284
+ async function resetQuery(userId) {
285
+ const response = await fetch(`/admin/user/${userId}/reset_query`, {
286
+ method: 'POST',
287
+ headers: { 'Content-Type': 'application/json' }
288
+ });
289
+ const result = await response.json();
290
+ alert(result.message || result.error);
291
+ location.reload();
292
+ }
293
+
294
+ function openUpdateModal(userId, accountType, isAdmin, queryLimit) {
295
+ document.getElementById('updateUserId').value = userId;
296
+ document.getElementById('updateAccountType').value = accountType;
297
+ document.getElementById('updateIsAdmin').checked = isAdmin;
298
+ document.getElementById('updateQueryLimit').value = queryLimit || '';
299
+ toggleQueryLimit();
300
+ const modal = new bootstrap.Modal(document.getElementById('updateModal'));
301
+ modal.show();
302
+ }
303
+
304
+ function closeUpdateModal() {
305
+ const modal = bootstrap.Modal.getInstance(document.getElementById('updateModal'));
306
+ modal.hide();
307
+ }
308
+
309
+ function toggleQueryLimit() {
310
+ const accountType = document.getElementById('updateAccountType').value;
311
+ const queryLimitInput = document.getElementById('updateQueryLimit');
312
+ queryLimitInput.disabled = accountType === 'unlimited';
313
+ if (accountType === 'unlimited') {
314
+ queryLimitInput.value = '';
315
+ }
316
+ }
317
+
318
+ async function logout() {
319
+ try {
320
+ const response = await fetch('/logout', {
321
+ method: 'POST',
322
+ headers: { 'Content-Type': 'application/json' }
323
+ });
324
+ if (response.ok) {
325
+ alert('Đăng xuất thành công');
326
+ window.location.href = '/';
327
+ } else {
328
+ alert('Lỗi khi đăng xuất');
329
+ }
330
+ } catch (error) {
331
+ alert('Lỗi khi đăng xuất: ' + error.message);
332
+ }
333
+ }
334
+
335
+ document.getElementById('updateForm').addEventListener('submit', async (e) => {
336
+ e.preventDefault();
337
+ const userId = document.getElementById('updateUserId').value;
338
+ const accountType = document.getElementById('updateAccountType').value;
339
+ const isAdmin = document.getElementById('updateIsAdmin').checked;
340
+ const queryLimit = document.getElementById('updateQueryLimit').value;
341
+ if (accountType === 'limited' && queryLimit && !/^\d+$/.test(queryLimit)) {
342
+ alert('Giới hạn hỏi đáp phải là một số nguyên dương.');
343
+ return;
344
+ }
345
+ const updates = { account_type: accountType, is_admin: isAdmin };
346
+ if (accountType === 'limited' && queryLimit) {
347
+ updates.query_limit = parseInt(queryLimit);
348
+ }
349
+ try {
350
+ const response = await fetch(`/admin/user/${userId}`, {
351
+ method: 'PUT',
352
+ headers: { 'Content-Type': 'application/json' },
353
+ body: JSON.stringify(updates)
354
+ });
355
+ const result = await response.json();
356
+ alert(result.message || result.error);
357
+ closeUpdateModal();
358
+ location.reload();
359
+ } catch (error) {
360
+ alert('Lỗi khi cập nhật người dùng: ' + error.message);
361
+ }
362
+ });
363
+ </script>
364
+ </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  </html>