feat: complete UI redesign of admin dashboard with custom Tailwind

This commit is contained in:
Your Name
2026-07-18 22:59:37 +05:00
parent 9e63ebadff
commit 8bca008f74
12 changed files with 1307 additions and 838 deletions
+113 -86
View File
@@ -1,109 +1,122 @@
{% extends "base.html" %}
{% block body %}
<div class="mb-6">
<h1 class="text-3xl font-bold">Источники</h1>
<div class="text-base-content/60 mt-1">VK-источники для парсинга. Название идёт в prompt, тэг — в будущие хэштеги.</div>
<div class="mb-8">
<h1 class="text-3xl font-bold tracking-tight text-white flex items-center gap-3 mb-2">
<i data-lucide="database" class="text-app-primary w-8 h-8"></i>
Источники
</h1>
<div class="text-app-textMuted text-sm">VK-источники для парсинга. Название идёт в prompt, тэг — в будущие хэштеги.</div>
</div>
<div class="collapse collapse-arrow bg-base-100 shadow-sm border border-base-200 mb-6 {% if source_preview %}collapse-open{% endif %}">
<input type="checkbox" {% if source_preview %}checked{% endif %} />
<div class="collapse-title text-lg font-bold">
Добавить источники
</div>
<div class="collapse-content border-t border-base-200 pt-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<details class="card mb-8 group/details" {% if source_preview %}open{% endif %}>
<summary class="p-4 flex items-center justify-between cursor-pointer select-none hover:bg-app-surfaceHover transition-colors list-none">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-app-primary/20 text-app-primary flex items-center justify-center">
<i data-lucide="plus" class="w-5 h-5"></i>
</div>
<span class="text-lg font-bold text-white">Добавить источники</span>
</div>
<i data-lucide="chevron-down" class="w-5 h-5 text-app-textMuted transition-transform group-open/details:rotate-180"></i>
</summary>
<div class="p-6 border-t border-app-border bg-app-bg/30">
<div class="grid grid-cols-1 xl:grid-cols-2 gap-10">
<!-- Single Add -->
<div>
<h3 class="font-bold mb-4">Добавить один</h3>
<form method="post" action="/sources/new" class="flex flex-col gap-3">
<h3 class="font-bold text-white mb-4 flex items-center gap-2"><i data-lucide="file-plus" class="w-4 h-4 text-app-textMuted"></i> Добавить один</h3>
<form method="post" action="/sources/new" class="flex flex-col gap-4">
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
<input type="hidden" name="platform" value="vk">
<input type="hidden" name="priority" value="100">
<div class="form-control">
<label class="label"><span class="label-text">Название</span></label>
<input name="name" class="input input-bordered input-sm" placeholder="Можно оставить как в VK">
<div>
<label class="block text-xs font-bold text-app-textMuted mb-1">Название</label>
<input name="name" class="input" placeholder="Можно оставить пустым, подтянется из VK">
</div>
<div class="form-control">
<label class="label"><span class="label-text">Тэг</span></label>
<input name="tag" class="input input-bordered input-sm" placeholder="academy_gear">
<div>
<label class="block text-xs font-bold text-app-textMuted mb-1">Тэг (для хэштега)</label>
<input name="tag" class="input" placeholder="academy_gear">
</div>
<div class="form-control">
<label class="label"><span class="label-text">Ссылка</span></label>
<input name="url" class="input input-bordered input-sm" placeholder="https://vk.com/academy_gear" required>
<div>
<label class="block text-xs font-bold text-app-textMuted mb-1">Ссылка VK <span class="text-app-error">*</span></label>
<input name="url" class="input" placeholder="https://vk.com/academy_gear" required>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-2">
<input name="active" type="checkbox" class="checkbox checkbox-primary checkbox-sm" checked>
<span class="label-text">Включить сразу</span>
</label>
</div>
<label class="flex items-center gap-3 mt-2 cursor-pointer group">
<input name="active" type="checkbox" class="checkbox" checked>
<span class="text-sm font-medium text-app-textMain group-hover:text-white transition-colors">Включить сразу после добавления</span>
</label>
<button class="btn btn-primary btn-sm mt-2" type="submit">Добавить</button>
<button class="btn btn-primary mt-2 self-start" type="submit">
<i data-lucide="plus" class="w-4 h-4"></i> Добавить источник
</button>
</form>
</div>
<!-- Bulk Add -->
<div>
<h3 class="font-bold mb-4">Массовое добавление</h3>
<form method="post" action="/sources/preview" class="flex flex-col gap-3">
<h3 class="font-bold text-white mb-4 flex items-center gap-2"><i data-lucide="list-plus" class="w-4 h-4 text-app-textMuted"></i> Массовое добавление</h3>
<form method="post" action="/sources/preview" class="flex flex-col gap-4">
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
<input type="hidden" name="q" value="{{ q }}">
<input type="hidden" name="status_filter" value="{{ status_filter }}">
<input type="hidden" name="per_page" value="{{ pagination.per_page }}">
<div class="form-control">
<label class="label"><span class="label-text">Список (Название Тэг Ссылка)</span></label>
<textarea name="bulk_text" class="textarea textarea-bordered font-mono text-xs" rows="5" placeholder="Academy Gear academy_gear https://vk.com/academy_gear&#10;A2 Technologies a2technologies https://vk.com/a2technologies">{{ bulk_text }}</textarea>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-2">
<input name="bulk_active" type="checkbox" class="checkbox checkbox-primary checkbox-sm" {% if bulk_active %}checked{% endif %}>
<span class="label-text">Включить после добавления</span>
<div>
<label class="block text-xs font-bold text-app-textMuted mb-1 flex justify-between">
<span>Список (Название Тэг Ссылка)</span>
<span class="text-[10px] text-app-textMuted/60 font-normal">По 1 на строку</span>
</label>
<textarea name="bulk_text" class="textarea font-mono text-xs h-40 leading-relaxed" placeholder="Academy Gear academy_gear https://vk.com/academy_gear&#10;A2 Technologies a2technologies https://vk.com/a2technologies">{{ bulk_text }}</textarea>
</div>
<button class="btn btn-outline btn-sm mt-2" type="submit">Проверить список</button>
<label class="flex items-center gap-3 mt-2 cursor-pointer group">
<input name="bulk_active" type="checkbox" class="checkbox" {% if bulk_active %}checked{% endif %}>
<span class="text-sm font-medium text-app-textMain group-hover:text-white transition-colors">Включить после добавления</span>
</label>
<button class="btn btn-surface mt-2 self-start" type="submit">
<i data-lucide="search" class="w-4 h-4"></i> Проверить список
</button>
</form>
</div>
</div>
{% if source_preview %}
<div class="mt-8 pt-4 border-t border-base-200">
<h3 class="font-bold mb-4">Предпросмотр массового добавления</h3>
<div class="overflow-x-auto bg-base-200 rounded-lg">
<table class="table table-sm">
<thead>
<tr>
<th># строки</th>
<th>Название</th>
<th>Тэг</th>
<th>Ссылка</th>
<th>VK ID</th>
<th>Статус</th>
<div class="mt-8 pt-6 border-t border-app-border">
<h3 class="font-bold text-white mb-4 flex items-center gap-2"><i data-lucide="eye" class="w-4 h-4 text-app-primary"></i> Результаты проверки</h3>
<div class="overflow-x-auto bg-app-bg border border-app-border rounded-xl">
<table class="w-full text-left text-sm whitespace-nowrap">
<thead class="border-b border-app-border bg-app-surface">
<tr class="text-app-textMuted font-semibold tracking-wide uppercase text-[10px]">
<th class="px-4 py-3"># Стр.</th>
<th class="px-4 py-3">Название</th>
<th class="px-4 py-3">Тэг</th>
<th class="px-4 py-3">Ссылка</th>
<th class="px-4 py-3">VK ID</th>
<th class="px-4 py-3">Статус</th>
</tr>
</thead>
<tbody>
<tbody class="divide-y divide-app-border text-xs">
{% for item in source_preview %}
<tr>
<td>{{ item.line_no }}</td>
<td>{{ item.name or "—" }}</td>
<td class="font-mono text-xs">#{{ item.tag or "—" }}</td>
<td><a href="{{ item.url }}" target="_blank" class="link">{{ item.url }}</a></td>
<td class="font-mono text-xs text-base-content/70">
<tr class="hover:bg-app-surfaceHover transition-colors">
<td class="px-4 py-3 text-app-textMuted font-mono">{{ item.line_no }}</td>
<td class="px-4 py-3 font-semibold text-app-textMain">{{ item.name or "—" }}</td>
<td class="px-4 py-3 font-mono text-blue-400">#{{ item.tag or "—" }}</td>
<td class="px-4 py-3"><a href="{{ item.url }}" target="_blank" class="text-app-textMuted hover:text-white hover:underline">{{ item.url }}</a></td>
<td class="px-4 py-3 font-mono text-app-textMuted text-[10px]">
{{ item.external_id }}<br>
{{ item.external_owner_id or "" }}
</td>
<td>
<td class="px-4 py-3">
{% if item.ok %}
<span class="badge badge-success badge-sm">Готов</span>
<span class="badge badge-success">Готов</span>
{% else %}
<span class="badge badge-error badge-sm" title="{{ item.error }}">Ошибка</span>
<span class="badge badge-error" title="{{ item.error }}">Ошибка</span>
{% endif %}
</td>
</tr>
@@ -112,43 +125,57 @@
</table>
</div>
<form method="post" action="/sources/bulk" class="mt-4 flex items-center gap-4">
<form method="post" action="/sources/bulk" class="mt-6 flex flex-col sm:flex-row items-center gap-4 bg-app-warningBg border border-app-warning/20 p-4 rounded-xl">
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
<input type="hidden" name="bulk_payload" value='{{ source_preview|tojson }}'>
<button class="btn btn-primary btn-sm" type="submit">Добавить прошедшие проверку</button>
<span class="text-xs text-base-content/60">Строки с ошибками и дубли будут пропущены.</span>
<button class="btn btn-primary" type="submit">
<i data-lucide="check-circle" class="w-4 h-4"></i> Добавить прошедшие проверку
</button>
<div class="text-sm text-app-warning flex items-center gap-2">
<i data-lucide="info" class="w-4 h-4"></i>
<span>Строки с ошибками и дубликаты будут автоматически пропущены.</span>
</div>
</form>
</div>
{% endif %}
</div>
</div>
</details>
<div class="card bg-base-100 shadow-sm border border-base-200 mb-6">
<div class="card-body p-4 flex flex-col md:flex-row gap-4 justify-between items-end bg-base-200/50 rounded-t-xl">
<form class="flex-1 flex flex-wrap gap-4 items-end w-full" hx-get="/sources" hx-target="#content-area" hx-push-url="true">
<div class="form-control w-full max-w-xs">
<label class="label"><span class="label-text font-bold">Поиск</span></label>
<input name="q" value="{{ q }}" placeholder="Название, ссылка, id" class="input input-bordered input-sm w-full">
<div class="card mb-6">
<div class="p-4 border-b border-app-border bg-app-bg/50">
<form class="flex flex-col md:flex-row gap-4 justify-between items-end" hx-get="/sources" hx-target="#content-area" hx-push-url="true">
<div class="flex flex-col sm:flex-row gap-4 flex-1">
<div class="w-full sm:w-72">
<label class="block text-xs font-bold text-app-textMuted mb-1">Поиск</label>
<div class="relative">
<i data-lucide="search" class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-app-textMuted"></i>
<input name="q" value="{{ q }}" placeholder="Название, ссылка, id" class="input w-full pl-9">
</div>
</div>
<div class="w-full sm:w-48">
<label class="block text-xs font-bold text-app-textMuted mb-1">Статус</label>
<select name="status_filter" class="select w-full">
<option value="">Все статусы</option>
{% for st in ["new","ok","paused","error"] %}
<option value="{{ st }}" {% if status_filter == st %}selected{% endif %}>{{ st }}</option>
{% endfor %}
</select>
</div>
<div class="w-full sm:w-auto">
<label class="block text-xs font-bold text-transparent mb-1 hidden sm:block">.</label>
<button class="btn btn-surface w-full sm:w-auto" type="submit">Найти</button>
</div>
</div>
<div class="form-control">
<label class="label"><span class="label-text font-bold">Статус</span></label>
<select name="status_filter" class="select select-bordered select-sm">
<option value="">Все</option>
{% for st in ["new","ok","paused","error"] %}
<option value="{{ st }}" {% if status_filter == st %}selected{% endif %}>{{ st }}</option>
{% endfor %}
</select>
</div>
<button class="btn btn-primary btn-sm" type="submit">Найти</button>
<div class="w-full">
{% include "table_tools.html" %}
<div class="flex items-center gap-2 mt-4 md:mt-0 w-full md:w-auto">
{% include "table_tools.html" ignore missing %}
</div>
</form>
</div>
<div class="card-body p-0" id="content-area">
<div id="content-area">
{% include "sources_content.html" %}
</div>
</div>