добавил возможность администраторам создавать игроков, тренеров и судей вручную

This commit is contained in:
2026-08-04 10:49:03 +03:00
parent 2c2d9b9a7c
commit adea3a8bd6
10 changed files with 891 additions and 3 deletions

View File

@@ -149,6 +149,9 @@
<form method="get" action="/admin/db/coaches" class="search-form">
<input type="text" name="q" value="{{ q }}" placeholder="Поиск по ФИО или external_id">
<button type="submit" class="btn btn-primary">Найти</button>
{% if request.state.current_user and request.state.current_user.role == "admin" %}
<a href="/admin/db/create?entity=coach" class="btn btn-primary"> Создать тренера</a>
{% endif %}
<a href="/admin/db" class="btn btn-secondary">Назад</a>
</form>
</div>

View File

@@ -0,0 +1,459 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Создание записи</title>
<link rel="icon" href="/static/smith.ico" type="image/x-icon">
<style>
:root {
--bg: #0f1115;
--panel: #171a21;
--panel-2: #1d222b;
--border: #2b3240;
--text: #e8ecf3;
--muted: #9aa4b2;
--accent: #4f8cff;
--accent-hover: #3e78e6;
--success: #44cf88;
--danger: #ff6b78;
--shadow: 0 10px 30px rgba(0, 0, 0, .35);
--radius: 16px;
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: Arial, sans-serif;
}
.page {
max-width: 1180px;
margin: 0 auto;
padding: 24px;
}
.panel {
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 22px;
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.title {
font-size: 26px;
font-weight: 700;
margin-bottom: 6px;
}
.subtitle {
color: var(--muted);
line-height: 1.45;
max-width: 760px;
}
.entity-tabs {
display: grid;
grid-template-columns: repeat(3, minmax(150px, 1fr));
gap: 10px;
margin-bottom: 22px;
}
.entity-tab {
min-height: 52px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--border);
background: var(--panel-2);
border-radius: 12px;
color: var(--text);
text-decoration: none;
font-weight: 700;
}
.entity-tab.active {
border-color: var(--accent);
background: rgba(79, 140, 255, .14);
color: #cfe0ff;
}
.notice {
padding: 14px 16px;
border-radius: 12px;
margin-bottom: 18px;
line-height: 1.45;
}
.notice.error {
border: 1px solid rgba(255, 107, 120, .5);
background: rgba(255, 107, 120, .1);
color: #ffd2d6;
}
.notice.success {
border: 1px solid rgba(68, 207, 136, .5);
background: rgba(68, 207, 136, .1);
color: #caffdf;
}
.notice a { color: inherit; font-weight: 700; }
.section {
margin-top: 18px;
padding-top: 18px;
border-top: 1px solid var(--border);
}
.section:first-of-type {
margin-top: 0;
padding-top: 0;
border-top: 0;
}
.section-title {
font-size: 17px;
font-weight: 700;
margin-bottom: 14px;
}
.section-hint {
color: var(--muted);
font-size: 13px;
margin-top: -7px;
margin-bottom: 14px;
line-height: 1.45;
}
.form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 15px;
}
.form-grid.stats {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.form-group {
display: flex;
flex-direction: column;
gap: 7px;
}
.form-group.full { grid-column: 1 / -1; }
label {
color: var(--muted);
font-size: 13px;
font-weight: 600;
}
.required::after {
content: " *";
color: var(--danger);
}
input[type="text"], input[type="date"], input[type="number"], select {
width: 100%;
min-height: 43px;
padding: 0 12px;
border-radius: 10px;
border: 1px solid var(--border);
background: var(--panel-2);
color: var(--text);
outline: none;
}
input:focus, select:focus { border-color: var(--accent); }
.checkbox-grid {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.checkbox-card {
min-height: 44px;
display: inline-flex;
align-items: center;
gap: 9px;
padding: 0 14px;
border: 1px solid var(--border);
border-radius: 10px;
background: var(--panel-2);
color: var(--text);
}
.checkbox-card input {
width: 18px;
height: 18px;
accent-color: var(--accent);
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 24px;
}
.btn {
min-height: 43px;
padding: 0 15px;
border-radius: 10px;
border: 0;
font-weight: 700;
text-decoration: none;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
.btn-primary { background: var(--accent); color: white; }
.btn-primary:hover { background: var(--accent-hover); }
.btn-secondary { background: transparent; color: var(--text); border: 1px solid var(--border); }
.empty-teams {
padding: 14px;
border: 1px dashed var(--danger);
border-radius: 10px;
color: #ffd2d6;
background: rgba(255, 107, 120, .08);
}
@media (max-width: 760px) {
.page { padding: 12px; }
.panel { padding: 16px; }
.entity-tabs, .form-grid, .form-grid.stats { grid-template-columns: 1fr; }
.form-group.full { grid-column: auto; }
}
</style>
</head>
<body>
<div class="page">
<div class="panel">
<div class="header">
<div>
<div class="title">Создание записи</div>
<div class="subtitle">Выберите тип человека. Форма покажет только те поля, которые используются для этой роли в базе WFL.</div>
</div>
<a href="/admin/db" class="btn btn-secondary">Назад к разделам</a>
</div>
<div class="entity-tabs">
<a href="/admin/db/create?entity=player" class="entity-tab {% if entity_type == 'player' %}active{% endif %}">⚽ Игрок</a>
<a href="/admin/db/create?entity=coach" class="entity-tab {% if entity_type == 'coach' %}active{% endif %}">📋 Тренер</a>
<a href="/admin/db/create?entity=referee" class="entity-tab {% if entity_type == 'referee' %}active{% endif %}">🟨 Судья</a>
</div>
{% if error %}
<div class="notice error">{{ error }}</div>
{% endif %}
{% if created_id %}
<div class="notice success">
Запись успешно создана. ID: <b>{{ created_id }}</b>.
{% if entity_type == 'player' %}
<a href="/admin/db/players/{{ created_id }}/edit">Открыть карточку игрока</a>
{% elif entity_type == 'coach' %}
<a href="/admin/db/coaches/{{ created_id }}/edit">Открыть карточку тренера</a>
{% else %}
<a href="/admin/db/referees/{{ created_id }}/edit">Открыть карточку судьи</a>
{% endif %}
</div>
{% endif %}
<form method="post" action="/admin/db/create">
<input type="hidden" name="entity_type" value="{{ entity_type }}">
{% if entity_type == 'player' %}
<div class="section">
<div class="section-title">Основные данные игрока</div>
<div class="form-grid">
<div class="form-group">
<label class="required">Команда</label>
{% if teams %}
<select name="team_id" required>
<option value="">Выберите команду</option>
{% for team in teams %}
<option value="{{ team.id }}" {% if form_values.get('team_id') == team.id|string %}selected{% endif %}>
{{ team.name }}{% if team.city %} — {{ team.city }}{% endif %}
</option>
{% endfor %}
</select>
{% else %}
<div class="empty-teams">В базе нет команд. Сначала добавьте или загрузите команды.</div>
{% endif %}
</div>
<div class="form-group">
<label>External ID</label>
<input type="text" name="external_id" value="{{ form_values.get('external_id', '') }}" placeholder="ID игрока на сайте РФС">
</div>
<div class="form-group full">
<label class="required">Полное имя / ФИО</label>
<input type="text" name="full_name" value="{{ form_values.get('full_name', '') }}" required placeholder="Фамилия Имя">
</div>
<div class="form-group">
<label>Имя</label>
<input type="text" name="first_name" value="{{ form_values.get('first_name', '') }}">
</div>
<div class="form-group">
<label>Фамилия</label>
<input type="text" name="last_name" value="{{ form_values.get('last_name', '') }}">
</div>
<div class="form-group">
<label>Игровой номер</label>
<input type="text" name="number" value="{{ form_values.get('number', '') }}" placeholder="Например, 10">
</div>
<div class="form-group">
<label class="required">Амплуа</label>
<input type="text" name="position" list="playerPositions" value="{{ form_values.get('position', '') }}" required placeholder="Выберите или введите">
<datalist id="playerPositions">
<option value="Вратарь">
<option value="Защитник">
<option value="Полузащитник">
<option value="Нападающий">
</datalist>
</div>
<div class="form-group">
<label>Дата рождения</label>
<input type="date" name="birth_date" value="{{ form_values.get('birth_date', '') }}">
</div>
<div class="form-group">
<label>Рост, см</label>
<input type="number" name="height_cm" min="1" value="{{ form_values.get('height_cm', '') }}">
</div>
<div class="form-group">
<label>Вес, кг</label>
<input type="number" name="weight_kg" min="1" value="{{ form_values.get('weight_kg', '') }}">
</div>
</div>
</div>
<div class="section">
<div class="section-title">Медиа</div>
<div class="section-hint">Можно указать имя файла или полный путь — формат остаётся таким же, как в существующих карточках.</div>
<div class="form-grid">
<div class="form-group">
<label>Фото</label>
<input type="text" name="photo" value="{{ form_values.get('photo', '') }}" placeholder="Путь или имя файла фотографии">
</div>
<div class="form-group">
<label>Видео</label>
<input type="text" name="video" value="{{ form_values.get('video', '') }}" placeholder="Путь или имя видео">
</div>
</div>
</div>
<div class="section">
<div class="section-title">Статистика</div>
<div class="section-hint">Для нового игрока можно оставить нули. При следующем парсинге статистика обновится по External ID.</div>
<div class="form-grid stats">
<div class="form-group"><label>Игры</label><input type="number" name="games" min="0" value="{{ form_values.get('games', '0') }}"></div>
<div class="form-group"><label>Голы</label><input type="number" name="goals" min="0" value="{{ form_values.get('goals', '0') }}"></div>
<div class="form-group"><label>Голы с пенальти</label><input type="number" name="penaltys" min="0" value="{{ form_values.get('penaltys', '0') }}"></div>
<div class="form-group"><label>Передачи</label><input type="number" name="assists" min="0" value="{{ form_values.get('assists', '0') }}"></div>
<div class="form-group"><label>Жёлтые карточки</label><input type="number" name="yellows" min="0" value="{{ form_values.get('yellows', '0') }}"></div>
<div class="form-group"><label>Красные карточки</label><input type="number" name="reds" min="0" value="{{ form_values.get('reds', '0') }}"></div>
</div>
</div>
<div class="section">
<div class="checkbox-grid">
<label class="checkbox-card"><input type="checkbox" name="is_active" {% if not form_values or form_values.get('is_active') %}checked{% endif %}> Активный игрок</label>
<label class="checkbox-card"><input type="checkbox" name="photo_enabled" {% if form_values.get('photo_enabled') %}checked{% endif %}> Использовать фотографию в титрах</label>
</div>
</div>
{% elif entity_type == 'coach' %}
<div class="section">
<div class="section-title">Данные тренера</div>
<div class="form-grid">
<div class="form-group">
<label class="required">Команда</label>
{% if teams %}
<select name="team_id" required>
<option value="">Выберите команду</option>
{% for team in teams %}
<option value="{{ team.id }}" {% if form_values.get('team_id') == team.id|string %}selected{% endif %}>
{{ team.name }}{% if team.city %} — {{ team.city }}{% endif %}
</option>
{% endfor %}
</select>
{% else %}
<div class="empty-teams">В базе нет команд. Сначала добавьте или загрузите команды.</div>
{% endif %}
</div>
<div class="form-group">
<label>External ID</label>
<input type="text" name="external_id" value="{{ form_values.get('external_id', '') }}" placeholder="ID тренера на сайте РФС">
</div>
<div class="form-group full">
<label class="required">Полное имя / ФИО</label>
<input type="text" name="full_name" value="{{ form_values.get('full_name', '') }}" required placeholder="Фамилия Имя">
</div>
<div class="form-group">
<label>Имя</label>
<input type="text" name="first_name" value="{{ form_values.get('first_name', '') }}">
</div>
<div class="form-group">
<label>Фамилия</label>
<input type="text" name="last_name" value="{{ form_values.get('last_name', '') }}">
</div>
<div class="form-group">
<label class="required">Должность / амплуа</label>
<input type="text" name="role" list="coachRoles" value="{{ form_values.get('role', '') }}" required placeholder="Например, главный тренер">
<datalist id="coachRoles">
<option value="Главный тренер">
<option value="Старший тренер">
<option value="Тренер">
<option value="Тренер вратарей">
<option value="Тренер по физической подготовке">
</datalist>
</div>
<div class="form-group">
<label>Дата рождения</label>
<input type="date" name="birth_date" value="{{ form_values.get('birth_date', '') }}">
</div>
</div>
</div>
<div class="section">
<div class="checkbox-grid">
<label class="checkbox-card"><input type="checkbox" name="is_active" {% if not form_values or form_values.get('is_active') %}checked{% endif %}> Активный тренер</label>
</div>
</div>
{% else %}
<div class="section">
<div class="section-title">Данные судьи</div>
<div class="section-hint">Конкретная роль — главный судья, помощник, резервный — назначается отдельно в карточке матча.</div>
<div class="form-grid">
<div class="form-group full">
<label class="required">Полное имя / ФИО</label>
<input type="text" name="full_name" value="{{ form_values.get('full_name', '') }}" required placeholder="Фамилия Имя Отчество">
</div>
<div class="form-group">
<label>Фамилия</label>
<input type="text" name="last_name" value="{{ form_values.get('last_name', '') }}">
</div>
<div class="form-group">
<label>Имя</label>
<input type="text" name="first_name" value="{{ form_values.get('first_name', '') }}">
</div>
<div class="form-group">
<label>Отчество</label>
<input type="text" name="middle_name" value="{{ form_values.get('middle_name', '') }}">
</div>
<div class="form-group">
<label>Город</label>
<input type="text" name="city" value="{{ form_values.get('city', '') }}">
</div>
<div class="form-group full">
<label>External ID</label>
<input type="text" name="external_id" value="{{ form_values.get('external_id', '') }}" placeholder="ID судьи во внешнем источнике">
</div>
</div>
</div>
<div class="section">
<div class="checkbox-grid">
<label class="checkbox-card"><input type="checkbox" name="is_active" {% if not form_values or form_values.get('is_active') %}checked{% endif %}> Активный судья</label>
</div>
</div>
{% endif %}
<div class="actions">
<button type="submit" class="btn btn-primary" {% if entity_type in ['player', 'coach'] and not teams %}disabled{% endif %}>Создать запись</button>
{% if entity_type == 'player' %}
<a href="/admin/db/players" class="btn btn-secondary">Список игроков</a>
{% elif entity_type == 'coach' %}
<a href="/admin/db/coaches" class="btn btn-secondary">Список тренеров</a>
{% else %}
<a href="/admin/db/referees" class="btn btn-secondary">Список судей</a>
{% endif %}
</div>
</form>
</div>
</div>
</body>
</html>

View File

@@ -520,6 +520,9 @@
<div class="title">Редактирование базы</div>
<div class="tabs">
{% if current_user and current_user.role == "admin" %}
<a class="tab-link" href="/admin/db/create?entity=player"> Создать запись</a>
{% endif %}
<a class="tab-link" href="/admin/db/players">Игроки</a>
<a class="tab-link" href="/admin/db/referees">Судьи</a>
<a class="tab-link" href="/admin/db/teams">Команды</a>

View File

@@ -190,6 +190,9 @@
<input type="hidden" name="direction" value="{{ direction }}">
<input type="text" name="q" value="{{ q }}" placeholder="Поиск по ФИО, external_id или команде">
<button type="submit" class="btn btn-primary">Найти</button>
{% if request.state.current_user and request.state.current_user.role == "admin" %}
<a href="/admin/db/create?entity=player" class="btn btn-primary"> Создать игрока</a>
{% endif %}
<a href="/admin/db" class="btn btn-secondary">Назад</a>
</form>
</div>

View File

@@ -37,6 +37,9 @@
<form method="get" action="/admin/db/referees" class="search-form">
<input type="text" name="q" value="{{ q }}" placeholder="Поиск по ФИО или external_id">
<button type="submit" class="btn btn-primary">Найти</button>
{% if request.state.current_user and request.state.current_user.role == "admin" %}
<a href="/admin/db/create?entity=referee" class="btn btn-primary"> Создать судью</a>
{% endif %}
<a href="/admin/db" class="btn btn-secondary">Назад</a>
</form>
</div>