sqlalchemy-connection 2.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlalchemy_connection-2.0.1.dist-info/METADATA +26 -0
- sqlalchemy_connection-2.0.1.dist-info/RECORD +33 -0
- sqlalchemy_connection-2.0.1.dist-info/WHEEL +5 -0
- sqlalchemy_connection-2.0.1.dist-info/entry_points.txt +2 -0
- sqlalchemy_connection-2.0.1.dist-info/top_level.txt +1 -0
- sqlalchemy_connector/__init__.py +3 -0
- sqlalchemy_connector/_builder.py +425 -0
- sqlalchemy_connector/cli.py +200 -0
- sqlalchemy_connector/real_generator.py +2908 -0
- sqlalchemy_connector/templates/admin_cart_html_template.html +372 -0
- sqlalchemy_connector/templates/admin_html_template.html +364 -0
- sqlalchemy_connector/templates/admin_users_html_template.html +82 -0
- sqlalchemy_connector/templates/app_template.py +434 -0
- sqlalchemy_connector/templates/base_html_template.html +100 -0
- sqlalchemy_connector/templates/cart_html_template.html +103 -0
- sqlalchemy_connector/templates/catalog_html_template.html +98 -0
- sqlalchemy_connector/templates/checkout_html_template.html +70 -0
- sqlalchemy_connector/templates/dashboard_html_template.html +121 -0
- sqlalchemy_connector/templates/index_html_template.html +91 -0
- sqlalchemy_connector/templates/login_html_template.html +59 -0
- sqlalchemy_connector/templates/models_template.py +65 -0
- sqlalchemy_connector/templates/new_request_html_template.html +49 -0
- sqlalchemy_connector/templates/orders_html_template.html +65 -0
- sqlalchemy_connector/templates/product_form_html_template.html +142 -0
- sqlalchemy_connector/templates/product_html_template.html +131 -0
- sqlalchemy_connector/templates/profile_html_template.html +104 -0
- sqlalchemy_connector/templates/register_html_template.html +183 -0
- sqlalchemy_connector/templates/reviews_html_template.html +104 -0
- sqlalchemy_connector/templates/service_detail_html_template.html +67 -0
- sqlalchemy_connector/templates/service_form_html_template.html +86 -0
- sqlalchemy_connector/templates/services_html_template.html +47 -0
- sqlalchemy_connector/templates/slider_js_template.js +99 -0
- sqlalchemy_connector/templates/style_css_template.css +502 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Админ-панель{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block extra_head %}
|
|
6
|
+
<!-- Chart.js для статистики -->
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
8
|
+
<style>
|
|
9
|
+
/* Фикс прыганья модального окна */
|
|
10
|
+
body.modal-open {
|
|
11
|
+
padding-right: 0 !important;
|
|
12
|
+
overflow: auto !important;
|
|
13
|
+
}
|
|
14
|
+
.modal {
|
|
15
|
+
padding-right: 0 !important;
|
|
16
|
+
}
|
|
17
|
+
.modal-backdrop {
|
|
18
|
+
width: 100% !important;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
21
|
+
{% endblock %}
|
|
22
|
+
|
|
23
|
+
{% block content %}
|
|
24
|
+
<!-- Заголовок -->
|
|
25
|
+
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-2">
|
|
26
|
+
<h3 class="mb-0">👑 Админ-панель</h3>
|
|
27
|
+
<div class="d-flex gap-2">
|
|
28
|
+
<a href="{{ url_for('admin_users') }}" class="btn btn-outline-primary btn-sm">
|
|
29
|
+
👥 Пользователи
|
|
30
|
+
</a>
|
|
31
|
+
{{ADMIN_PRODUCT_BUTTONS}}
|
|
32
|
+
{{ADMIN_SERVICE_BUTTONS}}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Статистика -->
|
|
37
|
+
<div class="row g-3 mb-4">
|
|
38
|
+
{% for status, count in stats.items() %}
|
|
39
|
+
<div class="col-6 col-md-3">
|
|
40
|
+
<div class="card text-center border-0 shadow-sm h-100">
|
|
41
|
+
<div class="card-body py-3">
|
|
42
|
+
<div class="fs-2 fw-bold text-primary">{{ count }}</div>
|
|
43
|
+
<small class="text-muted">{{ status }}</small>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
{% endfor %}
|
|
48
|
+
<div class="col-6 col-md-3">
|
|
49
|
+
<div class="card text-center border-0 shadow-sm h-100">
|
|
50
|
+
<div class="card-body py-3">
|
|
51
|
+
<div class="fs-2 fw-bold text-dark">{{ total }}</div>
|
|
52
|
+
<small class="text-muted">Всего (с фильтром)</small>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<!-- График -->
|
|
59
|
+
<div class="row mb-4">
|
|
60
|
+
<div class="col-md-5">
|
|
61
|
+
<div class="card border-0 shadow-sm">
|
|
62
|
+
<div class="card-body">
|
|
63
|
+
<h6 class="card-title">Распределение по статусам</h6>
|
|
64
|
+
<canvas id="statusChart" height="200"></canvas>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="col-md-7">
|
|
69
|
+
<!-- Фильтры и поиск -->
|
|
70
|
+
<div class="card border-0 shadow-sm h-100">
|
|
71
|
+
<div class="card-body">
|
|
72
|
+
<h6 class="card-title">Фильтры</h6>
|
|
73
|
+
<form method="GET" action="{{ url_for('admin') }}" class="row g-2">
|
|
74
|
+
<div class="col-sm-6">
|
|
75
|
+
<input type="text" class="form-control form-control-sm" name="search"
|
|
76
|
+
placeholder="Поиск по описанию/услуге"
|
|
77
|
+
value="{{ search_query }}">
|
|
78
|
+
</div>
|
|
79
|
+
<div class="col-sm-6">
|
|
80
|
+
<select class="form-select form-select-sm" name="status">
|
|
81
|
+
<option value="all" {% if status_filter == 'all' %}selected{% endif %}>Все статусы</option>
|
|
82
|
+
{% for s in status_list %}
|
|
83
|
+
<option value="{{ s }}" {% if status_filter == s %}selected{% endif %}>{{ s }}</option>
|
|
84
|
+
{% endfor %}
|
|
85
|
+
</select>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="col-sm-4">
|
|
88
|
+
<input type="date" class="form-control form-control-sm" name="date_from"
|
|
89
|
+
value="{{ date_from }}" placeholder="Дата от">
|
|
90
|
+
</div>
|
|
91
|
+
<div class="col-sm-4">
|
|
92
|
+
<input type="date" class="form-control form-control-sm" name="date_to"
|
|
93
|
+
value="{{ date_to }}" placeholder="Дата до">
|
|
94
|
+
</div>
|
|
95
|
+
<div class="col-sm-4">
|
|
96
|
+
<select class="form-select form-select-sm" name="per_page">
|
|
97
|
+
{% for n in [10, 25, 50] %}
|
|
98
|
+
<option value="{{ n }}" {% if per_page == n %}selected{% endif %}>{{ n }} на стр.</option>
|
|
99
|
+
{% endfor %}
|
|
100
|
+
</select>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="col-12 d-flex gap-2">
|
|
103
|
+
<button type="submit" class="btn btn-primary btn-sm">🔍 Применить</button>
|
|
104
|
+
<a href="{{ url_for('admin') }}" class="btn btn-outline-secondary btn-sm">Сбросить</a>
|
|
105
|
+
</div>
|
|
106
|
+
</form>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<!-- Массовое изменение статуса -->
|
|
113
|
+
<form method="POST" action="{{ url_for('bulk_status') }}" id="bulkForm">
|
|
114
|
+
<div class="card border-0 shadow-sm mb-4">
|
|
115
|
+
<div class="card-header bg-dark text-white d-flex justify-content-between align-items-center flex-wrap gap-2">
|
|
116
|
+
<h5 class="mb-0">Все заявки</h5>
|
|
117
|
+
<div class="d-flex gap-2 align-items-center flex-wrap">
|
|
118
|
+
<span class="text-white-50 small">Выбрано: <span id="selectedCount">0</span></span>
|
|
119
|
+
<select class="form-select form-select-sm" name="bulk_status" style="width:auto;">
|
|
120
|
+
{% for s in status_list %}
|
|
121
|
+
<option value="{{ s }}">{{ s }}</option>
|
|
122
|
+
{% endfor %}
|
|
123
|
+
</select>
|
|
124
|
+
<button type="submit" class="btn btn-warning btn-sm"
|
|
125
|
+
onclick="return document.querySelectorAll('.req-check:checked').length > 0 || (alert('Выберите заявки') && false)">
|
|
126
|
+
Изменить статус
|
|
127
|
+
</button>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
<div class="card-body p-0">
|
|
131
|
+
<div class="table-responsive">
|
|
132
|
+
<table class="table table-hover align-middle mb-0">
|
|
133
|
+
<thead class="table-light">
|
|
134
|
+
<tr>
|
|
135
|
+
<th style="width:40px;">
|
|
136
|
+
<input type="checkbox" id="selectAll" class="form-check-input">
|
|
137
|
+
</th>
|
|
138
|
+
<th>#</th>
|
|
139
|
+
<th>Пользователь</th>
|
|
140
|
+
<th>Дата</th>
|
|
141
|
+
<th>Услуга</th>
|
|
142
|
+
{{ADMIN_REQUEST_TABLE_HEADERS}}
|
|
143
|
+
<th>Статус</th>
|
|
144
|
+
<th>Действия</th>
|
|
145
|
+
</tr>
|
|
146
|
+
</thead>
|
|
147
|
+
<tbody>
|
|
148
|
+
{% for req in requests %}
|
|
149
|
+
<tr>
|
|
150
|
+
<td>
|
|
151
|
+
<input type="checkbox" name="request_ids" value="{{ req.id }}"
|
|
152
|
+
class="form-check-input req-check">
|
|
153
|
+
</td>
|
|
154
|
+
<td class="text-muted">{{ req.id }}</td>
|
|
155
|
+
<td>
|
|
156
|
+
<strong>{{ req.user.login }}</strong>
|
|
157
|
+
{% if req.user.full_name is defined and req.user.full_name %}
|
|
158
|
+
<br><small class="text-muted">{{ req.user.full_name }}</small>
|
|
159
|
+
{% endif %}
|
|
160
|
+
</td>
|
|
161
|
+
<td>
|
|
162
|
+
{{ req.created_at.strftime('%d.%m.%Y') }}<br>
|
|
163
|
+
<small class="text-muted">{{ req.created_at.strftime('%H:%M') }}</small>
|
|
164
|
+
</td>
|
|
165
|
+
<td>
|
|
166
|
+
{% if req.service is defined and req.service %}
|
|
167
|
+
{{ req.service.name }}
|
|
168
|
+
{% elif req.service_name is defined and req.service_name %}
|
|
169
|
+
{{ req.service_name }}
|
|
170
|
+
{% else %}
|
|
171
|
+
<span class="text-muted">—</span>
|
|
172
|
+
{% endif %}
|
|
173
|
+
</td>
|
|
174
|
+
{{ADMIN_REQUEST_TABLE_CELLS}}
|
|
175
|
+
<td>
|
|
176
|
+
{% set status_colors = {'Новая': 'warning', 'В работе': 'info', 'Завершена': 'success'} %}
|
|
177
|
+
<span class="badge bg-{{ status_colors.get(req.status, 'secondary') }}
|
|
178
|
+
{% if req.status == 'Новая' %}text-dark{% endif %}">
|
|
179
|
+
{{ req.status }}
|
|
180
|
+
</span>
|
|
181
|
+
</td>
|
|
182
|
+
<td>
|
|
183
|
+
<div class="d-flex gap-1 flex-wrap">
|
|
184
|
+
<!-- Детали -->
|
|
185
|
+
<button type="button" class="btn btn-sm btn-outline-secondary"
|
|
186
|
+
data-bs-toggle="modal" data-bs-target="#details{{ req.id }}">
|
|
187
|
+
👁
|
|
188
|
+
</button>
|
|
189
|
+
<!-- Изменить статус -->
|
|
190
|
+
<button type="button" class="btn btn-sm btn-outline-primary"
|
|
191
|
+
data-bs-toggle="modal" data-bs-target="#statusModal{{ req.id }}">
|
|
192
|
+
✏️
|
|
193
|
+
</button>
|
|
194
|
+
</div>
|
|
195
|
+
</td>
|
|
196
|
+
</tr>
|
|
197
|
+
{% endfor %}
|
|
198
|
+
</tbody>
|
|
199
|
+
</table>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</form>
|
|
204
|
+
|
|
205
|
+
<!-- Кнопки удаления (вне формы bulkForm) -->
|
|
206
|
+
{% for req in requests %}
|
|
207
|
+
<form method="POST" action="{{ url_for('admin_delete_request', req_id=req.id) }}"
|
|
208
|
+
onsubmit="return confirm('Удалить заявку #{{ req.id }}?')" class="d-none" id="deleteForm{{ req.id }}">
|
|
209
|
+
</form>
|
|
210
|
+
{% endfor %}
|
|
211
|
+
|
|
212
|
+
<!-- Модальные окна вынесены за пределы таблицы и формы -->
|
|
213
|
+
{% for req in requests %}
|
|
214
|
+
<!-- Модал: детали -->
|
|
215
|
+
<div class="modal fade" id="details{{ req.id }}" tabindex="-1" aria-hidden="true">
|
|
216
|
+
<div class="modal-dialog modal-dialog-centered">
|
|
217
|
+
<div class="modal-content">
|
|
218
|
+
<div class="modal-header">
|
|
219
|
+
<h5 class="modal-title">Заявка #{{ req.id }}</h5>
|
|
220
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
221
|
+
</div>
|
|
222
|
+
<div class="modal-body">
|
|
223
|
+
<dl class="row mb-0">
|
|
224
|
+
<dt class="col-sm-5">Пользователь:</dt>
|
|
225
|
+
<dd class="col-sm-7">{{ req.user.login }}</dd>
|
|
226
|
+
<dt class="col-sm-5">Дата создания:</dt>
|
|
227
|
+
<dd class="col-sm-7">{{ req.created_at.strftime('%d.%m.%Y %H:%M') }}</dd>
|
|
228
|
+
<dt class="col-sm-5">Статус:</dt>
|
|
229
|
+
<dd class="col-sm-7">{{ req.status }}</dd>
|
|
230
|
+
{% if req.service is defined and req.service %}
|
|
231
|
+
<dt class="col-sm-5">Услуга:</dt>
|
|
232
|
+
<dd class="col-sm-7">{{ req.service.name }}</dd>
|
|
233
|
+
{% elif req.service_name is defined and req.service_name %}
|
|
234
|
+
<dt class="col-sm-5">Услуга:</dt>
|
|
235
|
+
<dd class="col-sm-7">{{ req.service_name }}</dd>
|
|
236
|
+
{% endif %}
|
|
237
|
+
{{REQUEST_MODAL_FIELDS}}
|
|
238
|
+
</dl>
|
|
239
|
+
{{ADMIN_PHOTO_MODAL}}
|
|
240
|
+
</div>
|
|
241
|
+
<div class="modal-footer">
|
|
242
|
+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
|
243
|
+
<button type="button" class="btn btn-danger btn-sm"
|
|
244
|
+
onclick="document.getElementById('deleteForm{{ req.id }}').submit()">
|
|
245
|
+
🗑 Удалить заявку
|
|
246
|
+
</button>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<!-- Модал: изменить статус -->
|
|
253
|
+
<div class="modal fade" id="statusModal{{ req.id }}" tabindex="-1" aria-hidden="true">
|
|
254
|
+
<div class="modal-dialog modal-sm modal-dialog-centered">
|
|
255
|
+
<div class="modal-content">
|
|
256
|
+
<div class="modal-header">
|
|
257
|
+
<h5 class="modal-title">Статус заявки #{{ req.id }}</h5>
|
|
258
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
259
|
+
</div>
|
|
260
|
+
<form method="POST" action="{{ url_for('change_status', req_id=req.id) }}"
|
|
261
|
+
enctype="multipart/form-data">
|
|
262
|
+
<div class="modal-body">
|
|
263
|
+
<label class="form-label">Новый статус:</label>
|
|
264
|
+
<select class="form-select" name="new_status">
|
|
265
|
+
{% for s in status_list %}
|
|
266
|
+
<option value="{{ s }}" {% if s == req.status %}selected{% endif %}>
|
|
267
|
+
{{ s }}
|
|
268
|
+
</option>
|
|
269
|
+
{% endfor %}
|
|
270
|
+
</select>
|
|
271
|
+
{{PHOTO_AFTER_FIELD}}
|
|
272
|
+
</div>
|
|
273
|
+
<div class="modal-footer">
|
|
274
|
+
<button type="submit" class="btn btn-primary btn-sm">Сохранить</button>
|
|
275
|
+
<button type="button" class="btn btn-secondary btn-sm"
|
|
276
|
+
data-bs-dismiss="modal">Отмена</button>
|
|
277
|
+
</div>
|
|
278
|
+
</form>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
{% endfor %}
|
|
283
|
+
|
|
284
|
+
<!-- Пагинация -->
|
|
285
|
+
{{ADMIN_SERVICE_MODAL}}
|
|
286
|
+
|
|
287
|
+
{% if total_pages > 1 %}
|
|
288
|
+
<nav aria-label="Пагинация">
|
|
289
|
+
<ul class="pagination justify-content-center flex-wrap">
|
|
290
|
+
<li class="page-item {% if page == 1 %}disabled{% endif %}">
|
|
291
|
+
<a class="page-link" href="{{ url_for('admin', page=page-1, per_page=per_page, status=status_filter, search=search_query, date_from=date_from, date_to=date_to) }}">
|
|
292
|
+
‹
|
|
293
|
+
</a>
|
|
294
|
+
</li>
|
|
295
|
+
{% for p in range(1, total_pages + 1) %}
|
|
296
|
+
{% if p == page or p == 1 or p == total_pages or (p >= page - 2 and p <= page + 2) %}
|
|
297
|
+
<li class="page-item {% if p == page %}active{% endif %}">
|
|
298
|
+
<a class="page-link" href="{{ url_for('admin', page=p, per_page=per_page, status=status_filter, search=search_query, date_from=date_from, date_to=date_to) }}">
|
|
299
|
+
{{ p }}
|
|
300
|
+
</a>
|
|
301
|
+
</li>
|
|
302
|
+
{% elif p == page - 3 or p == page + 3 %}
|
|
303
|
+
<li class="page-item disabled"><span class="page-link">…</span></li>
|
|
304
|
+
{% endif %}
|
|
305
|
+
{% endfor %}
|
|
306
|
+
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
|
|
307
|
+
<a class="page-link" href="{{ url_for('admin', page=page+1, per_page=per_page, status=status_filter, search=search_query, date_from=date_from, date_to=date_to) }}">
|
|
308
|
+
›
|
|
309
|
+
</a>
|
|
310
|
+
</li>
|
|
311
|
+
</ul>
|
|
312
|
+
</nav>
|
|
313
|
+
<p class="text-center text-muted small">
|
|
314
|
+
Показано {{ requests|length }} из {{ total }} заявок
|
|
315
|
+
</p>
|
|
316
|
+
{% endif %}
|
|
317
|
+
{% endblock %}
|
|
318
|
+
|
|
319
|
+
{% block extra_scripts %}
|
|
320
|
+
<script>
|
|
321
|
+
// ─── Выбор всех чекбоксов ────────────────────────────────────────
|
|
322
|
+
const selectAll = document.getElementById('selectAll');
|
|
323
|
+
const checkboxes = document.querySelectorAll('.req-check');
|
|
324
|
+
const countEl = document.getElementById('selectedCount');
|
|
325
|
+
|
|
326
|
+
function updateCount() {
|
|
327
|
+
const checked = document.querySelectorAll('.req-check:checked').length;
|
|
328
|
+
countEl.textContent = checked;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
selectAll.addEventListener('change', function() {
|
|
332
|
+
checkboxes.forEach(cb => { cb.checked = this.checked; });
|
|
333
|
+
updateCount();
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
checkboxes.forEach(cb => cb.addEventListener('change', updateCount));
|
|
337
|
+
|
|
338
|
+
// ─── График Chart.js ─────────────────────────────────────────────
|
|
339
|
+
const ctx = document.getElementById('statusChart');
|
|
340
|
+
if (ctx) {
|
|
341
|
+
const statsData = {{ stats | tojson }};
|
|
342
|
+
new Chart(ctx, {
|
|
343
|
+
type: 'doughnut',
|
|
344
|
+
data: {
|
|
345
|
+
labels: Object.keys(statsData),
|
|
346
|
+
datasets: [{
|
|
347
|
+
data: Object.values(statsData),
|
|
348
|
+
backgroundColor: [
|
|
349
|
+
'#ffc107', '#0dcaf0', '#198754', '#dc3545', '#6c757d',
|
|
350
|
+
'#0d6efd', '#fd7e14', '#20c997'
|
|
351
|
+
],
|
|
352
|
+
borderWidth: 2
|
|
353
|
+
}]
|
|
354
|
+
},
|
|
355
|
+
options: {
|
|
356
|
+
responsive: true,
|
|
357
|
+
plugins: {
|
|
358
|
+
legend: { position: 'bottom' }
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
</script>
|
|
364
|
+
{% endblock %}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Пользователи — Админ{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-2">
|
|
7
|
+
<h3 class="mb-0">👥 Управление пользователями</h3>
|
|
8
|
+
<a href="{{ url_for('admin') }}" class="btn btn-outline-secondary btn-sm">← Назад к заявкам</a>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div class="card border-0 shadow-sm">
|
|
12
|
+
<div class="card-body p-0">
|
|
13
|
+
<div class="table-responsive">
|
|
14
|
+
<table class="table table-hover align-middle mb-0">
|
|
15
|
+
<thead class="table-dark">
|
|
16
|
+
<tr>
|
|
17
|
+
<th>#</th>
|
|
18
|
+
<th>Логин</th>
|
|
19
|
+
<th>ФИО / Email</th>
|
|
20
|
+
<th>Роль</th>
|
|
21
|
+
<th>Статус</th>
|
|
22
|
+
<th>Дата регистрации</th>
|
|
23
|
+
<th>Заявок</th>
|
|
24
|
+
<th>Действия</th>
|
|
25
|
+
</tr>
|
|
26
|
+
</thead>
|
|
27
|
+
<tbody>
|
|
28
|
+
{% for u in users %}
|
|
29
|
+
<tr class="{% if u.is_blocked %}table-danger{% endif %}">
|
|
30
|
+
<td class="text-muted">{{ u.id }}</td>
|
|
31
|
+
<td>
|
|
32
|
+
<strong>{{ u.login }}</strong>
|
|
33
|
+
</td>
|
|
34
|
+
<td>
|
|
35
|
+
{% if u.full_name is defined and u.full_name %}
|
|
36
|
+
<div>{{ u.full_name }}</div>
|
|
37
|
+
{% endif %}
|
|
38
|
+
{% if u.email is defined and u.email %}
|
|
39
|
+
<small class="text-muted">{{ u.email }}</small>
|
|
40
|
+
{% endif %}
|
|
41
|
+
</td>
|
|
42
|
+
<td>
|
|
43
|
+
<span class="badge {% if u.role == 'admin' %}bg-danger{% else %}bg-secondary{% endif %}">
|
|
44
|
+
{{ 'Админ' if u.role == 'admin' else 'Пользователь' }}
|
|
45
|
+
</span>
|
|
46
|
+
</td>
|
|
47
|
+
<td>
|
|
48
|
+
{% if u.is_blocked %}
|
|
49
|
+
<span class="badge bg-danger">Заблокирован</span>
|
|
50
|
+
{% else %}
|
|
51
|
+
<span class="badge bg-success">Активен</span>
|
|
52
|
+
{% endif %}
|
|
53
|
+
</td>
|
|
54
|
+
<td>{{ u.created_at.strftime('%d.%m.%Y') }}</td>
|
|
55
|
+
<td>{{ u.requests|length }}</td>
|
|
56
|
+
<td>
|
|
57
|
+
{% if u.role != 'admin' %}
|
|
58
|
+
<div class="d-flex gap-1 flex-wrap">
|
|
59
|
+
<form method="POST" action="{{ url_for('block_user', user_id=u.id) }}" class="d-inline">
|
|
60
|
+
<button type="submit" class="btn btn-sm {% if u.is_blocked %}btn-outline-success{% else %}btn-outline-warning{% endif %}">
|
|
61
|
+
{% if u.is_blocked %}🔓 Разблокировать{% else %}🔒 Заблокировать{% endif %}
|
|
62
|
+
</button>
|
|
63
|
+
</form>
|
|
64
|
+
<form method="POST" action="{{ url_for('delete_user', user_id=u.id) }}"
|
|
65
|
+
onsubmit="return confirm('Удалить пользователя {{ u.login }}? Все его заявки тоже будут удалены.')"
|
|
66
|
+
class="d-inline">
|
|
67
|
+
<button type="submit" class="btn btn-sm btn-outline-danger">🗑 Удалить</button>
|
|
68
|
+
</form>
|
|
69
|
+
</div>
|
|
70
|
+
{% else %}
|
|
71
|
+
<span class="text-muted small">—</span>
|
|
72
|
+
{% endif %}
|
|
73
|
+
</td>
|
|
74
|
+
</tr>
|
|
75
|
+
{% endfor %}
|
|
76
|
+
</tbody>
|
|
77
|
+
</table>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<p class="text-muted small mt-2">Всего пользователей: {{ users|length }}</p>
|
|
82
|
+
{% endblock %}
|