264 lines
12 KiB
HTML
264 lines
12 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}{{ user.nick or user.id_user }}{% endblock %}
|
||
|
||
{% block content %}
|
||
<p class="text-sm text-slate-500"><a href="/users" class="hover:underline">← Пользователи</a></p>
|
||
|
||
<div class="mt-2 flex flex-wrap items-start justify-between gap-4">
|
||
<div>
|
||
<h1 class="text-2xl font-semibold">{{ user.nick or "Без ника" }}</h1>
|
||
<p class="mt-1 text-slate-600">
|
||
{% if user.username %}<span class="font-mono">@{{ user.username }}</span> · {% endif %}
|
||
<span class="font-mono">{{ user.id_user }}</span>
|
||
{% if user.first_name or user.last_name %}
|
||
· {{ user.first_name or "" }} {{ user.last_name or "" }}
|
||
{% endif %}
|
||
</p>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
{% if user.title %}
|
||
<span class="rounded bg-indigo-100 px-3 py-1 text-sm text-indigo-800">{{ user.title }}</span>
|
||
{% endif %}
|
||
{% if user.subscribed %}
|
||
<span class="rounded bg-emerald-100 px-3 py-1 text-sm text-emerald-800">подписан на рассылки</span>
|
||
{% else %}
|
||
<span class="rounded bg-slate-100 px-3 py-1 text-sm text-slate-500">без рассылок</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
{% if saved %}
|
||
<div class="mt-4 rounded-lg border border-emerald-200 bg-emerald-50 p-3 text-sm text-emerald-800">
|
||
{% if saved == 'nick' %}Ник обновлён.
|
||
{% elif saved == 'title' %}Звание обновлено.
|
||
{% elif saved == 'subscription' %}Подписка обновлена.
|
||
{% elif saved == 'balance' %}Баланс скорректирован.
|
||
{% else %}Изменения сохранены.{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if errors %}
|
||
<ul class="mt-4 list-disc rounded-lg border border-red-200 bg-red-50 p-3 pl-8 text-sm text-red-700">
|
||
{% for error in errors %}<li>{{ error }}</li>{% endfor %}
|
||
</ul>
|
||
{% endif %}
|
||
|
||
<div class="mt-6 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||
<div class="text-xs uppercase tracking-wide text-slate-500">Уровень</div>
|
||
<div class="mt-1 text-xl font-semibold">{{ user.lvl }}</div>
|
||
</div>
|
||
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||
<div class="text-xs uppercase tracking-wide text-slate-500">Опыт</div>
|
||
<div class="mt-1 text-xl font-semibold">{{ user.expr }}</div>
|
||
<div class="text-xs text-slate-500">серия: {{ user.expr_streak }}</div>
|
||
</div>
|
||
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||
<div class="text-xs uppercase tracking-wide text-slate-500">Баланс DE</div>
|
||
<div class="mt-1 text-xl font-semibold">{{ user.balance }}</div>
|
||
</div>
|
||
{% if eggs_visible %}
|
||
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||
<div class="text-xs uppercase tracking-wide text-slate-500">Яички</div>
|
||
<div class="mt-1 text-xl font-semibold">{{ user.eggs }}</div>
|
||
</div>
|
||
{% endif %}
|
||
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||
<div class="text-xs uppercase tracking-wide text-slate-500">Сообщения</div>
|
||
<div class="mt-1 text-xl font-semibold">{{ user.msg_count }}</div>
|
||
<div class="text-xs text-slate-500">
|
||
последняя активность: {{ user.last_message_at.strftime("%d.%m.%Y") if user.last_message_at else "—" }}
|
||
</div>
|
||
</div>
|
||
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||
<div class="text-xs uppercase tracking-wide text-slate-500">Очищения</div>
|
||
<div class="mt-1 text-xl font-semibold">{{ user.purification_count }}</div>
|
||
<div class="text-xs text-slate-500">
|
||
последнее: {{ user.last_purification_at.strftime("%d.%m.%Y %H:%M") if user.last_purification_at else "—" }}
|
||
</div>
|
||
</div>
|
||
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||
<div class="text-xs uppercase tracking-wide text-slate-500">В клане с</div>
|
||
<div class="mt-1 text-xl font-semibold">
|
||
{{ user.dt_create.strftime("%d.%m.%Y") if user.dt_create else "—" }}
|
||
</div>
|
||
</div>
|
||
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||
<div class="text-xs uppercase tracking-wide text-slate-500">Telegram-ID</div>
|
||
<div class="mt-1 font-mono text-sm">{{ user.id_user }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-8">
|
||
<h2 class="text-lg font-medium">Управление</h2>
|
||
<div class="mt-3 grid gap-4 lg:grid-cols-2">
|
||
<form method="post" action="/users/{{ user.id_user }}/nick" class="rounded-lg border border-slate-200 bg-white p-4">
|
||
<input type="hidden" name="csrf_token" value="{{ request.session.get('csrf', '') }}" />
|
||
<label for="nick" class="text-sm font-medium text-slate-700">Ник</label>
|
||
<div class="mt-2 flex gap-2">
|
||
<input
|
||
id="nick"
|
||
name="nick"
|
||
type="text"
|
||
value="{{ user.nick or '' }}"
|
||
maxlength="{{ max_nick_length }}"
|
||
class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-sky-500 focus:outline-none"
|
||
/>
|
||
<button type="submit" class="rounded-lg bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-700">
|
||
Сохранить
|
||
</button>
|
||
</div>
|
||
</form>
|
||
|
||
<form method="post" action="/users/{{ user.id_user }}/title" class="rounded-lg border border-slate-200 bg-white p-4">
|
||
<input type="hidden" name="csrf_token" value="{{ request.session.get('csrf', '') }}" />
|
||
<label for="title_id" class="text-sm font-medium text-slate-700">Звание</label>
|
||
<div class="mt-2 flex gap-2">
|
||
<select
|
||
id="title_id"
|
||
name="title_id"
|
||
class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-sky-500 focus:outline-none"
|
||
>
|
||
{% for title in titles %}
|
||
<option value="{{ title.id_title }}" {% if title.id_title == user.id_title %}selected{% endif %}>
|
||
{{ title.title }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
<button type="submit" class="rounded-lg bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-700">
|
||
Сохранить
|
||
</button>
|
||
</div>
|
||
</form>
|
||
|
||
<form method="post" action="/users/{{ user.id_user }}/subscription" class="rounded-lg border border-slate-200 bg-white p-4">
|
||
<input type="hidden" name="csrf_token" value="{{ request.session.get('csrf', '') }}" />
|
||
<input type="hidden" name="subscribed" value="{{ '0' if user.subscribed else '1' }}" />
|
||
<div class="text-sm font-medium text-slate-700">Рассылки</div>
|
||
<p class="mt-1 text-sm text-slate-600">
|
||
{% if user.subscribed %}Сейчас подписан.{% else %}Сейчас не подписан.{% endif %}
|
||
</p>
|
||
<button type="submit" class="mt-2 rounded-lg border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">
|
||
{% if user.subscribed %}Отписать{% else %}Подписать{% endif %}
|
||
</button>
|
||
</form>
|
||
|
||
<form method="post" action="/users/{{ user.id_user }}/balance" class="rounded-lg border border-slate-200 bg-white p-4">
|
||
<input type="hidden" name="csrf_token" value="{{ request.session.get('csrf', '') }}" />
|
||
<div class="text-sm font-medium text-slate-700">Корректировка баланса</div>
|
||
<div class="mt-2 flex gap-2">
|
||
<input
|
||
name="amount"
|
||
type="number"
|
||
step="1"
|
||
placeholder="+50 или -20"
|
||
class="w-32 rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-sky-500 focus:outline-none"
|
||
/>
|
||
<input
|
||
name="reason"
|
||
type="text"
|
||
maxlength="{{ max_comment_length }}"
|
||
placeholder="Причина"
|
||
class="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-sky-500 focus:outline-none"
|
||
/>
|
||
<button type="submit" class="rounded-lg bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-700">
|
||
Провести
|
||
</button>
|
||
</div>
|
||
<p class="mt-1 text-xs text-slate-500">Запись уйдёт в журнал денег с действием «Корректировка админом».</p>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
{% if can_kick %}
|
||
<div class="mt-6 rounded-lg border border-red-200 bg-red-50 p-4">
|
||
<h2 class="text-lg font-medium text-red-800">Исключить из чата</h2>
|
||
<p class="mt-1 text-sm text-red-700">
|
||
Бот заблокирует пользователя и сразу разблокирует (чтобы мог вернуться),
|
||
а данные из бота будут удалены.
|
||
</p>
|
||
<form
|
||
method="post"
|
||
action="/users/{{ user.id_user }}/kick"
|
||
class="mt-3"
|
||
onsubmit="return confirm('Исключить {{ user.nick or user.id_user }} из чата?');"
|
||
>
|
||
<input type="hidden" name="csrf_token" value="{{ request.session.get('csrf', '') }}" />
|
||
<button type="submit" class="rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700">
|
||
Выгнать из чата
|
||
</button>
|
||
</form>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="mt-8">
|
||
<h2 class="text-lg font-medium">История изменений</h2>
|
||
{% if history %}
|
||
<div class="mt-3 overflow-x-auto rounded-lg border border-slate-200 bg-white">
|
||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||
<thead class="bg-slate-50 text-left text-slate-600">
|
||
<tr>
|
||
<th class="px-4 py-3 font-medium">Когда</th>
|
||
<th class="px-4 py-3 font-medium">Что</th>
|
||
<th class="px-4 py-3 font-medium">Было → стало</th>
|
||
<th class="px-4 py-3 font-medium">Комментарий</th>
|
||
<th class="px-4 py-3 font-medium">Источник</th>
|
||
<th class="px-4 py-3 font-medium">Админ</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-slate-100">
|
||
{% set field_labels = {"nick": "Ник", "title": "Звание", "subscription": "Подписка", "balance": "Баланс"} %}
|
||
{% for item in history %}
|
||
<tr>
|
||
<td class="px-4 py-3 text-slate-600">{{ item.changed_at.strftime("%d.%m.%Y %H:%M") }}</td>
|
||
<td class="px-4 py-3">{{ field_labels.get(item.field, item.field) }}</td>
|
||
<td class="px-4 py-3 text-slate-700">{{ item.old_value or "—" }} → {{ item.new_value or "—" }}</td>
|
||
<td class="px-4 py-3 text-slate-600">{{ item.comment or "—" }}</td>
|
||
<td class="px-4 py-3 text-slate-600">
|
||
{% if item.source == 'user' %}Пользователь{% else %}Админ{% endif %}
|
||
</td>
|
||
<td class="px-4 py-3 font-mono text-xs text-slate-500">{{ item.editor_id or "—" }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% else %}
|
||
<p class="mt-2 text-sm text-slate-500">Ручных изменений ещё не было.</p>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="mt-8">
|
||
<h2 class="text-lg font-medium">Последние операции по балансу</h2>
|
||
{% if money %}
|
||
<div class="mt-3 overflow-x-auto rounded-lg border border-slate-200 bg-white">
|
||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||
<thead class="bg-slate-50 text-left text-slate-600">
|
||
<tr>
|
||
<th class="px-4 py-3 font-medium">Когда</th>
|
||
<th class="px-4 py-3 font-medium">Сумма</th>
|
||
<th class="px-4 py-3 font-medium">Действие</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-slate-100">
|
||
{% for item in money %}
|
||
<tr>
|
||
<td class="px-4 py-3 text-slate-600">
|
||
{{ item.dt_action.strftime("%d.%m.%Y %H:%M") if item.dt_action else "—" }}
|
||
</td>
|
||
<td class="px-4 py-3 font-medium {% if item.amount < 0 %}text-red-600{% else %}text-emerald-700{% endif %}">
|
||
{{ "%+d" | format(item.amount) }}
|
||
</td>
|
||
<td class="px-4 py-3 text-slate-700">{{ item.action or "—" }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% else %}
|
||
<p class="mt-2 text-sm text-slate-500">Операций по балансу не было.</p>
|
||
{% endif %}
|
||
</div>
|
||
{% endblock %}
|