feat: apply new Мои Товары design system across all templates
Replace Pico CSS with custom design: dark sidebar layout, Golos Text + JetBrains Mono fonts, orange accent (#FF5500), new component classes (cards, tables, buttons, tags, toggles, alerts, tabs, login split-panel). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,147 +1,147 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}API Логи — ЭВОСИНК{% endblock %}
|
||||
{% block title %}API Логи — Мои Товары{% endblock %}
|
||||
{% block page_title %}API Логи{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-between align-center mb-3">
|
||||
<h1 style="font-size:1.3rem; margin:0;"><i class="bi bi-journal-text me-2"></i>API Логи</h1>
|
||||
<span class="text-muted small">Найдено: {{ total }}</span>
|
||||
<div class="pg-title">API Логи</div>
|
||||
<div class="pg-sub">Журнал всех исходящих запросов · Найдено: {{ total }}</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="card" style="margin-bottom:14px;padding:14px 20px;">
|
||||
<form method="get" action="/admin/logs" style="display:flex;flex-wrap:wrap;gap:8px;align-items:center;">
|
||||
<select class="inp" name="service" style="width:auto;">
|
||||
<option value="" {% if not filter_service %}selected{% endif %}>Все сервисы</option>
|
||||
<option value="evotor" {% if filter_service == 'evotor' %}selected{% endif %}>Эвотор</option>
|
||||
<option value="vk" {% if filter_service == 'vk' %}selected{% endif %}>ВКонтакте</option>
|
||||
<option value="other" {% if filter_service == 'other' %}selected{% endif %}>Другое</option>
|
||||
</select>
|
||||
<select class="inp" name="method" style="width:auto;">
|
||||
<option value="" {% if not filter_method %}selected{% endif %}>Все методы</option>
|
||||
<option value="GET" {% if filter_method == 'GET' %}selected{% endif %}>GET</option>
|
||||
<option value="POST" {% if filter_method == 'POST' %}selected{% endif %}>POST</option>
|
||||
</select>
|
||||
<select class="inp" name="status" style="width:auto;">
|
||||
<option value="" {% if not filter_status %}selected{% endif %}>Любой статус</option>
|
||||
<option value="ok" {% if filter_status == 'ok' %}selected{% endif %}>2xx / 3xx</option>
|
||||
<option value="error" {% if filter_status == 'error' %}selected{% endif %}>4xx / 5xx</option>
|
||||
<option value="200" {% if filter_status == '200' %}selected{% endif %}>200</option>
|
||||
<option value="401" {% if filter_status == '401' %}selected{% endif %}>401</option>
|
||||
<option value="403" {% if filter_status == '403' %}selected{% endif %}>403</option>
|
||||
<option value="429" {% if filter_status == '429' %}selected{% endif %}>429</option>
|
||||
<option value="500" {% if filter_status == '500' %}selected{% endif %}>500</option>
|
||||
</select>
|
||||
<select class="inp" name="hours" style="width:auto;">
|
||||
<option value="1" {% if filter_hours == 1 %}selected{% endif %}>Последний час</option>
|
||||
<option value="6" {% if filter_hours == 6 %}selected{% endif %}>6 часов</option>
|
||||
<option value="24" {% if filter_hours == 24 %}selected{% endif %}>24 часа</option>
|
||||
<option value="168" {% if filter_hours == 168 or (not filter_hours) %}selected{% endif %}>7 дней</option>
|
||||
<option value="720" {% if filter_hours == 720 %}selected{% endif %}>30 дней</option>
|
||||
</select>
|
||||
<input class="inp" type="search" name="q" value="{{ filter_q }}"
|
||||
placeholder="URL или тело ответа…" style="flex:1;min-width:160px;">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Применить</button>
|
||||
{% if filter_service or filter_method or filter_status or filter_q or filter_hours != 24 %}
|
||||
<a href="/admin/logs" class="btn btn-outline btn-sm">Сбросить</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{# ── filters ── #}
|
||||
<form method="get" action="/admin/logs" class="mb-3" style="display:flex; flex-wrap:wrap; gap:0.5rem; align-items:center;">
|
||||
<select name="service" style="width:auto;">
|
||||
<option value="" {% if not filter_service %}selected{% endif %}>Все сервисы</option>
|
||||
<option value="evotor" {% if filter_service == 'evotor' %}selected{% endif %}>Эвотор</option>
|
||||
<option value="vk" {% if filter_service == 'vk' %}selected{% endif %}>ВКонтакте</option>
|
||||
<option value="other" {% if filter_service == 'other' %}selected{% endif %}>Другое</option>
|
||||
</select>
|
||||
<select name="method" style="width:auto;">
|
||||
<option value="" {% if not filter_method %}selected{% endif %}>Все методы</option>
|
||||
<option value="GET" {% if filter_method == 'GET' %}selected{% endif %}>GET</option>
|
||||
<option value="POST" {% if filter_method == 'POST' %}selected{% endif %}>POST</option>
|
||||
</select>
|
||||
<select name="status" style="width:auto;">
|
||||
<option value="" {% if not filter_status %}selected{% endif %}>Любой статус</option>
|
||||
<option value="ok" {% if filter_status == 'ok' %}selected{% endif %}>2xx / 3xx</option>
|
||||
<option value="error" {% if filter_status == 'error' %}selected{% endif %}>4xx / 5xx</option>
|
||||
<option value="200" {% if filter_status == '200' %}selected{% endif %}>200</option>
|
||||
<option value="401" {% if filter_status == '401' %}selected{% endif %}>401</option>
|
||||
<option value="403" {% if filter_status == '403' %}selected{% endif %}>403</option>
|
||||
<option value="429" {% if filter_status == '429' %}selected{% endif %}>429</option>
|
||||
<option value="500" {% if filter_status == '500' %}selected{% endif %}>500</option>
|
||||
</select>
|
||||
<select name="hours" style="width:auto;">
|
||||
<option value="1" {% if filter_hours == 1 %}selected{% endif %}>Последний час</option>
|
||||
<option value="6" {% if filter_hours == 6 %}selected{% endif %}>6 часов</option>
|
||||
<option value="24" {% if filter_hours == 24 %}selected{% endif %}>24 часа</option>
|
||||
<option value="168" {% if filter_hours == 168 or (not filter_hours) %}selected{% endif %}>7 дней</option>
|
||||
<option value="720" {% if filter_hours == 720 %}selected{% endif %}>30 дней</option>
|
||||
</select>
|
||||
<input type="search" name="q" value="{{ filter_q }}" placeholder="URL или тело ответа…" style="flex:1; min-width:160px;">
|
||||
<button type="submit">Применить</button>
|
||||
{% if filter_service or filter_method or filter_status or filter_q or filter_hours != 24 %}
|
||||
<a href="/admin/logs" role="button" class="outline secondary">Сбросить</a>
|
||||
<div class="card" style="padding:0;">
|
||||
{% if logs %}
|
||||
<div class="table-wrap">
|
||||
<table class="tbl" style="font-size:12px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:150px;">Время</th>
|
||||
<th style="width:80px;">Сервис</th>
|
||||
<th style="width:50px;">Метод</th>
|
||||
<th style="width:60px;">Статус</th>
|
||||
<th style="width:70px;">Мс</th>
|
||||
<th>URL</th>
|
||||
<th style="width:32px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in logs %}
|
||||
{% set is_error = log.response_status and log.response_status >= 400 %}
|
||||
<tr style="cursor:pointer;" onclick="toggleDetail({{ log.id }})">
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ log.created_at | datefmt }}</span></td>
|
||||
<td>
|
||||
{% if log.service == 'evotor' %}
|
||||
<span class="tag tag-bl" style="font-size:10px;padding:2px 6px;">{{ log.service }}</span>
|
||||
{% elif log.service == 'vk' %}
|
||||
<span class="tag tag-or" style="font-size:10px;padding:2px 6px;">{{ log.service }}</span>
|
||||
{% else %}
|
||||
<span class="tag tag-dim" style="font-size:10px;padding:2px 6px;">{{ log.service }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><span class="mono" style="font-size:11px;">{{ log.method }}</span></td>
|
||||
<td>
|
||||
{% if log.response_status %}
|
||||
<span class="mono" style="font-size:11px;color:{% if is_error %}#E53935{% else %}#17A865{% endif %};">{{ log.response_status }}</span>
|
||||
{% else %}
|
||||
<span style="color:#9EA8BE;">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ log.duration_ms if log.duration_ms is not none else '—' }}</span></td>
|
||||
<td style="max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
|
||||
<span class="mono" style="font-size:11px;{% if is_error %}color:#E53935;{% endif %}" title="{{ log.url }}">{{ log.url }}</span>
|
||||
</td>
|
||||
<td style="color:#9EA8BE;"><i class="bi bi-chevron-down"></i></td>
|
||||
</tr>
|
||||
<tr id="detail-{{ log.id }}" style="display:none;background:#F9FAFB;">
|
||||
<td colspan="7" style="padding:14px 20px;">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
|
||||
<div>
|
||||
<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.06em;color:#9EA8BE;margin-bottom:6px;">URL</div>
|
||||
<code style="word-break:break-all;font-size:11px;">{{ log.url }}</code>
|
||||
{% if log.request_body %}
|
||||
<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.06em;color:#9EA8BE;margin:10px 0 6px;">Request body</div>
|
||||
<pre style="font-size:11px;max-height:180px;overflow:auto;">{{ log.request_body }}</pre>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:0.06em;color:#9EA8BE;margin-bottom:6px;">Response ({{ log.response_status }})</div>
|
||||
{% if log.response_body %}
|
||||
<pre style="font-size:11px;max-height:180px;overflow:auto;">{{ log.response_body }}</pre>
|
||||
{% else %}
|
||||
<span style="color:#9EA8BE;font-size:12px;">—</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if total_pages > 1 %}
|
||||
<div class="pagination">
|
||||
{% if page > 1 %}
|
||||
<a href="?service={{ filter_service }}&method={{ filter_method }}&status={{ filter_status }}&q={{ filter_q }}&hours={{ filter_hours }}&page={{ page - 1 }}" class="btn btn-outline btn-sm">← Назад</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
<article class="card" style="padding:0;">
|
||||
{% if logs %}
|
||||
<div class="table-scroll">
|
||||
<table class="align-middle" style="font-size:0.82rem;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:140px;">Время</th>
|
||||
<th style="width:60px;">Сервис</th>
|
||||
<th style="width:40px;">Метод</th>
|
||||
<th style="width:50px;">Статус</th>
|
||||
<th style="width:60px;">Мс</th>
|
||||
<th>URL</th>
|
||||
<th style="width:30px;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for log in logs %}
|
||||
{% set is_error = log.response_status and log.response_status >= 400 %}
|
||||
<tr class="{{ 'text-danger' if is_error else '' }}" style="cursor:pointer;" onclick="toggleDetail({{ log.id }})">
|
||||
<td class="text-muted">{{ log.created_at | datefmt }}</td>
|
||||
<td>
|
||||
<span class="badge {{ 'badge-evotor' if log.service == 'evotor' else 'badge-vk' if log.service == 'vk' else '' }}">
|
||||
{{ log.service }}
|
||||
</span>
|
||||
</td>
|
||||
<td><code>{{ log.method }}</code></td>
|
||||
<td>
|
||||
{% if log.response_status %}
|
||||
<span class="{{ 'text-danger' if is_error else 'text-muted' }}">{{ log.response_status }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-muted">{{ log.duration_ms if log.duration_ms is not none else '—' }}</td>
|
||||
<td style="max-width:400px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">
|
||||
<span title="{{ log.url }}">{{ log.url }}</span>
|
||||
</td>
|
||||
<td class="text-muted"><i class="bi bi-chevron-down"></i></td>
|
||||
</tr>
|
||||
<tr id="detail-{{ log.id }}" style="display:none; background:var(--pico-card-background-color);">
|
||||
<td colspan="7" style="padding:0.75rem 1rem;">
|
||||
<div style="display:grid; grid-template-columns:1fr 1fr; gap:1rem;">
|
||||
<div>
|
||||
<div class="text-muted small mb-1"><strong>URL</strong></div>
|
||||
<code style="word-break:break-all; font-size:0.78rem;">{{ log.url }}</code>
|
||||
{% if log.request_body %}
|
||||
<div class="text-muted small mt-2 mb-1"><strong>Request body</strong></div>
|
||||
<pre style="font-size:0.75rem; max-height:200px; overflow:auto; margin:0; background:var(--pico-code-background-color); padding:0.5rem; border-radius:4px;">{{ log.request_body }}</pre>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-muted small mb-1"><strong>Response ({{ log.response_status }})</strong></div>
|
||||
{% if log.response_body %}
|
||||
<pre style="font-size:0.75rem; max-height:200px; overflow:auto; margin:0; background:var(--pico-code-background-color); padding:0.5rem; border-radius:4px;">{{ log.response_body }}</pre>
|
||||
{% else %}
|
||||
<span class="text-muted">—</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{# ── pagination ── #}
|
||||
{% if total_pages > 1 %}
|
||||
<div style="display:flex; justify-content:center; gap:0.5rem; padding:1rem;">
|
||||
{% if page > 1 %}
|
||||
<a href="?service={{ filter_service }}&method={{ filter_method }}&status={{ filter_status }}&q={{ filter_q }}&hours={{ filter_hours }}&page={{ page - 1 }}" role="button" class="outline secondary sm">← Назад</a>
|
||||
{% endif %}
|
||||
<span class="text-muted" style="line-height:2.2rem;">Стр. {{ page }} / {{ total_pages }}</span>
|
||||
{% if page < total_pages %}
|
||||
<a href="?service={{ filter_service }}&method={{ filter_method }}&status={{ filter_status }}&q={{ filter_q }}&hours={{ filter_hours }}&page={{ page + 1 }}" role="button" class="outline secondary sm">Вперёд →</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<span style="font-size:12px;color:#9EA8BE;">Стр. {{ page }} / {{ total_pages }}</span>
|
||||
{% if page < total_pages %}
|
||||
<a href="?service={{ filter_service }}&method={{ filter_method }}&status={{ filter_status }}&q={{ filter_q }}&hours={{ filter_hours }}&page={{ page + 1 }}" class="btn btn-outline btn-sm">Вперёд →</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
<div class="text-center py-5 text-muted">
|
||||
<i class="bi bi-journal-x" style="font-size:2rem;"></i>
|
||||
<p class="mt-2">Записей не найдено за выбранный период.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</article>
|
||||
|
||||
<style>
|
||||
.badge { display:inline-block; padding:0.1rem 0.4rem; border-radius:4px; font-size:0.75rem; font-weight:600; }
|
||||
.badge-evotor { background:#e8f4fd; color:#0986E2; }
|
||||
.badge-vk { background:#e8f0fe; color:#3b5998; }
|
||||
.text-danger { color:#dc3545; }
|
||||
</style>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-journal-x"></i>
|
||||
<p>Записей не найдено за выбранный период.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
function toggleDetail(id) {
|
||||
const row = document.getElementById('detail-' + id);
|
||||
row.style.display = row.style.display === 'none' ? 'table-row' : 'none';
|
||||
const row = document.getElementById('detail-' + id);
|
||||
row.style.display = row.style.display === 'none' ? 'table-row' : 'none';
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,40 +1,44 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Роли и права — ЭВОСИНК{% endblock %}
|
||||
{% block title %}Роли и права — Мои Товары{% endblock %}
|
||||
{% block page_title %}Роли и права{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<nav class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/admin/users">Пользователи</a></li>
|
||||
<li class="breadcrumb-item active">Роли и права</li>
|
||||
</nav>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/admin/users">Пользователи</a></li>
|
||||
<li>Роли и права</li>
|
||||
</ol>
|
||||
|
||||
<h1 style="font-size:1.3rem;" class="mb-3"><i class="bi bi-shield-lock me-2"></i>Роли и права</h1>
|
||||
<div class="pg-title">Роли и права</div>
|
||||
<div class="pg-sub">Управление разрешениями для каждой роли</div>
|
||||
|
||||
{% for role in roles %}
|
||||
<article class="card mb-3">
|
||||
<header>
|
||||
<h2 style="font-size:1rem;">{{ role.name }}
|
||||
<span class="text-muted small fw-normal">— {{ role.description or '' }}</span>
|
||||
</h2>
|
||||
</header>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/admin/roles/{{ role.id }}/permissions">
|
||||
<div class="row gap-2 flex-wrap">
|
||||
{% for perm in permissions %}
|
||||
<div class="col-auto">
|
||||
<label style="display:flex; align-items:center; gap:0.4rem; margin:0;">
|
||||
<input type="checkbox" name="perm_{{ perm.id }}" value="{{ perm.id }}"
|
||||
{% if perm.id in role_perm_ids[role.id] %}checked{% endif %}>
|
||||
{{ perm.name }}
|
||||
{% if perm.description %}
|
||||
<span class="text-muted small">({{ perm.description }})</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button type="submit" class="sm mt-3">Сохранить права для «{{ role.name }}»</button>
|
||||
</form>
|
||||
<div class="card" style="margin-bottom:14px;">
|
||||
<div class="card-hd">
|
||||
<div>
|
||||
<div class="card-title"><i class="bi bi-shield-lock" style="margin-right:6px;"></i>{{ role.name }}</div>
|
||||
{% if role.description %}
|
||||
<div class="card-sub">{{ role.description }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<form method="post" action="/admin/roles/{{ role.id }}/permissions">
|
||||
<div style="display:flex;flex-wrap:wrap;gap:12px;margin-bottom:16px;">
|
||||
{% for perm in permissions %}
|
||||
<label style="display:flex;align-items:center;gap:7px;cursor:pointer;font-size:13px;padding:6px 10px;border:1px solid #E4E6EE;border-radius:7px;background:#F9FAFB;">
|
||||
<input type="checkbox" name="perm_{{ perm.id }}" value="{{ perm.id }}"
|
||||
{% if perm.id in role_perm_ids[role.id] %}checked{% endif %}
|
||||
style="accent-color:#FF5500;">
|
||||
{{ perm.name }}
|
||||
{% if perm.description %}
|
||||
<span style="font-size:11px;color:#9EA8BE;">({{ perm.description }})</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-save"></i> Сохранить права для «{{ role.name }}»
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,152 +1,176 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ target.first_name }} {{ target.last_name }} — Админ — ЭВОСИНК{% endblock %}
|
||||
{% block title %}{{ target.first_name }} {{ target.last_name }} — Админ — Мои Товары{% endblock %}
|
||||
{% block page_title %}Пользователь{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<nav class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/admin/users">Пользователи</a></li>
|
||||
<li class="breadcrumb-item active">{{ target.first_name }} {{ target.last_name }}</li>
|
||||
</nav>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/admin/users">Пользователи</a></li>
|
||||
<li>{{ target.first_name }} {{ target.last_name }}</li>
|
||||
</ol>
|
||||
|
||||
{% if request.query_params.get('success') == 'reset_sent' %}
|
||||
<div class="alert alert-success mb-3"><p>Ссылка для сброса пароля отправлена.</p></div>
|
||||
<div class="alert alert-gr"><span><i class="bi bi-check-circle"></i></span><div>Ссылка для сброса пароля отправлена.</div></div>
|
||||
{% elif request.query_params.get('success') == 'invite_sent' %}
|
||||
<div class="alert alert-success mb-3"><p>Приглашение отправлено.</p></div>
|
||||
<div class="alert alert-gr"><span><i class="bi bi-check-circle"></i></span><div>Приглашение отправлено.</div></div>
|
||||
{% elif request.query_params.get('success') == 'saved' %}
|
||||
<div class="alert alert-success mb-3"><p>Данные сохранены.</p></div>
|
||||
<div class="alert alert-gr"><span><i class="bi bi-check-circle"></i></span><div>Данные сохранены.</div></div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row gap-3 align-start">
|
||||
<div class="col-lg-6">
|
||||
<article class="card">
|
||||
<header><h2>Профиль</h2></header>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item"><span class="text-muted small">ID</span><span>{{ target.id }}</span></li>
|
||||
<li class="list-group-item"><span class="text-muted small">Имя</span><span>{{ target.first_name }} {{ target.last_name }}</span></li>
|
||||
<li class="list-group-item"><span class="text-muted small">Email</span>
|
||||
<span>{{ target.email }}
|
||||
{% if target.is_email_confirmed %}
|
||||
<span class="badge badge-success ms-1">подтверждён</span>
|
||||
{% else %}
|
||||
<span class="badge badge-warning ms-1">не подтверждён</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item"><span class="text-muted small">Телефон</span><span>{{ target.phone }}</span></li>
|
||||
<li class="list-group-item"><span class="text-muted small">Роль</span>
|
||||
<span>
|
||||
{% if target.role == 'system' %}<span class="badge badge-danger">Системный</span>
|
||||
{% elif target.role == 'admin' %}<span class="badge badge-warning">Администратор</span>
|
||||
{% else %}<span class="badge badge-secondary">Пользователь</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item"><span class="text-muted small">Статус</span>
|
||||
<span>
|
||||
{% if target.status == 'active' %}<span class="badge badge-success">Активен</span>
|
||||
{% elif target.status == 'pending' %}<span class="badge badge-warning">Ожидает</span>
|
||||
{% else %}<span class="badge badge-danger">Заблокирован</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item"><span class="text-muted small">Регистрация</span><span>{{ target.created_at | datefmt }}</span></li>
|
||||
{% if target.evotor_user_id %}
|
||||
<li class="list-group-item"><span class="text-muted small">Эвотор ID</span><span class="font-monospace small">{{ target.evotor_user_id }}</span></li>
|
||||
{% endif %}
|
||||
{% if target.invite_token %}
|
||||
<li class="list-group-item"><span class="text-muted small">Приглашение до</span><span>{{ target.invite_expires | datefmt }}</span></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</article>
|
||||
<!-- User header -->
|
||||
<div style="display:flex;align-items:center;gap:14px;margin-bottom:24px;">
|
||||
<div class="avatar" style="width:48px;height:48px;font-size:16px;">
|
||||
{{ target.first_name[0] if target.first_name else '?' }}{{ target.last_name[0] if target.last_name else '' }}
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:18px;font-weight:800;letter-spacing:-0.02em;">{{ target.first_name }} {{ target.last_name }}</div>
|
||||
<div class="mono" style="font-size:12px;color:#9EA8BE;">{{ target.email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if target.evotor_meta %}
|
||||
<article class="card mt-3">
|
||||
<header><h2>Данные Эвотор</h2></header>
|
||||
<div class="card-body">
|
||||
<pre class="font-monospace small" style="overflow-x:auto; white-space:pre-wrap; margin:0;">{{ target.evotor_meta | tojson(indent=2) }}</pre>
|
||||
</div>
|
||||
</article>
|
||||
<div class="g2" style="align-items:start;">
|
||||
|
||||
<!-- Left column -->
|
||||
<div style="display:flex;flex-direction:column;gap:14px;">
|
||||
|
||||
<!-- Profile -->
|
||||
<div class="card">
|
||||
<div class="card-hd"><div><div class="card-title">Профиль</div></div></div>
|
||||
<div class="conn-detail">
|
||||
<div class="conn-row"><span class="conn-k">ID</span><span class="conn-v">{{ target.id }}</span></div>
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Email</span>
|
||||
<span class="conn-v" style="display:flex;align-items:center;gap:6px;">
|
||||
{{ target.email }}
|
||||
{% if target.is_email_confirmed %}
|
||||
<span class="tag tag-gr" style="font-size:10px;padding:1px 6px;">подтверждён</span>
|
||||
{% else %}
|
||||
<span class="tag tag-yl" style="font-size:10px;padding:1px 6px;">не подтверждён</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="conn-row"><span class="conn-k">Телефон</span><span class="conn-v">{{ target.phone or '—' }}</span></div>
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Роль</span>
|
||||
<span class="conn-v" style="font-family:inherit;">
|
||||
{% if target.role == 'system' %}<span class="tag tag-rd" style="font-size:10.5px;">Системный</span>
|
||||
{% elif target.role == 'admin' %}<span class="tag tag-or" style="font-size:10.5px;">Администратор</span>
|
||||
{% else %}<span class="tag tag-dim" style="font-size:10.5px;">Пользователь</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Статус</span>
|
||||
<span class="conn-v" style="font-family:inherit;">
|
||||
{% if target.status == 'active' %}<span class="tag tag-gr"><span class="dot g"></span>Активен</span>
|
||||
{% elif target.status == 'pending' %}<span class="tag tag-yl"><span class="dot y pulse"></span>Ожидает</span>
|
||||
{% else %}<span class="tag tag-rd"><span class="dot r"></span>Заблокирован</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="conn-row"><span class="conn-k">Регистрация</span><span class="conn-v">{{ target.created_at | datefmt }}</span></div>
|
||||
{% if target.evotor_user_id %}
|
||||
<div class="conn-row"><span class="conn-k">Эвотор ID</span><span class="conn-v">{{ target.evotor_user_id }}</span></div>
|
||||
{% endif %}
|
||||
{% if target.invite_token %}
|
||||
<div class="conn-row"><span class="conn-k">Приглашение до</span><span class="conn-v">{{ target.invite_expires | datefmt }}</span></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<article class="card">
|
||||
<header><h2>Действия</h2></header>
|
||||
<div class="card-body d-grid gap-2">
|
||||
{% if target.status != 'active' %}
|
||||
<form method="post" action="/admin/users/{{ target.id }}/activate">
|
||||
<button type="submit" class="w-100">
|
||||
<i class="bi bi-check-circle me-1"></i>Активировать
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if target.status != 'suspended' %}
|
||||
<form method="post" action="/admin/users/{{ target.id }}/suspend">
|
||||
<button type="submit" class="w-100 outline danger">
|
||||
<i class="bi bi-slash-circle me-1"></i>Заблокировать
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form method="post" action="/admin/users/{{ target.id }}/reset-password">
|
||||
<button type="submit" class="w-100 outline secondary">
|
||||
<i class="bi bi-key me-1"></i>Сбросить пароль
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" action="/admin/users/{{ target.id }}/send-invite">
|
||||
<button type="submit" class="w-100 outline secondary">
|
||||
<i class="bi bi-envelope me-1"></i>Отправить приглашение
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" action="/admin/users/{{ target.id }}/view-as">
|
||||
<button type="submit" class="w-100 outline">
|
||||
<i class="bi bi-eye me-1"></i>Просмотр от имени пользователя
|
||||
</button>
|
||||
</form>
|
||||
{% if user.role == 'system' and target.id != user.id %}
|
||||
<form method="post" action="/admin/users/{{ target.id }}/delete"
|
||||
onsubmit="return confirm('Удалить пользователя {{ target.email }}? Это действие необратимо.')">
|
||||
<button type="submit" class="w-100 danger sm">
|
||||
<i class="bi bi-trash me-1"></i>Удалить
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card mt-3">
|
||||
<header><h2>Редактировать</h2></header>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/admin/users/{{ target.id }}/edit">
|
||||
<div class="row gap-2 mb-2">
|
||||
<div class="col">
|
||||
<label for="first_name">Имя
|
||||
<input type="text" id="first_name" name="first_name" value="{{ target.first_name }}" required>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="last_name">Фамилия
|
||||
<input type="text" id="last_name" name="last_name" value="{{ target.last_name }}" required>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<label for="email">Email
|
||||
<input type="email" id="email" name="email" value="{{ target.email }}">
|
||||
</label>
|
||||
<label for="phone">Телефон
|
||||
<input type="tel" id="phone" name="phone" value="{{ target.phone }}">
|
||||
</label>
|
||||
{% if user.role == 'system' %}
|
||||
<label for="role">Роль
|
||||
<select id="role" name="role">
|
||||
<option value="user" {% if target.role == 'user' %}selected{% endif %}>Пользователь</option>
|
||||
<option value="admin" {% if target.role == 'admin' %}selected{% endif %}>Администратор</option>
|
||||
<option value="system" {% if target.role == 'system' %}selected{% endif %}>Системный</option>
|
||||
</select>
|
||||
</label>
|
||||
{% endif %}
|
||||
<button type="submit">Сохранить</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
{% if target.evotor_meta %}
|
||||
<div class="card">
|
||||
<div class="card-hd"><div><div class="card-title">Данные Эвотор</div></div></div>
|
||||
<pre>{{ target.evotor_meta | tojson(indent=2) }}</pre>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Right column -->
|
||||
<div style="display:flex;flex-direction:column;gap:14px;">
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="card">
|
||||
<div class="card-title" style="margin-bottom:14px;">Действия</div>
|
||||
<div style="display:flex;flex-direction:column;gap:8px;">
|
||||
{% if target.status != 'active' %}
|
||||
<form method="post" action="/admin/users/{{ target.id }}/activate">
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="bi bi-check-circle"></i> Активировать
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if target.status != 'suspended' %}
|
||||
<form method="post" action="/admin/users/{{ target.id }}/suspend">
|
||||
<button type="submit" class="btn btn-danger w-100">
|
||||
<i class="bi bi-slash-circle"></i> Заблокировать
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form method="post" action="/admin/users/{{ target.id }}/reset-password">
|
||||
<button type="submit" class="btn btn-outline w-100">
|
||||
<i class="bi bi-key"></i> Сбросить пароль
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" action="/admin/users/{{ target.id }}/send-invite">
|
||||
<button type="submit" class="btn btn-outline w-100">
|
||||
<i class="bi bi-envelope"></i> Отправить приглашение
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" action="/admin/users/{{ target.id }}/view-as">
|
||||
<button type="submit" class="btn btn-outline w-100">
|
||||
<i class="bi bi-eye"></i> Просмотр от имени пользователя
|
||||
</button>
|
||||
</form>
|
||||
{% if user.role == 'system' and target.id != user.id %}
|
||||
<form method="post" action="/admin/users/{{ target.id }}/delete"
|
||||
onsubmit="return confirm('Удалить пользователя {{ target.email }}? Это действие необратимо.')">
|
||||
<button type="submit" class="btn btn-danger btn-sm w-100">
|
||||
<i class="bi bi-trash"></i> Удалить
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit -->
|
||||
<div class="card">
|
||||
<div class="card-title" style="margin-bottom:14px;">Редактировать</div>
|
||||
<form method="post" action="/admin/users/{{ target.id }}/edit">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="first_name">Имя</label>
|
||||
<input class="inp" type="text" id="first_name" name="first_name" value="{{ target.first_name }}" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="last_name">Фамилия</label>
|
||||
<input class="inp" type="text" id="last_name" name="last_name" value="{{ target.last_name }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="email">Email</label>
|
||||
<input class="inp" type="email" id="email" name="email" value="{{ target.email }}">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="phone">Телефон</label>
|
||||
<input class="inp" type="tel" id="phone" name="phone" value="{{ target.phone }}">
|
||||
</div>
|
||||
{% if user.role == 'system' %}
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="role">Роль</label>
|
||||
<select class="inp" id="role" name="role">
|
||||
<option value="user" {% if target.role == 'user' %}selected{% endif %}>Пользователь</option>
|
||||
<option value="admin" {% if target.role == 'admin' %}selected{% endif %}>Администратор</option>
|
||||
<option value="system" {% if target.role == 'system' %}selected{% endif %}>Системный</option>
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
<button type="submit" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-save"></i> Сохранить
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,173 +1,201 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Пользователи — Администрирование — ЭВОСИНК{% endblock %}
|
||||
{% block title %}Пользователи — Администрирование — Мои Товары{% endblock %}
|
||||
{% block page_title %}Пользователи{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-between align-center mb-3">
|
||||
<h1 style="font-size:1.3rem; margin:0;"><i class="bi bi-people me-2"></i>Пользователи</h1>
|
||||
<button onclick="document.getElementById('create-user-dialog').showModal()" class="sm">
|
||||
<i class="bi bi-person-plus me-1"></i>Создать пользователя
|
||||
</button>
|
||||
<div class="pg-title">Пользователи</div>
|
||||
<div class="pg-sub">Управление аккаунтами и подключениями пользователей</div>
|
||||
|
||||
<!-- Topbar action -->
|
||||
<div style="display:flex;justify-content:flex-end;margin-bottom:16px;">
|
||||
<button class="btn btn-primary btn-sm" onclick="document.getElementById('create-user-dialog').showModal()">
|
||||
<i class="bi bi-person-plus"></i> Создать пользователя
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Create user dialog -->
|
||||
<dialog id="create-user-dialog">
|
||||
<article>
|
||||
<header>
|
||||
<button aria-label="Закрыть" rel="prev" onclick="document.getElementById('create-user-dialog').close()"></button>
|
||||
<h3>Создать пользователя</h3>
|
||||
</header>
|
||||
{% if create_errors %}
|
||||
<div role="alert" class="alert alert-danger mb-3">
|
||||
{% for e in create_errors %}<p>{{ e }}</p>{% endfor %}
|
||||
<div class="dialog-hd">
|
||||
<div class="dialog-title">Создать пользователя</div>
|
||||
<button class="dialog-close" onclick="document.getElementById('create-user-dialog').close()">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
{% if create_errors %}
|
||||
<div class="alert alert-rd" style="margin-bottom:14px;">
|
||||
<span><i class="bi bi-x-circle"></i></span>
|
||||
<div>{% for e in create_errors %}<div>{{ e }}</div>{% endfor %}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" action="/admin/users/create" novalidate>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="cu_first_name">Имя</label>
|
||||
<input class="inp" type="text" id="cu_first_name" name="first_name"
|
||||
value="{{ create_form.first_name if create_form else '' }}" required>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" action="/admin/users/create" novalidate>
|
||||
<div class="row gap-2 mb-2">
|
||||
<div class="col">
|
||||
<label for="cu_first_name">Имя
|
||||
<input type="text" id="cu_first_name" name="first_name" value="{{ create_form.first_name if create_form else '' }}" required>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="cu_last_name">Фамилия
|
||||
<input type="text" id="cu_last_name" name="last_name" value="{{ create_form.last_name if create_form else '' }}">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<label for="cu_email">Email
|
||||
<input type="text" id="cu_email" name="email" value="{{ create_form.email if create_form else '' }}" required>
|
||||
</label>
|
||||
<label for="cu_phone">Телефон
|
||||
<input type="tel" id="cu_phone" name="phone" value="{{ create_form.phone if create_form else '' }}" placeholder="+7 (999) 999-99-99">
|
||||
</label>
|
||||
<label for="cu_password">Пароль
|
||||
<input type="password" id="cu_password" name="password" required>
|
||||
</label>
|
||||
{% if user.role == 'system' %}
|
||||
<label for="cu_role">Роль
|
||||
<select id="cu_role" name="role">
|
||||
<option value="user" {% if not create_form or create_form.role == 'user' %}selected{% endif %}>Пользователь</option>
|
||||
<option value="admin" {% if create_form and create_form.role == 'admin' %}selected{% endif %}>Администратор</option>
|
||||
</select>
|
||||
</label>
|
||||
{% endif %}
|
||||
<footer class="d-flex gap-2 justify-end">
|
||||
<button type="button" class="outline secondary" onclick="document.getElementById('create-user-dialog').close()">Отмена</button>
|
||||
<button type="submit">Создать</button>
|
||||
</footer>
|
||||
</form>
|
||||
</article>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="cu_last_name">Фамилия</label>
|
||||
<input class="inp" type="text" id="cu_last_name" name="last_name"
|
||||
value="{{ create_form.last_name if create_form else '' }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="cu_email">Email</label>
|
||||
<input class="inp" type="text" id="cu_email" name="email"
|
||||
value="{{ create_form.email if create_form else '' }}" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="cu_phone">Телефон</label>
|
||||
<input class="inp" type="tel" id="cu_phone" name="phone"
|
||||
value="{{ create_form.phone if create_form else '' }}" placeholder="+7 (999) 999-99-99">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="cu_password">Пароль</label>
|
||||
<input class="inp" type="password" id="cu_password" name="password" required>
|
||||
</div>
|
||||
{% if user.role == 'system' %}
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="cu_role">Роль</label>
|
||||
<select class="inp" id="cu_role" name="role">
|
||||
<option value="user" {% if not create_form or create_form.role == 'user' %}selected{% endif %}>Пользователь</option>
|
||||
<option value="admin" {% if create_form and create_form.role == 'admin' %}selected{% endif %}>Администратор</option>
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px;">
|
||||
<button type="button" class="btn btn-outline" onclick="document.getElementById('create-user-dialog').close()">Отмена</button>
|
||||
<button type="submit" class="btn btn-primary">Создать</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
{% if create_errors %}
|
||||
<script>document.addEventListener('DOMContentLoaded', () => document.getElementById('create-user-dialog').showModal());</script>
|
||||
{% endif %}
|
||||
|
||||
<article class="card mb-3">
|
||||
<div class="card-body">
|
||||
<form method="get" action="/admin/users" class="d-flex gap-2 flex-wrap align-center">
|
||||
<input type="text" name="search" value="{{ search }}" placeholder="Поиск по имени, email, телефону" style="flex:1; min-width:200px; margin:0;">
|
||||
<select name="status" style="width:auto; margin:0;">
|
||||
<option value="">Все статусы</option>
|
||||
<option value="pending" {% if status_filter == 'pending' %}selected{% endif %}>Ожидает</option>
|
||||
<option value="active" {% if status_filter == 'active' %}selected{% endif %}>Активен</option>
|
||||
<option value="suspended" {% if status_filter == 'suspended' %}selected{% endif %}>Заблокирован</option>
|
||||
</select>
|
||||
<select name="role" style="width:auto; margin:0;">
|
||||
<option value="">Все роли</option>
|
||||
<option value="user" {% if role_filter == 'user' %}selected{% endif %}>Пользователь</option>
|
||||
<option value="admin" {% if role_filter == 'admin' %}selected{% endif %}>Администратор</option>
|
||||
<option value="system" {% if role_filter == 'system' %}selected{% endif %}>Системный</option>
|
||||
</select>
|
||||
<button type="submit" class="sm">Найти</button>
|
||||
{% if search or status_filter or role_filter %}
|
||||
<a href="/admin/users" role="button" class="outline secondary sm">Сбросить</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="table-scroll">
|
||||
<table class="align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Имя</th>
|
||||
<th>Email</th>
|
||||
<th>Телефон</th>
|
||||
<th>Роль</th>
|
||||
<th>Статус</th>
|
||||
<th>Эвотор</th>
|
||||
<th>Дата</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for u in users %}
|
||||
<tr>
|
||||
<td class="text-muted small">{{ u.id }}</td>
|
||||
<td>{{ u.first_name }} {{ u.last_name }}</td>
|
||||
<td title="{{ u.email }}">
|
||||
{{ u.email }}
|
||||
{% if not u.is_email_confirmed %}
|
||||
<span class="badge badge-warning ms-1" title="Email не подтверждён"><i class="bi bi-exclamation-circle"></i></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ u.phone or '—' }}</td>
|
||||
<td>
|
||||
{% if u.role == 'system' %}<span class="badge badge-danger">Системный</span>
|
||||
{% elif u.role == 'admin' %}<span class="badge badge-warning">Админ</span>
|
||||
{% else %}<span class="badge badge-secondary">Польз.</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if u.status == 'active' %}<span class="badge badge-success">Активен</span>
|
||||
{% elif u.status == 'pending' %}<span class="badge badge-warning">Ожидает</span>
|
||||
{% else %}<span class="badge badge-danger">Заблок.</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if u.evotor_user_id %}
|
||||
<i class="bi bi-check-circle text-success" title="{{ u.evotor_user_id }}"></i>
|
||||
{% else %}
|
||||
<span class="text-muted">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-muted small">{{ u.created_at | datefmt }}</td>
|
||||
<td>
|
||||
<a href="/admin/users/{{ u.id }}" role="button" class="outline sm">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="9" class="text-center text-muted py-4">Пользователи не найдены</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if total_pages > 1 %}
|
||||
<footer>
|
||||
<div class="d-flex gap-2 justify-center align-center">
|
||||
{% if page > 1 %}
|
||||
<a href="?page={{ page - 1 }}&search={{ search }}&status={{ status_filter }}&role={{ role_filter }}" role="button" class="outline sm">«</a>
|
||||
{% endif %}
|
||||
<span class="text-muted small">Стр. {{ page }} из {{ total_pages }}</span>
|
||||
{% if page < total_pages %}
|
||||
<a href="?page={{ page + 1 }}&search={{ search }}&status={{ status_filter }}&role={{ role_filter }}" role="button" class="outline sm">»</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Search / filter bar -->
|
||||
<div class="card" style="margin-bottom:14px;padding:14px 20px;">
|
||||
<form method="get" action="/admin/users" style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
||||
<input class="inp" type="text" name="search" value="{{ search }}"
|
||||
placeholder="Поиск по имени, email, телефону" style="flex:1;min-width:200px;">
|
||||
<select class="inp" name="status" style="width:auto;">
|
||||
<option value="">Все статусы</option>
|
||||
<option value="pending" {% if status_filter == 'pending' %}selected{% endif %}>Ожидает</option>
|
||||
<option value="active" {% if status_filter == 'active' %}selected{% endif %}>Активен</option>
|
||||
<option value="suspended" {% if status_filter == 'suspended' %}selected{% endif %}>Заблокирован</option>
|
||||
</select>
|
||||
<select class="inp" name="role" style="width:auto;">
|
||||
<option value="">Все роли</option>
|
||||
<option value="user" {% if role_filter == 'user' %}selected{% endif %}>Пользователь</option>
|
||||
<option value="admin" {% if role_filter == 'admin' %}selected{% endif %}>Администратор</option>
|
||||
<option value="system" {% if role_filter == 'system' %}selected{% endif %}>Системный</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary btn-sm">Найти</button>
|
||||
{% if search or status_filter or role_filter %}
|
||||
<a href="/admin/users" class="btn btn-outline btn-sm">Сбросить</a>
|
||||
{% endif %}
|
||||
</article>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Users table -->
|
||||
<div class="card" style="padding:0;">
|
||||
<div class="table-wrap">
|
||||
<table class="tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Пользователь</th>
|
||||
<th>Телефон</th>
|
||||
<th>Роль</th>
|
||||
<th>Статус</th>
|
||||
<th>Эвотор</th>
|
||||
<th>Дата</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for u in users %}
|
||||
<tr>
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ u.id }}</span></td>
|
||||
<td>
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<div class="avatar" style="width:30px;height:30px;font-size:10px;">
|
||||
{{ u.first_name[0] if u.first_name else '?' }}{{ u.last_name[0] if u.last_name else '' }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="tbl-name">{{ u.first_name }} {{ u.last_name }}</div>
|
||||
<div class="tbl-sub">
|
||||
{{ u.email }}
|
||||
{% if not u.is_email_confirmed %}
|
||||
<span class="tag tag-yl" style="font-size:9.5px;padding:0 5px;margin-left:4px;"><i class="bi bi-exclamation-circle"></i></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-size:12px;color:#9EA8BE;">{{ u.phone or '—' }}</td>
|
||||
<td>
|
||||
{% if u.role == 'system' %}
|
||||
<span class="tag tag-rd" style="font-size:10.5px;">Системный</span>
|
||||
{% elif u.role == 'admin' %}
|
||||
<span class="tag tag-or" style="font-size:10.5px;">Админ</span>
|
||||
{% else %}
|
||||
<span class="tag tag-dim" style="font-size:10.5px;">Польз.</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if u.status == 'active' %}
|
||||
<span class="tag tag-gr"><span class="dot g"></span>Активен</span>
|
||||
{% elif u.status == 'pending' %}
|
||||
<span class="tag tag-yl"><span class="dot y pulse"></span>Ожидает</span>
|
||||
{% else %}
|
||||
<span class="tag tag-rd"><span class="dot r"></span>Заблок.</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if u.evotor_user_id %}
|
||||
<span class="tag tag-gr" style="font-size:10.5px;"><i class="bi bi-check-circle"></i></span>
|
||||
{% else %}
|
||||
<span style="color:#9EA8BE;font-size:12px;">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ u.created_at | datefmt }}</span></td>
|
||||
<td>
|
||||
<a href="/admin/users/{{ u.id }}" class="btn btn-outline btn-xs">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="8" class="text-center" style="padding:32px;color:#9EA8BE;">Пользователи не найдены</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if total_pages > 1 %}
|
||||
<div class="pagination">
|
||||
{% if page > 1 %}
|
||||
<a href="?page={{ page - 1 }}&search={{ search }}&status={{ status_filter }}&role={{ role_filter }}" class="btn btn-outline btn-sm">« Назад</a>
|
||||
{% endif %}
|
||||
<span style="font-size:12px;color:#9EA8BE;">Стр. {{ page }} из {{ total_pages }}</span>
|
||||
{% if page < total_pages %}
|
||||
<a href="?page={{ page + 1 }}&search={{ search }}&status={{ status_filter }}&role={{ role_filter }}" class="btn btn-outline btn-sm">Вперёд »</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if user.role == 'system' %}
|
||||
<div class="mt-3 text-end">
|
||||
<a href="/admin/roles" role="button" class="outline secondary sm">
|
||||
<i class="bi bi-shield-lock me-1"></i>Управление ролями
|
||||
</a>
|
||||
<div style="margin-top:14px;text-align:right;">
|
||||
<a href="/admin/roles" class="btn btn-outline btn-sm">
|
||||
<i class="bi bi-shield-lock"></i> Управление ролями
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,121 +1,155 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru" data-theme="light">
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}ЭВОСИНК{% endblock %}</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Мои Товары{% endblock %}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Golos+Text:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
<nav class="container">
|
||||
<ul>
|
||||
<li><a href="/" class="brand-logo">ЭВОСИНК</a></li>
|
||||
</ul>
|
||||
<ul class="nav-links">
|
||||
{% if user %}
|
||||
{% if user.role not in ('admin', 'system') or viewed_user %}
|
||||
<li><a href="/connections">Подключения</a></li>
|
||||
<li><a href="/catalog">Каталог Эвотор</a></li>
|
||||
<li><a href="/vk-catalog/albums">Каталог ВК</a></li>
|
||||
<li><a href="/sync">Синхронизация</a></li>
|
||||
{% endif %}
|
||||
{% if user.role in ('admin', 'system') %}
|
||||
<li><a href="/admin/users"><i class="bi bi-shield-lock"></i> Админ</a></li>
|
||||
<li><a href="/admin/logs"><i class="bi bi-journal-text"></i> Логи</a></li>
|
||||
{% endif %}
|
||||
<li><a href="/profile"><i class="bi bi-person-circle"></i> Личный кабинет</a></li>
|
||||
<li><a href="/logout" class="secondary">Выход</a></li>
|
||||
{% else %}
|
||||
<li><a href="/login">Вход</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% if user %}
|
||||
<details class="mobile-menu">
|
||||
<summary role="button" class="outline secondary icon-btn"><i class="bi bi-list"></i></summary>
|
||||
<ul>
|
||||
{% if user.role not in ('admin', 'system') or viewed_user %}
|
||||
<li><a href="/connections">Подключения</a></li>
|
||||
<li><a href="/catalog">Каталог Эвотор</a></li>
|
||||
<li><a href="/vk-catalog/albums">Каталог ВК</a></li>
|
||||
<li><a href="/sync">Синхронизация</a></li>
|
||||
{% endif %}
|
||||
{% if user.role in ('admin', 'system') %}
|
||||
<li><a href="/admin/users">Админ</a></li>
|
||||
<li><a href="/admin/logs">Логи</a></li>
|
||||
{% endif %}
|
||||
<li><a href="/profile">Личный кабинет</a></li>
|
||||
<li><a href="/logout">Выход</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
{% else %}
|
||||
<details class="mobile-menu">
|
||||
<summary role="button" class="outline secondary icon-btn"><i class="bi bi-list"></i></summary>
|
||||
<ul>
|
||||
<li><a href="/login">Вход</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</header>
|
||||
{% block body %}
|
||||
<div class="shell">
|
||||
|
||||
<!-- ── Sidebar ── -->
|
||||
<aside class="sidebar">
|
||||
<div class="sb-logo">
|
||||
<div class="sb-logo-icon">
|
||||
<svg width="22" height="22" viewBox="0 0 28 28" fill="none">
|
||||
<path d="M9 11h10l-1.5 9H10.5L9 11Z" fill="white" opacity="0.95"/>
|
||||
<path d="M11.5 11V9a2.5 2.5 0 015 0v2" stroke="white" stroke-width="1.6" stroke-linecap="round" fill="none"/>
|
||||
<line x1="12" y1="15" x2="16" y2="15" stroke="#FF5500" stroke-width="1.3" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sb-logo-name">Мои Товары</div>
|
||||
<div class="sb-logo-sub">мои-товары.рф</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="sb-nav">
|
||||
{% if user %}
|
||||
{% if user.role in ('admin', 'system') %}
|
||||
<div class="sb-section">Управление</div>
|
||||
<a href="/admin/users" class="sb-item {% if request.url.path.startswith('/admin/users') %}active{% endif %}">
|
||||
<span class="sb-icon"><i class="bi bi-people"></i></span>
|
||||
<span>Пользователи</span>
|
||||
</a>
|
||||
{% if user.role == 'system' %}
|
||||
<a href="/admin/logs" class="sb-item {% if request.url.path.startswith('/admin/logs') %}active{% endif %}">
|
||||
<span class="sb-icon"><i class="bi bi-journal-text"></i></span>
|
||||
<span>Логи</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="sb-section">Главное</div>
|
||||
<a href="/connections" class="sb-item {% if request.url.path == '/connections' %}active{% endif %}">
|
||||
<span class="sb-icon"><i class="bi bi-plug"></i></span>
|
||||
<span>Подключения</span>
|
||||
</a>
|
||||
<a href="/catalog/stores" class="sb-item {% if request.url.path.startswith('/catalog') %}active{% endif %}">
|
||||
<span class="sb-icon"><i class="bi bi-shop"></i></span>
|
||||
<span>Каталог Эвотор</span>
|
||||
</a>
|
||||
<a href="/vk-catalog/albums" class="sb-item {% if request.url.path.startswith('/vk-catalog') %}active{% endif %}">
|
||||
<span class="sb-icon"><i class="bi bi-bag"></i></span>
|
||||
<span>Каталог ВК</span>
|
||||
</a>
|
||||
<a href="/sync" class="sb-item {% if request.url.path == '/sync' %}active{% endif %}">
|
||||
<span class="sb-icon"><i class="bi bi-arrow-repeat"></i></span>
|
||||
<span>Синхронизация</span>
|
||||
</a>
|
||||
<div class="sb-section" style="margin-top:6px;">Аккаунт</div>
|
||||
<a href="/profile" class="sb-item {% if request.url.path.startswith('/profile') %}active{% endif %}">
|
||||
<span class="sb-icon"><i class="bi bi-person"></i></span>
|
||||
<span>Профиль</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</nav>
|
||||
|
||||
{% if user %}
|
||||
<a href="/profile" class="sb-user">
|
||||
<div class="avatar {% if user.role in ('admin','system') %}admin{% endif %}">
|
||||
{{ user.first_name[0] if user.first_name else '?' }}{{ user.last_name[0] if user.last_name else '' }}
|
||||
</div>
|
||||
<div style="flex:1; min-width:0;">
|
||||
<div class="sb-user-name">{{ user.first_name }} {{ user.last_name }}</div>
|
||||
<div class="sb-user-role">
|
||||
<span class="role-chip {% if user.role in ('admin','system') %}admin{% else %}user{% endif %}">
|
||||
{% if user.role == 'system' %}SYSTEM{% elif user.role == 'admin' %}ADMIN{% else %}USER{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
</aside>
|
||||
|
||||
<!-- ── Main ── -->
|
||||
<div class="main">
|
||||
|
||||
<!-- Topbar -->
|
||||
<div class="topbar">
|
||||
<div class="topbar-title">{% block page_title %}{% endblock %}</div>
|
||||
{% block topbar_extras %}{% endblock %}
|
||||
{% if user %}
|
||||
<a href="/logout" class="btn btn-ghost btn-sm" style="margin-left:auto;" title="Выйти">
|
||||
<i class="bi bi-box-arrow-right"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if viewed_user %}
|
||||
<div style="background:#e65c00;color:#fff;text-align:center;padding:0.4rem 1rem;font-size:0.9rem;">
|
||||
<i class="bi bi-eye me-1"></i>Просмотр от имени: <strong>{{ viewed_user.first_name }} {{ viewed_user.last_name }}</strong> ({{ viewed_user.email }})
|
||||
<form method="post" action="/admin/view-as/stop" style="display:inline;margin-left:1rem;">
|
||||
<button type="submit" style="background:none;border:1px solid #fff;color:#fff;padding:0.1rem 0.6rem;font-size:0.85rem;cursor:pointer;border-radius:4px;">Выйти</button>
|
||||
</form>
|
||||
<div class="view-as-bar">
|
||||
<i class="bi bi-eye"></i> Просмотр от имени: <strong>{{ viewed_user.first_name }} {{ viewed_user.last_name }}</strong> ({{ viewed_user.email }})
|
||||
<form method="post" action="/admin/view-as/stop" style="display:inline;margin-left:1rem;">
|
||||
<button type="submit" style="background:none;border:1px solid rgba(255,255,255,0.6);color:#fff;padding:2px 10px;font-size:12px;cursor:pointer;border-radius:4px;font-family:inherit;">Выйти</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<main class="container py-4">
|
||||
{% if errors %}
|
||||
<div role="alert" class="alert alert-danger">
|
||||
{% for error in errors %}
|
||||
<p>{{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="content">
|
||||
{% if errors %}
|
||||
<div class="alert alert-rd" style="margin-bottom:16px;">
|
||||
<span><i class="bi bi-x-circle"></i></span>
|
||||
<div>{% for e in errors %}<div>{{ e }}</div>{% endfor %}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if success %}
|
||||
<div class="alert alert-gr" style="margin-bottom:16px;">
|
||||
<span><i class="bi bi-check-circle"></i></span>
|
||||
<div>{{ success }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
{% if success %}
|
||||
<div role="alert" class="alert alert-success">
|
||||
<p>{{ success }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
{% if jivosite_widget_id %}
|
||||
<script src="//code.jivosite.com/widget/{{ jivosite_widget_id }}" async></script>
|
||||
{% endif %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/inputmask@5.0.9/dist/inputmask.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var phoneInputs = document.querySelectorAll('input[name="phone"]');
|
||||
if (phoneInputs.length) {
|
||||
Inputmask('+7 (999) 999-99-99', {
|
||||
placeholder: '_',
|
||||
showMaskOnHover: false,
|
||||
clearMaskOnLostFocus: false
|
||||
}).mask(phoneInputs);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
document.addEventListener('invalid', function(e) {
|
||||
if (e.target.validity.valueMissing) {
|
||||
e.target.setCustomValidity('Пожалуйста, заполните это поле');
|
||||
} else if (e.target.validity.typeMismatch) {
|
||||
e.target.setCustomValidity('Пожалуйста, введите корректное значение');
|
||||
}
|
||||
}, true);
|
||||
document.addEventListener('input', function(e) {
|
||||
if (e.target.required) e.target.setCustomValidity('');
|
||||
}, true);
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
{% if jivosite_widget_id %}
|
||||
<script src="//code.jivosite.com/widget/{{ jivosite_widget_id }}" async></script>
|
||||
{% endif %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/inputmask@5.0.9/dist/inputmask.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var phoneInputs = document.querySelectorAll('input[name="phone"]');
|
||||
if (phoneInputs.length) {
|
||||
Inputmask('+7 (999) 999-99-99', { placeholder: '_', showMaskOnHover: false, clearMaskOnLostFocus: false }).mask(phoneInputs);
|
||||
}
|
||||
});
|
||||
document.addEventListener('invalid', function(e) {
|
||||
if (e.target.validity.valueMissing) e.target.setCustomValidity('Пожалуйста, заполните это поле');
|
||||
else if (e.target.validity.typeMismatch) e.target.setCustomValidity('Пожалуйста, введите корректное значение');
|
||||
}, true);
|
||||
document.addEventListener('input', function(e) {
|
||||
if (e.target.required) e.target.setCustomValidity('');
|
||||
}, true);
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,71 +1,63 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Группы — {{ store.name }} — ЭВОСИНК{% endblock %}
|
||||
{% block title %}Группы — {{ store.name }} — Мои Товары{% endblock %}
|
||||
{% block page_title %}Каталог Эвотор{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<nav aria-label="breadcrumb" class="mb-3">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/catalog/stores">Магазины</a></li>
|
||||
<li>{{ store.name }}</li>
|
||||
<li>Группы</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/catalog/stores">Магазины</a></li>
|
||||
<li>{{ store.name }}</li>
|
||||
<li>Группы</li>
|
||||
</ol>
|
||||
|
||||
<div class="d-flex justify-between align-center mb-3">
|
||||
<h1 style="font-size:1.3rem; margin:0;"><i class="bi bi-folder me-2"></i>Группы товаров — {{ store.name }}</h1>
|
||||
<span class="text-muted small">Всего: {{ groups | length }}</span>
|
||||
<div class="pg-title">Группы товаров — {{ store.name }}</div>
|
||||
<div class="pg-sub">Управление категориями · Всего: {{ groups | length }}</div>
|
||||
|
||||
<div class="card" style="padding:0;">
|
||||
{% if groups %}
|
||||
<div class="table-wrap">
|
||||
<table class="tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Синхронизация</th>
|
||||
<th>Группа</th>
|
||||
<th>Товаров</th>
|
||||
<th>ID</th>
|
||||
<th>Обновлено</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for g in groups %}
|
||||
{% set is_enabled = (enabled_ids is none) or (g.evotor_id in enabled_ids) %}
|
||||
<tr>
|
||||
<td>
|
||||
<form method="post" action="/catalog/stores/{{ store.evotor_id }}/groups/{{ g.evotor_id }}/toggle" style="margin:0;">
|
||||
<button type="submit" class="tog {% if is_enabled %}on{% endif %}"
|
||||
title="{% if is_enabled %}Отключить синхронизацию{% else %}Включить синхронизацию{% endif %}"
|
||||
style="border:none;background:none;padding:0;cursor:pointer;"></button>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<div class="tbl-name"><i class="bi bi-folder2" style="color:#9EA8BE;margin-right:6px;"></i>{{ g.name }}</div>
|
||||
</td>
|
||||
<td><span class="mono" style="font-size:12px;">{{ product_counts.get(g.evotor_id, 0) }}</span></td>
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ g.evotor_id }}</span></td>
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ g.fetched_at | datefmt }}</span></td>
|
||||
<td>
|
||||
<a href="/catalog/stores/{{ store.evotor_id }}/products?group={{ g.evotor_id }}" class="btn btn-outline btn-xs">
|
||||
<i class="bi bi-box-seam"></i> Товары
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-folder"></i>
|
||||
<p>Группы для этого магазина ещё не загружены.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<article class="card">
|
||||
{% if groups %}
|
||||
<div class="table-scroll">
|
||||
<table class="align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Синхронизация</th>
|
||||
<th>Название</th>
|
||||
<th>Количество товаров</th>
|
||||
<th>ID</th>
|
||||
<th>Обновлено</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for g in groups %}
|
||||
{% set is_enabled = (enabled_ids is none) or (g.evotor_id in enabled_ids) %}
|
||||
<tr class="{% if not is_enabled %}text-muted{% endif %}">
|
||||
<td>
|
||||
<form method="post" action="/catalog/stores/{{ store.evotor_id }}/groups/{{ g.evotor_id }}/toggle" style="margin:0;">
|
||||
<button type="submit"
|
||||
class="outline sm {% if is_enabled %}success{% else %}secondary{% endif %}"
|
||||
title="{% if is_enabled %}Отключить синхронизацию{% else %}Включить синхронизацию{% endif %}"
|
||||
style="padding:0.2rem 0.6rem;">
|
||||
{% if is_enabled %}
|
||||
<i class="bi bi-toggle-on"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-toggle-off"></i>
|
||||
{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td><i class="bi bi-folder2 me-1 text-muted"></i> <strong>{{ g.name }}</strong></td>
|
||||
<td class="text-muted small">{{ product_counts.get(g.evotor_id, 0) }}</td>
|
||||
<td class="text-muted small">{{ g.evotor_id }}</td>
|
||||
<td class="text-muted small">{{ g.fetched_at | datefmt }}</td>
|
||||
<td>
|
||||
<a href="/catalog/stores/{{ store.evotor_id }}/products?group={{ g.evotor_id }}" role="button" class="outline sm">
|
||||
<i class="bi bi-box-seam"></i> Товары
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5 text-muted">
|
||||
<i class="bi bi-folder" style="font-size:2rem;"></i>
|
||||
<p class="mt-2">Группы для этого магазина ещё не загружены.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,83 +1,78 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Товары — {{ store.name }} — ЭВОСИНК{% endblock %}
|
||||
{% block title %}Товары — {{ store.name }} — Мои Товары{% endblock %}
|
||||
{% block page_title %}Каталог Эвотор{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<nav aria-label="breadcrumb" class="mb-3">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/catalog/stores">Магазины</a></li>
|
||||
<li>{{ store.name }}</li>
|
||||
<li>Товары</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/catalog/stores">Магазины</a></li>
|
||||
<li>{{ store.name }}</li>
|
||||
<li>Товары</li>
|
||||
</ol>
|
||||
|
||||
<div class="d-flex justify-between align-center mb-3">
|
||||
<h1 style="font-size:1.3rem; margin:0;"><i class="bi bi-box-seam me-2"></i>Товары — {{ store.name }}</h1>
|
||||
<span class="text-muted small">Всего: {{ products | length }}</span>
|
||||
</div>
|
||||
<div class="pg-title">Товары — {{ store.name }}</div>
|
||||
<div class="pg-sub">Всего: {{ products | length }}</div>
|
||||
|
||||
{% if groups %}
|
||||
<article class="card mb-3">
|
||||
<div class="card-body">
|
||||
<form method="get" class="d-flex gap-2 align-center flex-wrap">
|
||||
<select name="group" style="width:auto; margin:0;" onchange="this.form.submit()">
|
||||
<option value="">Все группы</option>
|
||||
{% for g in groups %}
|
||||
<option value="{{ g.evotor_id }}" {% if group_id == g.evotor_id %}selected{% endif %}>{{ g.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% if group_id %}
|
||||
<a href="/catalog/stores/{{ store.evotor_id }}/products" role="button" class="outline secondary sm">Сбросить</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
<div class="card" style="margin-bottom:16px;padding:14px 20px;">
|
||||
<form method="get" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
|
||||
<select class="inp" name="group" style="width:auto;" onchange="this.form.submit()">
|
||||
<option value="">Все группы</option>
|
||||
{% for g in groups %}
|
||||
<option value="{{ g.evotor_id }}" {% if group_id == g.evotor_id %}selected{% endif %}>{{ g.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% if group_id %}
|
||||
<a href="/catalog/stores/{{ store.evotor_id }}/products" class="btn btn-outline btn-sm">Сбросить</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<article class="card">
|
||||
{% if products %}
|
||||
<div class="table-scroll">
|
||||
<table class="align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Группа</th>
|
||||
<th>Артикул</th>
|
||||
<th>Цена</th>
|
||||
<th>Остаток</th>
|
||||
<th>Ед.</th>
|
||||
<th>Продаётся</th>
|
||||
<th>Обновлено</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in products %}
|
||||
<tr>
|
||||
<td>{{ p.name }}</td>
|
||||
<td class="text-muted small">{{ group_map.get(p.group_evotor_id) or '—' }}</td>
|
||||
<td class="text-muted small">{{ p.article_number or '—' }}</td>
|
||||
<td>{% if p.price is not none %}{{ p.price | price }}{% else %}—{% endif %}</td>
|
||||
<td>{% if p.quantity is not none %}{{ p.quantity }}{% else %}—{% endif %}</td>
|
||||
<td class="text-muted small">{{ p.measure_name or '—' }}</td>
|
||||
<td>
|
||||
{% if p.allow_to_sell %}
|
||||
<i class="bi bi-check-circle text-success"></i>
|
||||
{% elif p.allow_to_sell == false %}
|
||||
<i class="bi bi-x-circle text-danger"></i>
|
||||
{% else %}
|
||||
<span class="text-muted">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-muted small">{{ p.fetched_at | datefmt }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5 text-muted">
|
||||
<i class="bi bi-box-seam" style="font-size:2rem;"></i>
|
||||
<p class="mt-2">Товары не найдены.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</article>
|
||||
<div class="card" style="padding:0;">
|
||||
{% if products %}
|
||||
<div class="table-wrap">
|
||||
<table class="tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Группа</th>
|
||||
<th>Артикул</th>
|
||||
<th>Цена</th>
|
||||
<th>Остаток</th>
|
||||
<th>Ед.</th>
|
||||
<th>Продаётся</th>
|
||||
<th>Обновлено</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in products %}
|
||||
<tr>
|
||||
<td><div class="tbl-name">{{ p.name }}</div></td>
|
||||
<td style="font-size:12px;color:#9EA8BE;">{{ group_map.get(p.group_evotor_id) or '—' }}</td>
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ p.article_number or '—' }}</span></td>
|
||||
<td><span class="mono">{% if p.price is not none %}{{ p.price | price }}{% else %}—{% endif %}</span></td>
|
||||
<td><span class="mono" style="color:{% if p.quantity is not none and p.quantity == 0 %}#E53935{% else %}#1C1F2E{% endif %};">{% if p.quantity is not none %}{{ p.quantity }}{% else %}—{% endif %}</span></td>
|
||||
<td style="font-size:12px;color:#9EA8BE;">{{ p.measure_name or '—' }}</td>
|
||||
<td>
|
||||
{% if p.allow_to_sell %}
|
||||
<span class="tag tag-gr" style="font-size:10.5px;"><i class="bi bi-check-circle"></i></span>
|
||||
{% elif p.allow_to_sell == false %}
|
||||
<span class="tag tag-rd" style="font-size:10.5px;"><i class="bi bi-x-circle"></i></span>
|
||||
{% else %}
|
||||
<span style="color:#9EA8BE;">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ p.fetched_at | datefmt }}</span></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<p>Товары не найдены.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,66 +1,62 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Магазины — ЭВОСИНК{% endblock %}
|
||||
{% block title %}Магазины — Мои Товары{% endblock %}
|
||||
{% block page_title %}Каталог Эвотор{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-between align-center mb-3">
|
||||
<h1 style="font-size:1.3rem; margin:0;"><i class="bi bi-shop me-2"></i>Магазины Эвотор</h1>
|
||||
<span class="text-muted small">Всего: {{ stores | length }}</span>
|
||||
</div>
|
||||
<div class="pg-title">Магазины Эвотор</div>
|
||||
<div class="pg-sub">Выберите магазины для синхронизации · Всего: {{ stores | length }}</div>
|
||||
|
||||
<article class="card">
|
||||
{% if stores %}
|
||||
<div class="table-scroll">
|
||||
<table class="align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Синхронизация</th>
|
||||
<th>Название</th>
|
||||
<th>Адрес</th>
|
||||
<th>ID</th>
|
||||
<th>Обновлено</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in stores %}
|
||||
{% set is_enabled = (enabled_ids is none) or (s.evotor_id in enabled_ids) %}
|
||||
<tr class="{% if not is_enabled %}text-muted{% endif %}">
|
||||
<td>
|
||||
<form method="post" action="/catalog/stores/{{ s.evotor_id }}/toggle" style="margin:0;">
|
||||
<button type="submit"
|
||||
class="outline sm {% if is_enabled %}success{% else %}secondary{% endif %}"
|
||||
title="{% if is_enabled %}Отключить синхронизацию{% else %}Включить синхронизацию{% endif %}"
|
||||
style="padding:0.2rem 0.6rem;">
|
||||
{% if is_enabled %}
|
||||
<i class="bi bi-toggle-on"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-toggle-off"></i>
|
||||
{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td><strong>{{ s.name }}</strong></td>
|
||||
<td class="text-muted">{{ s.address or '—' }}</td>
|
||||
<td class="text-muted small">{{ s.evotor_id }}</td>
|
||||
<td class="text-muted small">{{ s.fetched_at | datefmt }}</td>
|
||||
<td>
|
||||
<a href="/catalog/stores/{{ s.evotor_id }}/products" role="button" class="outline sm" title="Товары">
|
||||
<i class="bi bi-box-seam"></i> Товары
|
||||
</a>
|
||||
<a href="/catalog/stores/{{ s.evotor_id }}/groups" role="button" class="outline secondary sm" title="Группы">
|
||||
<i class="bi bi-folder"></i> Группы
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5 text-muted">
|
||||
<i class="bi bi-shop" style="font-size:2rem;"></i>
|
||||
<p class="mt-2">Магазины ещё не загружены.<br>Синхронизация выполняется каждые {{ refresh_interval }} сек. автоматически.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</article>
|
||||
<div class="card" style="padding:0;">
|
||||
{% if stores %}
|
||||
<div class="table-wrap">
|
||||
<table class="tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Синхронизация</th>
|
||||
<th>Название</th>
|
||||
<th>Адрес</th>
|
||||
<th>ID</th>
|
||||
<th>Обновлено</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in stores %}
|
||||
{% set is_enabled = (enabled_ids is none) or (s.evotor_id in enabled_ids) %}
|
||||
<tr>
|
||||
<td>
|
||||
<form method="post" action="/catalog/stores/{{ s.evotor_id }}/toggle" style="margin:0;">
|
||||
<button type="submit" class="tog {% if is_enabled %}on{% endif %}"
|
||||
title="{% if is_enabled %}Отключить синхронизацию{% else %}Включить синхронизацию{% endif %}"
|
||||
style="border:none;background:none;padding:0;cursor:pointer;"></button>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<div class="tbl-name">{{ s.name }}</div>
|
||||
</td>
|
||||
<td style="color:#9EA8BE;font-size:12px;">{{ s.address or '—' }}</td>
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ s.evotor_id }}</span></td>
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ s.fetched_at | datefmt }}</span></td>
|
||||
<td>
|
||||
<div style="display:flex;gap:6px;">
|
||||
<a href="/catalog/stores/{{ s.evotor_id }}/products" class="btn btn-outline btn-xs">
|
||||
<i class="bi bi-box-seam"></i> Товары
|
||||
</a>
|
||||
<a href="/catalog/stores/{{ s.evotor_id }}/groups" class="btn btn-outline btn-xs">
|
||||
<i class="bi bi-folder"></i> Группы
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-shop"></i>
|
||||
<p>Магазины ещё не загружены.<br>Синхронизация выполняется каждые {{ refresh_interval }} сек. автоматически.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Подтверждение email — ЭВОСИНК{% endblock %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Подтверждение email — Мои Товары</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Golos+Text:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body style="display:flex;align-items:center;justify-content:center;min-height:100vh;background:#F4F5F7;">
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-center">
|
||||
<div class="col-sm-10 col-md-6 col-lg-5">
|
||||
<article class="card mt-5 text-center">
|
||||
<div class="card-body" style="padding: 2.5rem;">
|
||||
<i class="bi bi-envelope-check fs-1 text-primary mb-3 d-block"></i>
|
||||
<h1 style="font-size:1.3rem;" class="mb-3">Подтвердите ваш email</h1>
|
||||
<p class="text-muted">Проверьте почту и нажмите на ссылку для подтверждения.</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="card" style="max-width:400px;width:100%;padding:40px 32px;text-align:center;">
|
||||
<i class="bi bi-envelope-check" style="font-size:48px;color:#FF5500;display:block;margin-bottom:16px;"></i>
|
||||
<div class="pg-title" style="margin-bottom:8px;">Подтвердите ваш email</div>
|
||||
<div style="font-size:13px;color:#5C6278;">Проверьте почту и нажмите на ссылку для подтверждения.</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,210 +1,212 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Подключения — ЭВОСИНК{% endblock %}
|
||||
{% block title %}Подключения — Мои Товары{% endblock %}
|
||||
{% block page_title %}Подключения{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-center">
|
||||
<div class="col-sm-10 col-md-8 col-lg-6">
|
||||
<div class="pg-title">Подключения</div>
|
||||
<div class="pg-sub">Управление интеграциями с Эвотор и VK Market</div>
|
||||
|
||||
<h1 style="font-size:1.3rem; margin-bottom:1.5rem;">
|
||||
<i class="bi bi-plug me-2"></i>Подключения
|
||||
</h1>
|
||||
{% if request.query_params.get('success') %}
|
||||
<div class="alert alert-gr">
|
||||
<span><i class="bi bi-check-circle"></i></span>
|
||||
<div>Подключение сохранено.</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if request.query_params.get('success') %}
|
||||
<div role="alert" class="alert alert-success mb-3">
|
||||
<p>Подключение сохранено.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# ── Evotor ── #}
|
||||
<article class="card mb-4">
|
||||
<header class="d-flex align-center justify-between">
|
||||
<span><i class="bi bi-cpu me-2"></i><strong>Эвотор</strong></span>
|
||||
{% if evotor %}
|
||||
<span class="badge badge-success"><i class="bi bi-check-circle me-1"></i>Подключено</span>
|
||||
{% else %}
|
||||
<span class="badge badge-secondary">Не подключено</span>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
{% if evotor %}
|
||||
<ul class="list-group mb-3">
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Токен</span>
|
||||
<span class="font-monospace small">{{ evotor.access_token[:8] }}••••••••</span>
|
||||
</li>
|
||||
{% if evotor.evotor_user_id %}
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Evotor User ID</span>
|
||||
<span class="font-monospace small">{{ evotor.evotor_user_id }}</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Подключено</span>
|
||||
<span>{{ evotor.connected_at | datefmt }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Обновлено</span>
|
||||
<span>{{ evotor.updated_at | datefmt }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<div class="card-body">
|
||||
<details {% if not evotor %}open{% endif %}>
|
||||
<summary>
|
||||
{% if evotor %}Обновить токен{% else %}Ввести API-токен{% endif %}
|
||||
</summary>
|
||||
<form method="post" action="/connections/evotor" class="mt-3">
|
||||
<label>
|
||||
API-токен Эвотор
|
||||
<input type="text" name="access_token"
|
||||
placeholder="Вставьте токен из личного кабинета Эвотор"
|
||||
value="{{ evotor.access_token if evotor else '' }}"
|
||||
required autocomplete="off">
|
||||
</label>
|
||||
<label>
|
||||
Evotor User ID <span class="text-muted small">(необязательно)</span>
|
||||
<input type="text" name="evotor_user_id"
|
||||
placeholder="Например: 01234567-89ab-cdef-0123-456789abcdef"
|
||||
value="{{ evotor.evotor_user_id if evotor and evotor.evotor_user_id else '' }}"
|
||||
autocomplete="off">
|
||||
</label>
|
||||
<button type="submit">
|
||||
<i class="bi bi-save me-1"></i>Сохранить
|
||||
</button>
|
||||
</form>
|
||||
</details>
|
||||
|
||||
{% if evotor %}
|
||||
<div class="d-flex gap-2 mt-3" style="flex-wrap:wrap; align-items:center;">
|
||||
<button type="button" class="outline sm" onclick="testConnection('evotor', this)">
|
||||
<i class="bi bi-wifi me-1"></i>Проверить соединение
|
||||
</button>
|
||||
<span id="evotor-test-result" class="small"></span>
|
||||
</div>
|
||||
<form method="post" action="/connections/evotor/disconnect"
|
||||
class="mt-2"
|
||||
onsubmit="return confirm('Отключить Эвотор? Кешированные данные каталога останутся.')">
|
||||
<button type="submit" class="outline danger sm">
|
||||
<i class="bi bi-plug me-1"></i>Отключить
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
{# ── VK ── #}
|
||||
<article class="card mb-4">
|
||||
<header class="d-flex align-center justify-between">
|
||||
<span><i class="bi bi-badge-vr me-2"></i><strong>ВКонтакте (Маркет)</strong></span>
|
||||
{% if vk %}
|
||||
<span class="badge badge-success"><i class="bi bi-check-circle me-1"></i>Подключено</span>
|
||||
{% else %}
|
||||
<span class="badge badge-secondary">Не подключено</span>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
{% if vk %}
|
||||
<ul class="list-group mb-3">
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Токен</span>
|
||||
<span class="font-monospace small">{{ vk.access_token[:8] }}••••••••</span>
|
||||
</li>
|
||||
{% if vk.vk_user_id %}
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">ID сообщества</span>
|
||||
<span class="font-monospace small">{{ vk.vk_user_id }}</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if vk.first_name or vk.last_name %}
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Аккаунт</span>
|
||||
<span>{{ vk.first_name }} {{ vk.last_name }}</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Подключено</span>
|
||||
<span>{{ vk.connected_at | datefmt }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Обновлено</span>
|
||||
<span>{{ vk.updated_at | datefmt }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<div class="card-body">
|
||||
<div class="mt-3">
|
||||
<a href="/vk-auth" role="button">
|
||||
<i class="bi bi-box-arrow-in-right me-1"></i>
|
||||
{% if vk %}Переподключить ВКонтакте{% else %}Войти через ВКонтакте{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<details class="mt-3">
|
||||
<summary class="text-muted small">Ввести токен вручную</summary>
|
||||
<form method="post" action="/connections/vk" class="mt-2">
|
||||
<label>
|
||||
Токен доступа VK
|
||||
<input type="text" name="access_token"
|
||||
placeholder="vk1.a.xxxxxxxxxxxxxxxx…"
|
||||
value="{{ vk.access_token if vk else '' }}"
|
||||
required autocomplete="off">
|
||||
</label>
|
||||
<label>
|
||||
ID сообщества ВКонтакте
|
||||
<input type="text" name="vk_group_id"
|
||||
placeholder="Например: 229744980"
|
||||
value="{{ vk.vk_user_id if vk and vk.vk_user_id else '' }}"
|
||||
autocomplete="off">
|
||||
<small class="text-muted">Числовой ID группы/паблика с включённым Маркетом (без минуса)</small>
|
||||
</label>
|
||||
<button type="submit">
|
||||
<i class="bi bi-save me-1"></i>Сохранить
|
||||
</button>
|
||||
</form>
|
||||
</details>
|
||||
|
||||
{% if vk %}
|
||||
<div class="d-flex gap-2 mt-3" style="flex-wrap:wrap; align-items:center;">
|
||||
<button type="button" class="outline sm" onclick="testConnection('vk', this)">
|
||||
<i class="bi bi-wifi me-1"></i>Проверить соединение
|
||||
</button>
|
||||
<span id="vk-test-result" class="small"></span>
|
||||
</div>
|
||||
<form method="post" action="/connections/vk/disconnect"
|
||||
class="mt-2"
|
||||
onsubmit="return confirm('Отключить ВКонтакте?')">
|
||||
<button type="submit" class="outline danger sm">
|
||||
<i class="bi bi-plug me-1"></i>Отключить
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
<div class="g2" style="align-items:start;">
|
||||
|
||||
{# ── Evotor ── #}
|
||||
<div class="card">
|
||||
<div class="card-hd">
|
||||
<div>
|
||||
<div class="card-title"><i class="bi bi-cpu" style="margin-right:6px;"></i>Эвотор</div>
|
||||
<div class="card-sub">Платформа кассовых решений и товарного учёта</div>
|
||||
</div>
|
||||
{% if evotor %}
|
||||
<span class="tag tag-gr"><span class="dot g"></span>Подключено</span>
|
||||
{% else %}
|
||||
<span class="tag tag-dim"><span class="dot d"></span>Не подключено</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if evotor %}
|
||||
<div class="conn-detail" style="margin-bottom:16px;">
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Токен</span>
|
||||
<span class="conn-v">{{ evotor.access_token[:8] }}••••••••</span>
|
||||
</div>
|
||||
{% if evotor.evotor_user_id %}
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Evotor User ID</span>
|
||||
<span class="conn-v">{{ evotor.evotor_user_id }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Подключено</span>
|
||||
<span class="conn-v">{{ evotor.connected_at | datefmt }}</span>
|
||||
</div>
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Обновлено</span>
|
||||
<span class="conn-v">{{ evotor.updated_at | datefmt }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<details {% if not evotor %}open{% endif %} style="margin-bottom:14px;">
|
||||
<summary class="btn btn-outline btn-sm" style="cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:6px;">
|
||||
<i class="bi bi-pencil"></i>
|
||||
{% if evotor %}Обновить токен{% else %}Ввести API-токен{% endif %}
|
||||
</summary>
|
||||
<form method="post" action="/connections/evotor" style="margin-top:12px;">
|
||||
<div class="form-row">
|
||||
<label class="form-lbl">API-токен Эвотор</label>
|
||||
<input class="inp" type="text" name="access_token"
|
||||
placeholder="Вставьте токен из личного кабинета Эвотор"
|
||||
value="{{ evotor.access_token if evotor else '' }}"
|
||||
required autocomplete="off">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl">Evotor User ID <span style="font-weight:400;text-transform:none;letter-spacing:0;">(необязательно)</span></label>
|
||||
<input class="inp" type="text" name="evotor_user_id"
|
||||
placeholder="Например: 01234567-89ab-cdef-0123-456789abcdef"
|
||||
value="{{ evotor.evotor_user_id if evotor and evotor.evotor_user_id else '' }}"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-save"></i> Сохранить
|
||||
</button>
|
||||
</form>
|
||||
</details>
|
||||
|
||||
{% if evotor %}
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
||||
<button type="button" class="btn btn-outline btn-sm" onclick="testConnection('evotor', this)">
|
||||
<i class="bi bi-wifi"></i> Проверить соединение
|
||||
</button>
|
||||
<span id="evotor-test-result" style="font-size:12px;"></span>
|
||||
</div>
|
||||
<form method="post" action="/connections/evotor/disconnect" style="margin-top:10px;"
|
||||
onsubmit="return confirm('Отключить Эвотор? Кешированные данные каталога останутся.')">
|
||||
<button type="submit" class="btn btn-danger btn-sm">
|
||||
<i class="bi bi-plug"></i> Отключить
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# ── VK ── #}
|
||||
<div class="card">
|
||||
<div class="card-hd">
|
||||
<div>
|
||||
<div class="card-title"><i class="bi bi-badge-vr" style="margin-right:6px;"></i>ВКонтакте (Маркет)</div>
|
||||
<div class="card-sub">market.* API, версия 5.199</div>
|
||||
</div>
|
||||
{% if vk %}
|
||||
<span class="tag tag-gr"><span class="dot g"></span>Подключено</span>
|
||||
{% else %}
|
||||
<span class="tag tag-dim"><span class="dot d"></span>Не подключено</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if vk %}
|
||||
<div class="conn-detail" style="margin-bottom:16px;">
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Токен</span>
|
||||
<span class="conn-v">{{ vk.access_token[:8] }}••••••••</span>
|
||||
</div>
|
||||
{% if vk.vk_user_id %}
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">ID сообщества</span>
|
||||
<span class="conn-v">{{ vk.vk_user_id }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if vk.first_name or vk.last_name %}
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Аккаунт</span>
|
||||
<span class="conn-v">{{ vk.first_name }} {{ vk.last_name }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Подключено</span>
|
||||
<span class="conn-v">{{ vk.connected_at | datefmt }}</span>
|
||||
</div>
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Обновлено</span>
|
||||
<span class="conn-v">{{ vk.updated_at | datefmt }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div style="margin-bottom:14px;">
|
||||
<a href="/vk-auth" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-box-arrow-in-right"></i>
|
||||
{% if vk %}Переподключить ВКонтакте{% else %}Войти через ВКонтакте{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<details style="margin-bottom:14px;">
|
||||
<summary class="btn btn-outline btn-sm" style="cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:6px;">
|
||||
<i class="bi bi-key"></i> Ввести токен вручную
|
||||
</summary>
|
||||
<form method="post" action="/connections/vk" style="margin-top:12px;">
|
||||
<div class="form-row">
|
||||
<label class="form-lbl">Токен доступа VK</label>
|
||||
<input class="inp" type="text" name="access_token"
|
||||
placeholder="vk1.a.xxxxxxxxxxxxxxxx…"
|
||||
value="{{ vk.access_token if vk else '' }}"
|
||||
required autocomplete="off">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl">ID сообщества ВКонтакте</label>
|
||||
<input class="inp" type="text" name="vk_group_id"
|
||||
placeholder="Например: 229744980"
|
||||
value="{{ vk.vk_user_id if vk and vk.vk_user_id else '' }}"
|
||||
autocomplete="off">
|
||||
<div style="font-size:11px;color:#9EA8BE;margin-top:4px;">Числовой ID группы/паблика с включённым Маркетом (без минуса)</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm">
|
||||
<i class="bi bi-save"></i> Сохранить
|
||||
</button>
|
||||
</form>
|
||||
</details>
|
||||
|
||||
{% if vk %}
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
||||
<button type="button" class="btn btn-outline btn-sm" onclick="testConnection('vk', this)">
|
||||
<i class="bi bi-wifi"></i> Проверить соединение
|
||||
</button>
|
||||
<span id="vk-test-result" style="font-size:12px;"></span>
|
||||
</div>
|
||||
<form method="post" action="/connections/vk/disconnect" style="margin-top:10px;"
|
||||
onsubmit="return confirm('Отключить ВКонтакте?')">
|
||||
<button type="submit" class="btn btn-danger btn-sm">
|
||||
<i class="bi bi-plug"></i> Отключить
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
async function testConnection(provider, btn) {
|
||||
const resultEl = document.getElementById(provider + '-test-result');
|
||||
btn.disabled = true;
|
||||
resultEl.textContent = 'Проверяем…';
|
||||
resultEl.style.color = '';
|
||||
try {
|
||||
const resp = await fetch('/connections/' + provider + '/test', {method: 'POST'});
|
||||
const data = await resp.json();
|
||||
resultEl.textContent = data.message;
|
||||
resultEl.style.color = data.ok ? 'var(--pico-color-green-500, #2d8a4e)' : 'var(--pico-color-red-500, #c0392b)';
|
||||
} catch (e) {
|
||||
resultEl.textContent = 'Ошибка сети';
|
||||
resultEl.style.color = 'var(--pico-color-red-500, #c0392b)';
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
const resultEl = document.getElementById(provider + '-test-result');
|
||||
btn.disabled = true;
|
||||
resultEl.textContent = 'Проверяем…';
|
||||
resultEl.style.color = '';
|
||||
try {
|
||||
const resp = await fetch('/connections/' + provider + '/test', {method: 'POST'});
|
||||
const data = await resp.json();
|
||||
resultEl.textContent = data.message;
|
||||
resultEl.style.color = data.ok ? '#17A865' : '#E53935';
|
||||
} catch (e) {
|
||||
resultEl.textContent = 'Ошибка сети';
|
||||
resultEl.style.color = '#E53935';
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Email подтвержден — ЭВОСИНК{% endblock %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Email подтверждён — Мои Товары</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Golos+Text:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body style="display:flex;align-items:center;justify-content:center;min-height:100vh;background:#F4F5F7;">
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-center">
|
||||
<div class="col-sm-10 col-md-6 col-lg-5">
|
||||
<article class="card mt-5 text-center">
|
||||
<div class="card-body" style="padding: 2.5rem;">
|
||||
<i class="bi bi-check-circle fs-1 text-success mb-3 d-block"></i>
|
||||
<h1 style="font-size:1.3rem;" class="mb-3">Email подтвержден!</h1>
|
||||
<p class="text-muted">Ваш email успешно подтвержден. Теперь вы можете войти в систему.</p>
|
||||
<a href="/login" role="button" class="mt-2">Войти</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="card" style="max-width:400px;width:100%;padding:40px 32px;text-align:center;">
|
||||
<i class="bi bi-check-circle" style="font-size:48px;color:#17A865;display:block;margin-bottom:16px;"></i>
|
||||
<div class="pg-title" style="margin-bottom:8px;">Email подтверждён!</div>
|
||||
<div style="font-size:13px;color:#5C6278;margin-bottom:20px;">Ваш email успешно подтверждён. Теперь вы можете войти в систему.</div>
|
||||
<a href="/login" class="btn btn-primary">Войти</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,24 +1,48 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Забыли пароль — ЭВОСИНК{% endblock %}
|
||||
<!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="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Golos+Text:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body style="display:flex;align-items:center;justify-content:center;min-height:100vh;background:#F4F5F7;">
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-center">
|
||||
<div class="col-sm-10 col-md-6 col-lg-5">
|
||||
<article class="card mt-4">
|
||||
<div class="card-body">
|
||||
<h1 style="font-size:1.3rem;" class="mb-2">Забыли пароль?</h1>
|
||||
<p class="text-muted small mb-4">Введите email, указанный при регистрации.</p>
|
||||
<form method="post" action="/forgot-password">
|
||||
<label for="email">Email
|
||||
<input type="email" id="email" name="email" required>
|
||||
</label>
|
||||
<button type="submit" class="w-100">Отправить ссылку для сброса</button>
|
||||
</form>
|
||||
<div class="text-center small mt-3">
|
||||
<a href="/login">Вернуться ко входу</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<div class="card" style="max-width:400px;width:100%;padding:32px;">
|
||||
<div style="text-align:center;margin-bottom:24px;">
|
||||
<div style="width:44px;height:44px;border-radius:11px;background:#FF5500;display:flex;align-items:center;justify-content:center;margin:0 auto 16px;">
|
||||
<svg width="24" height="24" viewBox="0 0 28 28" fill="none">
|
||||
<path d="M9 11h10l-1.5 9H10.5L9 11Z" fill="white" opacity="0.95"/>
|
||||
<path d="M11.5 11V9a2.5 2.5 0 015 0v2" stroke="white" stroke-width="1.6" stroke-linecap="round" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="login-box-title">Забыли пароль?</div>
|
||||
<div style="font-size:13px;color:#5C6278;margin-top:4px;">Введите email, указанный при регистрации</div>
|
||||
</div>
|
||||
|
||||
{% if errors %}
|
||||
<div class="alert alert-rd" style="margin-bottom:14px;">
|
||||
<span><i class="bi bi-x-circle"></i></span>
|
||||
<div>{% for e in errors %}<div>{{ e }}</div>{% endfor %}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/forgot-password">
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="email">Email</label>
|
||||
<input class="inp" type="email" id="email" name="email" placeholder="you@store.ru" required>
|
||||
</div>
|
||||
<button type="submit" class="login-btn">Отправить ссылку для сброса</button>
|
||||
</form>
|
||||
|
||||
<div class="login-hint" style="margin-top:16px;">
|
||||
<a href="/login">← Вернуться ко входу</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,44 +1,79 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Завершение регистрации — ЭВОСИНК{% endblock %}
|
||||
<!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="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Golos+Text:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body style="display:flex;align-items:center;justify-content:center;min-height:100vh;background:#F4F5F7;padding:24px;">
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-center">
|
||||
<div class="col-sm-10 col-md-7 col-lg-6">
|
||||
<article class="card mt-4">
|
||||
<header>
|
||||
<h1><i class="bi bi-person-plus me-2"></i>Добро пожаловать в ЭВОСИНК!</h1>
|
||||
</header>
|
||||
<div class="card-body">
|
||||
<p class="text-muted mb-4">Ваш аккаунт был создан через Эвотор. Заполните данные профиля и задайте пароль для входа.</p>
|
||||
<form method="post" action="/invite?token={{ token }}">
|
||||
<div class="row gap-2 mb-2">
|
||||
<div class="col">
|
||||
<label for="first_name">Имя <span class="text-danger">*</span>
|
||||
<input type="text" id="first_name" name="first_name" value="{{ form.first_name if form else (invite_user.first_name or '') }}" required>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="last_name">Фамилия <span class="text-danger">*</span>
|
||||
<input type="text" id="last_name" name="last_name" value="{{ form.last_name if form else (invite_user.last_name or '') }}" required>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<label for="email">Email <span class="text-danger">*</span>
|
||||
<input type="email" id="email" name="email" value="{{ form.email if form else (invite_user.email or '') }}" required>
|
||||
</label>
|
||||
<label for="phone">Телефон <span class="text-danger">*</span>
|
||||
<input type="tel" id="phone" name="phone" value="{{ form.phone if form else (invite_user.phone or '') }}" required>
|
||||
</label>
|
||||
<label for="password">Пароль <span class="text-danger">*</span>
|
||||
<input type="password" id="password" name="password" required minlength="8">
|
||||
</label>
|
||||
<label for="password_confirm">Подтверждение пароля <span class="text-danger">*</span>
|
||||
<input type="password" id="password_confirm" name="password_confirm" required>
|
||||
</label>
|
||||
<button type="submit" class="w-100">Завершить регистрацию</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
<div class="card" style="max-width:480px;width:100%;padding:32px;">
|
||||
<div style="text-align:center;margin-bottom:24px;">
|
||||
<div style="width:44px;height:44px;border-radius:11px;background:#FF5500;display:flex;align-items:center;justify-content:center;margin:0 auto 16px;">
|
||||
<svg width="24" height="24" viewBox="0 0 28 28" fill="none">
|
||||
<path d="M9 11h10l-1.5 9H10.5L9 11Z" fill="white" opacity="0.95"/>
|
||||
<path d="M11.5 11V9a2.5 2.5 0 015 0v2" stroke="white" stroke-width="1.6" stroke-linecap="round" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="login-box-title">Добро пожаловать!</div>
|
||||
<div style="font-size:13px;color:#5C6278;margin-top:4px;">Ваш аккаунт создан через Эвотор. Заполните профиль и задайте пароль для входа.</div>
|
||||
</div>
|
||||
|
||||
{% if errors %}
|
||||
<div class="alert alert-rd" style="margin-bottom:14px;">
|
||||
<span><i class="bi bi-x-circle"></i></span>
|
||||
<div>{% for e in errors %}<div>{{ e }}</div>{% endfor %}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/invite?token={{ token }}">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="first_name">Имя <span style="color:#E53935;">*</span></label>
|
||||
<input class="inp" type="text" id="first_name" name="first_name"
|
||||
value="{{ form.first_name if form else (invite_user.first_name or '') }}" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="last_name">Фамилия <span style="color:#E53935;">*</span></label>
|
||||
<input class="inp" type="text" id="last_name" name="last_name"
|
||||
value="{{ form.last_name if form else (invite_user.last_name or '') }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="email">Email <span style="color:#E53935;">*</span></label>
|
||||
<input class="inp" type="email" id="email" name="email"
|
||||
value="{{ form.email if form else (invite_user.email or '') }}" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="phone">Телефон <span style="color:#E53935;">*</span></label>
|
||||
<input class="inp" type="tel" id="phone" name="phone"
|
||||
value="{{ form.phone if form else (invite_user.phone or '') }}" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="password">Пароль <span style="color:#E53935;">*</span></label>
|
||||
<input class="inp" type="password" id="password" name="password" required minlength="8">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="password_confirm">Подтверждение пароля <span style="color:#E53935;">*</span></label>
|
||||
<input class="inp" type="password" id="password_confirm" name="password_confirm" required>
|
||||
</div>
|
||||
<button type="submit" class="login-btn">Завершить регистрацию</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/inputmask@5.0.9/dist/inputmask.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var phoneInputs = document.querySelectorAll('input[name="phone"]');
|
||||
if (phoneInputs.length) {
|
||||
Inputmask('+7 (999) 999-99-99', { placeholder: '_', showMaskOnHover: false, clearMaskOnLostFocus: false }).mask(phoneInputs);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,26 +1,105 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Вход — ЭВОСИНК{% endblock %}
|
||||
<!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="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Golos+Text:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-center">
|
||||
<div class="col-sm-10 col-md-6 col-lg-5">
|
||||
<article class="card mt-4">
|
||||
<div class="card-body">
|
||||
<h1 class="mb-4" style="font-size:1.3rem;">Вход</h1>
|
||||
<form method="post" action="/login">
|
||||
<label for="email">Email
|
||||
<input type="email" id="email" name="email" value="{{ form.email if form else '' }}" required>
|
||||
</label>
|
||||
<label for="password">Пароль
|
||||
<input type="password" id="password" name="password" required>
|
||||
</label>
|
||||
<button type="submit" class="w-100">Войти</button>
|
||||
</form>
|
||||
<div class="text-center small mt-3">
|
||||
<a href="/forgot-password">Забыли пароль?</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<div class="login-wrap">
|
||||
|
||||
<!-- Left panel -->
|
||||
<div class="login-left">
|
||||
<div class="login-left-bg"></div>
|
||||
<div class="login-left-pattern"></div>
|
||||
|
||||
<div class="login-brand">
|
||||
<div class="login-brand-icon">
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
|
||||
<path d="M9 11h10l-1.5 9H10.5L9 11Z" fill="white" opacity="0.95"/>
|
||||
<path d="M11.5 11V9a2.5 2.5 0 015 0v2" stroke="white" stroke-width="1.6" stroke-linecap="round" fill="none"/>
|
||||
<line x1="12" y1="15" x2="16" y2="15" stroke="#FF5500" stroke-width="1.3" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="login-brand-name">Мои Товары</div>
|
||||
<div class="login-brand-sub">мои-товары.рф</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="login-hero">
|
||||
<div class="login-hero-title">
|
||||
Управляйте товарами<br>и продавайте<br><em>везде сразу</em>
|
||||
</div>
|
||||
<div class="login-hero-body">
|
||||
Синхронизируйте ассортимент вашего магазина Эвотор с онлайн-площадками — быстро, без ручной работы.
|
||||
</div>
|
||||
<div class="login-chips">
|
||||
<div class="login-chip"><i class="bi bi-shop"></i> Эвотор</div>
|
||||
<div class="login-chip"><i class="bi bi-badge-vr"></i> VK Market</div>
|
||||
<div class="login-chip"><i class="bi bi-arrow-repeat"></i> Авто-синхронизация</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="login-footer">© 2025 Мои Товары · мои-товары.рф</div>
|
||||
</div>
|
||||
|
||||
<!-- Right panel -->
|
||||
<div class="login-right">
|
||||
<div class="login-box">
|
||||
<div style="margin-bottom:28px;">
|
||||
<div style="margin-bottom:18px;">
|
||||
<div style="width:36px;height:36px;border-radius:9px;background:#FF5500;display:flex;align-items:center;justify-content:center;">
|
||||
<svg width="20" height="20" viewBox="0 0 28 28" fill="none">
|
||||
<path d="M9 11h10l-1.5 9H10.5L9 11Z" fill="white" opacity="0.95"/>
|
||||
<path d="M11.5 11V9a2.5 2.5 0 015 0v2" stroke="white" stroke-width="1.6" stroke-linecap="round" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-box-title">Вход в аккаунт</div>
|
||||
<div class="login-box-sub">Введите данные, полученные после регистрации</div>
|
||||
</div>
|
||||
|
||||
{% if errors %}
|
||||
<div class="alert alert-rd" style="margin-bottom:14px;">
|
||||
<span><i class="bi bi-x-circle"></i></span>
|
||||
<div>{% for e in errors %}<div>{{ e }}</div>{% endfor %}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/login">
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="email">Email</label>
|
||||
<input class="inp" type="email" id="email" name="email"
|
||||
placeholder="you@store.ru"
|
||||
value="{{ form.email if form else '' }}" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="password">Пароль</label>
|
||||
<input class="inp" type="password" id="password" name="password"
|
||||
placeholder="••••••••" required>
|
||||
</div>
|
||||
<button type="submit" class="login-btn">Войти →</button>
|
||||
</form>
|
||||
|
||||
<div class="login-hint" style="margin-top:16px;">
|
||||
<a href="/forgot-password">Забыли пароль?</a>
|
||||
</div>
|
||||
<div class="login-hint">
|
||||
Нет аккаунта? Купите приложение на
|
||||
<a href="https://market.evotor.ru" target="_blank" rel="noreferrer">Эвотор.Маркет</a>
|
||||
и получите доступ автоматически.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ title }} — ЭВОСИНК{% endblock %}
|
||||
{% block title %}{{ title }} — Мои Товары{% endblock %}
|
||||
{% block page_title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-center">
|
||||
<div class="col-sm-10 col-md-6 col-lg-5">
|
||||
<article class="card mt-5 text-center">
|
||||
<div class="card-body" style="padding: 2.5rem;">
|
||||
<h1 style="font-size:1.3rem;" class="mb-3">{{ title }}</h1>
|
||||
<p class="text-muted">{{ message }}</p>
|
||||
{% if link %}
|
||||
<a href="{{ link }}" role="button" class="mt-2">{{ link_text }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh;">
|
||||
<div class="card" style="max-width:440px;width:100%;text-align:center;padding:40px 32px;">
|
||||
<div style="font-size:48px;margin-bottom:16px;">
|
||||
{% if 'ошибка' in title|lower or 'error' in title|lower %}
|
||||
<i class="bi bi-x-circle" style="color:#E53935;"></i>
|
||||
{% elif 'успешно' in title|lower or 'готово' in title|lower or 'подтвержден' in title|lower %}
|
||||
<i class="bi bi-check-circle" style="color:#17A865;"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-info-circle" style="color:#3B6FFF;"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="pg-title" style="margin-bottom:8px;">{{ title }}</div>
|
||||
<div style="font-size:13px;color:#5C6278;margin-bottom:20px;">{{ message }}</div>
|
||||
{% if link %}
|
||||
<a href="{{ link }}" class="btn btn-primary">{{ link_text }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Изменить пароль — ЭВОСИНК{% endblock %}
|
||||
{% block title %}Изменить пароль — Мои Товары{% endblock %}
|
||||
{% block page_title %}Изменить пароль{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-center">
|
||||
<div class="col-sm-10 col-md-6 col-lg-5">
|
||||
<article class="card mt-4">
|
||||
<header>
|
||||
<h1><i class="bi bi-key me-2"></i>Изменить пароль</h1>
|
||||
</header>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/profile/change-password">
|
||||
<label for="current_password">Текущий пароль
|
||||
<input type="password" id="current_password" name="current_password" required>
|
||||
</label>
|
||||
<label for="password">Новый пароль
|
||||
<input type="password" id="password" name="password" required>
|
||||
</label>
|
||||
<label for="password_confirm">Подтвердить пароль
|
||||
<input type="password" id="password_confirm" name="password_confirm" required>
|
||||
</label>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit">Изменить пароль</button>
|
||||
<a href="/profile" role="button" class="outline secondary">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
<div class="pg-title">Изменить пароль</div>
|
||||
<div class="pg-sub">Обновите пароль для входа в систему</div>
|
||||
|
||||
<div class="card" style="max-width:440px;">
|
||||
<form method="post" action="/profile/change-password">
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="current_password">Текущий пароль</label>
|
||||
<input class="inp" type="password" id="current_password" name="current_password" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="password">Новый пароль</label>
|
||||
<input class="inp" type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="password_confirm">Подтвердить пароль</label>
|
||||
<input class="inp" type="password" id="password_confirm" name="password_confirm" required>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-key"></i> Изменить пароль
|
||||
</button>
|
||||
<a href="/profile" class="btn btn-outline">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Удалить аккаунт — ЭВОСИНК{% endblock %}
|
||||
{% block title %}Удалить аккаунт — Мои Товары{% endblock %}
|
||||
{% block page_title %}Удалить аккаунт{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-center">
|
||||
<div class="col-sm-10 col-md-6 col-lg-5">
|
||||
<article class="card mt-4" style="border-color: #dc2626;">
|
||||
<header class="bg-danger-header">
|
||||
<h1><i class="bi bi-trash me-2"></i>Удалить аккаунт</h1>
|
||||
</header>
|
||||
<div class="card-body">
|
||||
<div role="alert" class="alert alert-warning mb-3">
|
||||
<i class="bi bi-exclamation-triangle me-1"></i>
|
||||
<strong>Внимание!</strong> Это действие необратимо. Все ваши данные будут удалены.
|
||||
</div>
|
||||
<form method="post" action="/profile/delete">
|
||||
<label for="password">Введите пароль для подтверждения
|
||||
<input type="password" id="password" name="password" required>
|
||||
</label>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="danger">
|
||||
<i class="bi bi-trash me-1"></i>Удалить мой аккаунт
|
||||
</button>
|
||||
<a href="/profile" role="button" class="outline secondary">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
<div class="pg-title">Удалить аккаунт</div>
|
||||
<div class="pg-sub">Это действие необратимо</div>
|
||||
|
||||
<div class="card" style="max-width:440px;border-color:#F4AEAE;">
|
||||
<div class="alert alert-yl" style="margin-bottom:18px;">
|
||||
<span><i class="bi bi-exclamation-triangle"></i></span>
|
||||
<div><strong>Внимание!</strong> Все ваши данные, подключения и история синхронизации будут удалены без возможности восстановления.</div>
|
||||
</div>
|
||||
<form method="post" action="/profile/delete">
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="password">Введите пароль для подтверждения</label>
|
||||
<input class="inp" type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="bi bi-trash"></i> Удалить мой аккаунт
|
||||
</button>
|
||||
<a href="/profile" class="btn btn-outline">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,43 +1,40 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Редактировать профиль — ЭВОСИНК{% endblock %}
|
||||
{% block title %}Редактировать профиль — Мои Товары{% endblock %}
|
||||
{% block page_title %}Редактировать профиль{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-center">
|
||||
<div class="col-sm-10 col-md-7 col-lg-6">
|
||||
<article class="card mt-4">
|
||||
<header>
|
||||
<h1><i class="bi bi-pencil me-2"></i>Редактировать профиль</h1>
|
||||
</header>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/profile/edit">
|
||||
<div class="row gap-2 mb-2">
|
||||
<div class="col">
|
||||
<label for="first_name">Имя
|
||||
<input type="text" id="first_name" name="first_name"
|
||||
value="{{ form.first_name if form else user.first_name }}" required>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="last_name">Фамилия
|
||||
<input type="text" id="last_name" name="last_name"
|
||||
value="{{ form.last_name if form else user.last_name }}" required>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<label>Email
|
||||
<input type="email" value="{{ user.email }}" disabled>
|
||||
</label>
|
||||
<label for="phone">Телефон
|
||||
<input type="tel" id="phone" name="phone"
|
||||
value="{{ form.phone if form else user.phone }}" required>
|
||||
</label>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit">Сохранить</button>
|
||||
<a href="/profile" role="button" class="outline secondary">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
<div class="pg-title">Редактировать профиль</div>
|
||||
<div class="pg-sub">Обновите ваши личные данные</div>
|
||||
|
||||
<div class="card" style="max-width:540px;">
|
||||
<form method="post" action="/profile/edit">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="first_name">Имя</label>
|
||||
<input class="inp" type="text" id="first_name" name="first_name"
|
||||
value="{{ form.first_name if form else user.first_name }}" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="last_name">Фамилия</label>
|
||||
<input class="inp" type="text" id="last_name" name="last_name"
|
||||
value="{{ form.last_name if form else user.last_name }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl">Email</label>
|
||||
<input class="inp" type="email" value="{{ user.email }}" disabled style="opacity:0.6;cursor:not-allowed;">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="phone">Телефон</label>
|
||||
<input class="inp" type="tel" id="phone" name="phone"
|
||||
value="{{ form.phone if form else user.phone }}" required>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-save"></i> Сохранить
|
||||
</button>
|
||||
<a href="/profile" class="btn btn-outline">Отмена</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,86 +1,113 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Личный кабинет — ЭВОСИНК{% endblock %}
|
||||
{% block title %}Профиль — Мои Товары{% endblock %}
|
||||
{% block page_title %}Профиль{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-center">
|
||||
<div class="col-sm-10 col-md-7 col-lg-6">
|
||||
<article class="card mt-4">
|
||||
<header>
|
||||
<h1><i class="bi bi-person-circle me-2"></i>Личный кабинет</h1>
|
||||
</header>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Имя</span>
|
||||
<span>{{ user.first_name }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Фамилия</span>
|
||||
<span>{{ user.last_name }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Email</span>
|
||||
<span>
|
||||
{{ user.email }}
|
||||
{% if user.is_email_confirmed %}
|
||||
<span class="badge badge-success ms-1"><i class="bi bi-check-circle"></i> подтверждён</span>
|
||||
{% else %}
|
||||
<span class="badge badge-warning ms-1"><i class="bi bi-exclamation-circle"></i> не подтверждён</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Телефон</span>
|
||||
<span>{{ user.phone }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Роль</span>
|
||||
<span>
|
||||
{% if user.role == 'system' %}<span class="badge badge-danger">Системный</span>
|
||||
{% elif user.role == 'admin' %}<span class="badge badge-warning">Администратор</span>
|
||||
{% else %}<span class="badge badge-secondary">Пользователь</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Статус</span>
|
||||
<span>
|
||||
{% if user.status == 'active' %}<span class="badge badge-success">Активен</span>
|
||||
{% elif user.status == 'pending' %}<span class="badge badge-warning">Ожидает подтверждения</span>
|
||||
{% else %}<span class="badge badge-danger">Заблокирован</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</li>
|
||||
{% if user.evotor_user_id %}
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Эвотор ID</span>
|
||||
<span class="font-monospace small">{{ user.evotor_user_id }}</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="list-group-item">
|
||||
<span class="text-muted small">Регистрация</span>
|
||||
<span>{{ user.created_at | datefmt }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="card-body d-grid gap-2">
|
||||
<a href="/profile/edit" role="button">
|
||||
<i class="bi bi-pencil me-1"></i>Редактировать профиль
|
||||
</a>
|
||||
<a href="/profile/change-password" role="button" class="secondary">
|
||||
<i class="bi bi-key me-1"></i>Изменить пароль
|
||||
</a>
|
||||
{% if not user.is_email_confirmed %}
|
||||
<a href="/resend-confirm" role="button" class="outline secondary">
|
||||
<i class="bi bi-envelope me-1"></i>Отправить письмо с подтверждением
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="/logout" role="button" class="outline secondary">
|
||||
<i class="bi bi-box-arrow-right me-1"></i>Выход
|
||||
</a>
|
||||
<a href="/profile/delete" role="button" class="outline danger sm mt-2">
|
||||
<i class="bi bi-trash me-1"></i>Удалить аккаунт
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
<div class="pg-title">Личный кабинет</div>
|
||||
<div class="pg-sub">Ваши данные и настройки аккаунта</div>
|
||||
|
||||
<div class="g2" style="align-items:start;">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-hd">
|
||||
<div>
|
||||
<div class="card-title">Данные профиля</div>
|
||||
<div class="card-sub">Основная информация об аккаунте</div>
|
||||
</div>
|
||||
<div class="avatar {% if user.role in ('admin','system') %}admin{% endif %}" style="width:44px;height:44px;font-size:15px;">
|
||||
{{ user.first_name[0] if user.first_name else '?' }}{{ user.last_name[0] if user.last_name else '' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="conn-detail">
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Имя</span>
|
||||
<span class="conn-v" style="font-family:'Golos Text',sans-serif;">{{ user.first_name }}</span>
|
||||
</div>
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Фамилия</span>
|
||||
<span class="conn-v" style="font-family:'Golos Text',sans-serif;">{{ user.last_name }}</span>
|
||||
</div>
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Email</span>
|
||||
<span class="conn-v" style="display:flex;align-items:center;gap:6px;">
|
||||
{{ user.email }}
|
||||
{% if user.is_email_confirmed %}
|
||||
<span class="tag tag-gr" style="font-size:10px;padding:1px 6px;"><i class="bi bi-check-circle"></i> подтверждён</span>
|
||||
{% else %}
|
||||
<span class="tag tag-yl" style="font-size:10px;padding:1px 6px;"><i class="bi bi-exclamation-circle"></i> не подтверждён</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Телефон</span>
|
||||
<span class="conn-v">{{ user.phone or '—' }}</span>
|
||||
</div>
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Роль</span>
|
||||
<span class="conn-v" style="font-family:'Golos Text',sans-serif;">
|
||||
{% if user.role == 'system' %}
|
||||
<span class="tag tag-rd">Системный</span>
|
||||
{% elif user.role == 'admin' %}
|
||||
<span class="tag tag-or">Администратор</span>
|
||||
{% else %}
|
||||
<span class="tag tag-dim">Пользователь</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Статус</span>
|
||||
<span class="conn-v" style="font-family:'Golos Text',sans-serif;">
|
||||
{% if user.status == 'active' %}
|
||||
<span class="tag tag-gr"><span class="dot g"></span>Активен</span>
|
||||
{% elif user.status == 'pending' %}
|
||||
<span class="tag tag-yl"><span class="dot y pulse"></span>Ожидает</span>
|
||||
{% else %}
|
||||
<span class="tag tag-rd"><span class="dot r"></span>Заблокирован</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% if user.evotor_user_id %}
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Эвотор ID</span>
|
||||
<span class="conn-v">{{ user.evotor_user_id }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="conn-row">
|
||||
<span class="conn-k">Регистрация</span>
|
||||
<span class="conn-v">{{ user.created_at | datefmt }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:12px;">
|
||||
<div class="card">
|
||||
<div class="card-title" style="margin-bottom:14px;">Действия</div>
|
||||
<div style="display:flex;flex-direction:column;gap:8px;">
|
||||
<a href="/profile/edit" class="btn btn-primary">
|
||||
<i class="bi bi-pencil"></i> Редактировать профиль
|
||||
</a>
|
||||
<a href="/profile/change-password" class="btn btn-outline">
|
||||
<i class="bi bi-key"></i> Изменить пароль
|
||||
</a>
|
||||
{% if not user.is_email_confirmed %}
|
||||
<a href="/resend-confirm" class="btn btn-outline">
|
||||
<i class="bi bi-envelope"></i> Отправить письмо с подтверждением
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="/logout" class="btn btn-outline">
|
||||
<i class="bi bi-box-arrow-right"></i> Выйти
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" style="border-color:#F4AEAE;">
|
||||
<div class="card-title" style="color:#E53935;margin-bottom:10px;"><i class="bi bi-exclamation-triangle" style="margin-right:6px;"></i>Опасная зона</div>
|
||||
<div class="card-sub" style="margin-bottom:12px;">Необратимые действия с аккаунтом</div>
|
||||
<a href="/profile/delete" class="btn btn-danger btn-sm">
|
||||
<i class="bi bi-trash"></i> Удалить аккаунт
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,44 +1,83 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Регистрация — ЭВОСИНК{% endblock %}
|
||||
<!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="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Golos+Text:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body style="display:flex;align-items:center;justify-content:center;min-height:100vh;background:#F4F5F7;padding:24px;">
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-center">
|
||||
<div class="col-sm-10 col-md-7 col-lg-6">
|
||||
<article class="card mt-4">
|
||||
<div class="card-body">
|
||||
<h1 class="mb-4" style="font-size:1.3rem;">Регистрация</h1>
|
||||
<form method="post" action="/register">
|
||||
<div class="row gap-2 mb-2">
|
||||
<div class="col">
|
||||
<label for="first_name">Имя
|
||||
<input type="text" id="first_name" name="first_name" value="{{ form.first_name if form else '' }}">
|
||||
</label>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="last_name">Фамилия
|
||||
<input type="text" id="last_name" name="last_name" value="{{ form.last_name if form else '' }}">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<label for="email">Email <span class="text-danger">*</span>
|
||||
<input type="email" id="email" name="email" value="{{ form.email if form else '' }}" required>
|
||||
</label>
|
||||
<label for="phone">Телефон <span class="text-danger">*</span>
|
||||
<input type="tel" id="phone" name="phone" value="{{ form.phone if form else '' }}" required>
|
||||
</label>
|
||||
<label for="password">Пароль <span class="text-danger">*</span>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</label>
|
||||
<label for="password_confirm">Подтверждение пароля <span class="text-danger">*</span>
|
||||
<input type="password" id="password_confirm" name="password_confirm" required>
|
||||
</label>
|
||||
<button type="submit" class="w-100">Зарегистрироваться</button>
|
||||
</form>
|
||||
<div class="text-center small mt-3">
|
||||
<a href="/login">Уже есть аккаунт? Войти</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<div class="card" style="max-width:480px;width:100%;padding:32px;">
|
||||
<div style="text-align:center;margin-bottom:24px;">
|
||||
<div style="width:44px;height:44px;border-radius:11px;background:#FF5500;display:flex;align-items:center;justify-content:center;margin:0 auto 16px;">
|
||||
<svg width="24" height="24" viewBox="0 0 28 28" fill="none">
|
||||
<path d="M9 11h10l-1.5 9H10.5L9 11Z" fill="white" opacity="0.95"/>
|
||||
<path d="M11.5 11V9a2.5 2.5 0 015 0v2" stroke="white" stroke-width="1.6" stroke-linecap="round" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="login-box-title">Регистрация</div>
|
||||
<div style="font-size:13px;color:#5C6278;margin-top:4px;">Создайте аккаунт для работы с Мои Товары</div>
|
||||
</div>
|
||||
|
||||
{% if errors %}
|
||||
<div class="alert alert-rd" style="margin-bottom:14px;">
|
||||
<span><i class="bi bi-x-circle"></i></span>
|
||||
<div>{% for e in errors %}<div>{{ e }}</div>{% endfor %}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/register">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="first_name">Имя</label>
|
||||
<input class="inp" type="text" id="first_name" name="first_name"
|
||||
value="{{ form.first_name if form else '' }}">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="last_name">Фамилия</label>
|
||||
<input class="inp" type="text" id="last_name" name="last_name"
|
||||
value="{{ form.last_name if form else '' }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="email">Email <span style="color:#E53935;">*</span></label>
|
||||
<input class="inp" type="email" id="email" name="email"
|
||||
value="{{ form.email if form else '' }}" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="phone">Телефон <span style="color:#E53935;">*</span></label>
|
||||
<input class="inp" type="tel" id="phone" name="phone"
|
||||
value="{{ form.phone if form else '' }}" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="password">Пароль <span style="color:#E53935;">*</span></label>
|
||||
<input class="inp" type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="password_confirm">Подтверждение пароля <span style="color:#E53935;">*</span></label>
|
||||
<input class="inp" type="password" id="password_confirm" name="password_confirm" required>
|
||||
</div>
|
||||
<button type="submit" class="login-btn">Зарегистрироваться</button>
|
||||
</form>
|
||||
|
||||
<div class="login-hint" style="margin-top:14px;">
|
||||
<a href="/login">Уже есть аккаунт? Войти</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/inputmask@5.0.9/dist/inputmask.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var phoneInputs = document.querySelectorAll('input[name="phone"]');
|
||||
if (phoneInputs.length) {
|
||||
Inputmask('+7 (999) 999-99-99', { placeholder: '_', showMaskOnHover: false, clearMaskOnLostFocus: false }).mask(phoneInputs);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,23 +1,45 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Новый пароль — ЭВОСИНК{% endblock %}
|
||||
<!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="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Golos+Text:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body style="display:flex;align-items:center;justify-content:center;min-height:100vh;background:#F4F5F7;">
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-center">
|
||||
<div class="col-sm-10 col-md-6 col-lg-5">
|
||||
<article class="card mt-4">
|
||||
<div class="card-body">
|
||||
<h1 style="font-size:1.3rem;" class="mb-4">Новый пароль</h1>
|
||||
<form method="post" action="/reset-password?token={{ token }}">
|
||||
<label for="password">Новый пароль
|
||||
<input type="password" id="password" name="password" required>
|
||||
</label>
|
||||
<label for="password_confirm">Подтверждение пароля
|
||||
<input type="password" id="password_confirm" name="password_confirm" required>
|
||||
</label>
|
||||
<button type="submit" class="w-100">Сменить пароль</button>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
<div class="card" style="max-width:400px;width:100%;padding:32px;">
|
||||
<div style="text-align:center;margin-bottom:24px;">
|
||||
<div style="width:44px;height:44px;border-radius:11px;background:#FF5500;display:flex;align-items:center;justify-content:center;margin:0 auto 16px;">
|
||||
<i class="bi bi-key" style="color:#fff;font-size:20px;"></i>
|
||||
</div>
|
||||
<div class="login-box-title">Новый пароль</div>
|
||||
<div style="font-size:13px;color:#5C6278;margin-top:4px;">Введите и подтвердите новый пароль</div>
|
||||
</div>
|
||||
|
||||
{% if errors %}
|
||||
<div class="alert alert-rd" style="margin-bottom:14px;">
|
||||
<span><i class="bi bi-x-circle"></i></span>
|
||||
<div>{% for e in errors %}<div>{{ e }}</div>{% endfor %}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/reset-password?token={{ token }}">
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="password">Новый пароль</label>
|
||||
<input class="inp" type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl" for="password_confirm">Подтверждение пароля</label>
|
||||
<input class="inp" type="password" id="password_confirm" name="password_confirm" required>
|
||||
</div>
|
||||
<button type="submit" class="login-btn">Сменить пароль</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,71 +1,102 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Синхронизация — ЭВОСИНК{% endblock %}
|
||||
{% block title %}Синхронизация — Мои Товары{% endblock %}
|
||||
{% block page_title %}Синхронизация{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-between align-center mb-3">
|
||||
<h1 style="font-size:1.3rem; margin:0;"><i class="bi bi-arrow-repeat me-2"></i>Синхронизация</h1>
|
||||
</div>
|
||||
<div class="pg-title">Синхронизация</div>
|
||||
<div class="pg-sub">Настройка и управление синхронизацией товаров Эвотор → VK Market</div>
|
||||
|
||||
{% if saved %}
|
||||
<div role="alert" class="alert alert-success"><p>Настройки сохранены.</p></div>
|
||||
<div class="alert alert-gr">
|
||||
<span><i class="bi bi-check-circle"></i></span>
|
||||
<div>Настройки сохранены.</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/sync/settings">
|
||||
|
||||
<article class="card mb-3">
|
||||
<h2 style="font-size:1.1rem; margin-bottom:1.25rem;">Фоновые задачи</h2>
|
||||
<p class="text-muted small" style="margin-bottom:1.25rem;">Включайте поочерёдно: сначала проверьте зеркало Эвотор, затем ВК, затем синхронизацию.</p>
|
||||
<div class="g2" style="align-items:start; margin-bottom:16px;">
|
||||
|
||||
<div style="display:flex; flex-direction:column; gap:1rem;">
|
||||
<div class="card">
|
||||
<div class="card-hd">
|
||||
<div>
|
||||
<div class="card-title">Фоновые задачи</div>
|
||||
<div class="card-sub">Включайте поочерёдно: сначала Эвотор, затем ВК, затем синхронизацию</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label style="display:flex; align-items:flex-start; gap:0.75rem; cursor:pointer;">
|
||||
<input type="hidden" name="evo_mirror_enabled" value="0">
|
||||
<input type="checkbox" name="evo_mirror_enabled" value="1" role="switch"
|
||||
{% if config and config.evo_mirror_enabled %}checked{% endif %}
|
||||
style="margin-top:0.2rem; flex-shrink:0;">
|
||||
<span>
|
||||
<strong>Зеркало Эвотор</strong><br>
|
||||
<span class="text-muted small">Периодически импортирует товары, группы и магазины из Эвотор в локальную базу.</span>
|
||||
</span>
|
||||
<div style="display:flex;flex-direction:column;gap:0;">
|
||||
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 0;border-bottom:1px solid #E4E6EE;">
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:600;color:#1C1F2E;">Зеркало Эвотор</div>
|
||||
<div style="font-size:12px;color:#9EA8BE;margin-top:2px;">Импортирует товары, группы и магазины из Эвотор в локальную базу</div>
|
||||
</div>
|
||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;margin:0;">
|
||||
<input type="hidden" name="evo_mirror_enabled" value="0">
|
||||
<input type="checkbox" name="evo_mirror_enabled" value="1"
|
||||
{% if config and config.evo_mirror_enabled %}checked{% endif %}
|
||||
id="evo_mirror_cb" style="display:none;">
|
||||
<div class="tog {% if config and config.evo_mirror_enabled %}on{% endif %}"
|
||||
onclick="this.previousElementSibling.click(); this.classList.toggle('on')"
|
||||
id="evo_mirror_tog"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label style="display:flex; align-items:flex-start; gap:0.75rem; cursor:pointer;">
|
||||
<input type="hidden" name="vk_mirror_enabled" value="0">
|
||||
<input type="checkbox" name="vk_mirror_enabled" value="1" role="switch"
|
||||
{% if config and config.vk_mirror_enabled %}checked{% endif %}
|
||||
style="margin-top:0.2rem; flex-shrink:0;">
|
||||
<span>
|
||||
<strong>Зеркало ВК</strong><br>
|
||||
<span class="text-muted small">Периодически импортирует альбомы и товары из VK Market в локальный кэш.</span>
|
||||
</span>
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 0;border-bottom:1px solid #E4E6EE;">
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:600;color:#1C1F2E;">Зеркало ВК</div>
|
||||
<div style="font-size:12px;color:#9EA8BE;margin-top:2px;">Импортирует альбомы и товары из VK Market в локальный кэш</div>
|
||||
</div>
|
||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;margin:0;">
|
||||
<input type="hidden" name="vk_mirror_enabled" value="0">
|
||||
<input type="checkbox" name="vk_mirror_enabled" value="1"
|
||||
{% if config and config.vk_mirror_enabled %}checked{% endif %}
|
||||
id="vk_mirror_cb" style="display:none;">
|
||||
<div class="tog {% if config and config.vk_mirror_enabled %}on{% endif %}"
|
||||
onclick="this.previousElementSibling.click(); this.classList.toggle('on')"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label style="display:flex; align-items:flex-start; gap:0.75rem; cursor:pointer;">
|
||||
<input type="hidden" name="is_enabled" value="0">
|
||||
<input type="checkbox" name="is_enabled" value="1" role="switch"
|
||||
{% if config and config.is_enabled %}checked{% endif %}
|
||||
style="margin-top:0.2rem; flex-shrink:0;">
|
||||
<span>
|
||||
<strong>Синхронизация</strong><br>
|
||||
<span class="text-muted small">Зеркалит каталог Эвотор в VK Market: создаёт, обновляет и удаляет товары.</span>
|
||||
</span>
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 0;">
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:600;color:#1C1F2E;">Синхронизация</div>
|
||||
<div style="font-size:12px;color:#9EA8BE;margin-top:2px;">Зеркалит каталог Эвотор в VK Market: создаёт, обновляет и удаляет товары</div>
|
||||
</div>
|
||||
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;margin:0;">
|
||||
<input type="hidden" name="is_enabled" value="0">
|
||||
<input type="checkbox" name="is_enabled" value="1"
|
||||
{% if config and config.is_enabled %}checked{% endif %}
|
||||
id="sync_cb" style="display:none;">
|
||||
<div class="tog {% if config and config.is_enabled %}on{% endif %}"
|
||||
onclick="this.previousElementSibling.click(); this.classList.toggle('on')"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<article class="card mb-3">
|
||||
<h2 style="font-size:1.1rem; margin-bottom:1.25rem;">Настройки цены</h2>
|
||||
<label style="max-width:320px;">
|
||||
Множитель цены
|
||||
<input type="number" name="price_multiplier" step="0.0001" min="0.0001"
|
||||
value="{{ config.price_multiplier if config else '1' }}"
|
||||
placeholder="1">
|
||||
<small class="text-muted">Цена из Эвотор умножается на это значение перед отправкой в ВК. По умолчанию: 1.</small>
|
||||
</label>
|
||||
</article>
|
||||
<div class="card">
|
||||
<div class="card-hd">
|
||||
<div>
|
||||
<div class="card-title">Настройки цены</div>
|
||||
<div class="card-sub">Трансформация цен при передаче в VK Market</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label class="form-lbl">Множитель цены</label>
|
||||
<input class="inp" type="number" name="price_multiplier" step="0.0001" min="0.0001"
|
||||
value="{{ config.price_multiplier if config else '1' }}"
|
||||
placeholder="1" style="max-width:160px;">
|
||||
<div style="font-size:11px;color:#9EA8BE;margin-top:5px;">Цена из Эвотор умножается на это значение перед отправкой в ВК. По умолчанию: 1.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit">Сохранить</button>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-save"></i> Сохранить настройки
|
||||
</button>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,52 +1,53 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Альбомы ВК — ЭВОСИНК{% endblock %}
|
||||
{% block title %}Альбомы ВК — Мои Товары{% endblock %}
|
||||
{% block page_title %}Каталог ВКонтакте{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-between align-center mb-3">
|
||||
<h1 style="font-size:1.3rem; margin:0;"><i class="bi bi-badge-vr me-2"></i>Каталог ВКонтакте — Альбомы</h1>
|
||||
<span class="text-muted small">Всего: {{ albums | length }}</span>
|
||||
</div>
|
||||
<div class="pg-title">Каталог ВКонтакте — Альбомы</div>
|
||||
<div class="pg-sub">Альбомы из VK Market · Всего: {{ albums | length }}</div>
|
||||
|
||||
<article class="card">
|
||||
{% if not vk_conn %}
|
||||
<div class="text-center py-5 text-muted">
|
||||
<i class="bi bi-plug" style="font-size:2rem;"></i>
|
||||
<p class="mt-2">ВКонтакте не подключён.<br><a href="/connections">Перейти к подключениям</a></p>
|
||||
</div>
|
||||
{% elif albums %}
|
||||
<div class="table-scroll">
|
||||
<table class="align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Товаров</th>
|
||||
<th>ID</th>
|
||||
<th>Обновлено</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for a in albums %}
|
||||
<tr>
|
||||
<td><i class="bi bi-collection me-1 text-muted"></i> <strong>{{ a.title }}</strong></td>
|
||||
<td class="text-muted">{{ a.count if a.count is not none else '—' }}</td>
|
||||
<td class="text-muted small">{{ a.album_id }}</td>
|
||||
<td class="text-muted small">{{ a.fetched_at | datefmt }}</td>
|
||||
<td>
|
||||
<a href="/vk-catalog/albums/{{ a.album_id }}/products" role="button" class="outline sm">
|
||||
<i class="bi bi-box-seam"></i> Товары
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5 text-muted">
|
||||
<i class="bi bi-collection" style="font-size:2rem;"></i>
|
||||
<p class="mt-2">Альбомы ещё не загружены.<br>Синхронизация выполняется каждые {{ refresh_interval }} сек. автоматически.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</article>
|
||||
<div class="card" style="padding:0;">
|
||||
{% if not vk_conn %}
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-plug"></i>
|
||||
<p>ВКонтакте не подключён.<br><a href="/connections" style="color:#FF5500;">Перейти к подключениям</a></p>
|
||||
</div>
|
||||
{% elif albums %}
|
||||
<div class="table-wrap">
|
||||
<table class="tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Товаров</th>
|
||||
<th>ID</th>
|
||||
<th>Обновлено</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for a in albums %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="tbl-name"><i class="bi bi-collection" style="color:#9EA8BE;margin-right:6px;"></i>{{ a.title }}</div>
|
||||
</td>
|
||||
<td><span class="mono" style="font-size:12px;">{{ a.count if a.count is not none else '—' }}</span></td>
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ a.album_id }}</span></td>
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ a.fetched_at | datefmt }}</span></td>
|
||||
<td>
|
||||
<a href="/vk-catalog/albums/{{ a.album_id }}/products" class="btn btn-outline btn-xs">
|
||||
<i class="bi bi-box-seam"></i> Товары
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-collection"></i>
|
||||
<p>Альбомы ещё не загружены.<br>Синхронизация выполняется каждые {{ refresh_interval }} сек. автоматически.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,75 +1,72 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Товары ВК — {{ album.title }} — ЭВОСИНК{% endblock %}
|
||||
{% block title %}Товары ВК — {{ album.title }} — Мои Товары{% endblock %}
|
||||
{% block page_title %}Каталог ВКонтакте{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<nav aria-label="breadcrumb" class="mb-3">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/vk-catalog/albums">Альбомы ВК</a></li>
|
||||
<li>{{ album.title }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="/vk-catalog/albums">Альбомы ВК</a></li>
|
||||
<li>{{ album.title }}</li>
|
||||
</ol>
|
||||
|
||||
<div class="d-flex justify-between align-center mb-3">
|
||||
<h1 style="font-size:1.3rem; margin:0;"><i class="bi bi-box-seam me-2"></i>{{ album.title }}</h1>
|
||||
<span class="text-muted small">Всего: {{ products | length }}</span>
|
||||
<div class="pg-title">{{ album.title }}</div>
|
||||
<div class="pg-sub">Товары из VK Market · Всего: {{ products | length }}</div>
|
||||
|
||||
<div class="card" style="padding:0;">
|
||||
{% if products %}
|
||||
<div class="table-wrap">
|
||||
<table class="tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:56px;"></th>
|
||||
<th>Название</th>
|
||||
<th>Цена</th>
|
||||
<th>Статус</th>
|
||||
<th>ID</th>
|
||||
<th>Обновлено</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in products %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if p.thumb_url %}
|
||||
<img src="{{ p.thumb_url }}" alt="" style="width:40px;height:40px;object-fit:cover;border-radius:6px;">
|
||||
{% else %}
|
||||
<div style="width:40px;height:40px;border-radius:6px;background:#F4F5F7;display:flex;align-items:center;justify-content:center;color:#9EA8BE;">
|
||||
<i class="bi bi-image"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="tbl-name">{{ p.name }}</div>
|
||||
{% if p.description %}
|
||||
<div class="tbl-sub">{{ p.description[:80] }}{% if p.description|length > 80 %}…{% endif %}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><span class="mono">{% if p.price is not none %}{{ p.price | price }}{% else %}—{% endif %}</span></td>
|
||||
<td>
|
||||
{% if p.availability == 0 %}
|
||||
<span class="tag tag-gr"><span class="dot g"></span>В наличии</span>
|
||||
{% elif p.availability == 1 %}
|
||||
<span class="tag tag-dim"><span class="dot d"></span>Удалён</span>
|
||||
{% elif p.availability == 2 %}
|
||||
<span class="tag tag-yl"><span class="dot y"></span>Недоступен</span>
|
||||
{% else %}
|
||||
<span style="color:#9EA8BE;font-size:12px;">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ p.vk_product_id }}</span></td>
|
||||
<td><span class="mono" style="font-size:11px;color:#9EA8BE;">{{ p.fetched_at | datefmt }}</span></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-box-seam"></i>
|
||||
<p>Товары в этом альбоме не найдены.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<article class="card">
|
||||
{% if products %}
|
||||
<div class="table-scroll">
|
||||
<table class="align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Название</th>
|
||||
<th>Цена</th>
|
||||
<th>Статус</th>
|
||||
<th>ID</th>
|
||||
<th>Обновлено</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in products %}
|
||||
<tr>
|
||||
<td style="width:48px;">
|
||||
{% if p.thumb_url %}
|
||||
<img src="{{ p.thumb_url }}" alt="" style="width:40px;height:40px;object-fit:cover;border-radius:4px;">
|
||||
{% else %}
|
||||
<span class="text-muted"><i class="bi bi-image" style="font-size:1.5rem;"></i></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ p.name }}</strong>
|
||||
{% if p.description %}
|
||||
<br><span class="text-muted small">{{ p.description[:80] }}{% if p.description|length > 80 %}…{% endif %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-muted">
|
||||
{% if p.price is not none %}{{ p.price | price }}{% else %}—{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if p.availability == 0 %}
|
||||
<span class="badge badge-success">В наличии</span>
|
||||
{% elif p.availability == 1 %}
|
||||
<span class="badge badge-secondary">Удалён</span>
|
||||
{% elif p.availability == 2 %}
|
||||
<span class="badge badge-warning">Недоступен</span>
|
||||
{% else %}
|
||||
<span class="text-muted small">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-muted small">{{ p.vk_product_id }}</td>
|
||||
<td class="text-muted small">{{ p.fetched_at | datefmt }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5 text-muted">
|
||||
<i class="bi bi-box-seam" style="font-size:2rem;"></i>
|
||||
<p class="mt-2">Товары в этом альбоме не найдены.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user