test ferst
This commit is contained in:
148
web/views/dashboard.py
Normal file
148
web/views/dashboard.py
Normal file
@@ -0,0 +1,148 @@
|
||||
import flet as ft
|
||||
import designer as d
|
||||
import api_client as api
|
||||
|
||||
|
||||
class DashboardView:
|
||||
def __init__(self, page: ft.Page):
|
||||
self.page = page
|
||||
self._stats_row = ft.Row(wrap=True, spacing=16, run_spacing=16)
|
||||
self._recent = ft.Column(spacing=0)
|
||||
|
||||
def build_page_view(self) -> ft.View:
|
||||
return ft.View(
|
||||
route="/admin",
|
||||
bgcolor=d.BACKGROUND,
|
||||
appbar=_admin_appbar(self.page, "Главная"),
|
||||
controls=[
|
||||
ft.Row(
|
||||
[
|
||||
_sidebar(self.page, active="/admin"),
|
||||
ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
d.headline("Обзор", size=22),
|
||||
ft.Divider(height=8, color="transparent"),
|
||||
self._stats_row,
|
||||
ft.Divider(height=16, color="transparent"),
|
||||
d.headline("Последние статьи", size=18),
|
||||
ft.Divider(height=8, color="transparent"),
|
||||
self._recent,
|
||||
],
|
||||
scroll=ft.ScrollMode.AUTO,
|
||||
),
|
||||
expand=True,
|
||||
padding=24,
|
||||
),
|
||||
],
|
||||
expand=True,
|
||||
spacing=0,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
async def load(self):
|
||||
token = self.page.session.get("token")
|
||||
data = await api.admin_list_articles(token, limit=100)
|
||||
if not data:
|
||||
return
|
||||
|
||||
items = data.get("items", [])
|
||||
total = data.get("total", 0)
|
||||
published = sum(1 for a in items if a.get("status") == "published")
|
||||
drafts = total - published
|
||||
|
||||
self._stats_row.controls = [
|
||||
d.stat_card("Всего статей", str(total), ft.icons.ARTICLE, d.PRIMARY),
|
||||
d.stat_card("Опубликовано", str(published), ft.icons.CHECK_CIRCLE, d.SUCCESS),
|
||||
d.stat_card("Черновики", str(drafts), ft.icons.EDIT_NOTE, d.WARNING),
|
||||
]
|
||||
|
||||
self._recent.controls = [_recent_item(a, self.page) for a in items[:10]]
|
||||
self.page.update()
|
||||
|
||||
|
||||
def _recent_item(article: dict, page: ft.Page) -> ft.Container:
|
||||
return ft.Container(
|
||||
content=ft.Row(
|
||||
[
|
||||
d.status_badge(article.get("status", "draft")),
|
||||
ft.Text(article["title"], size=14, expand=True, max_lines=1, overflow=ft.TextOverflow.ELLIPSIS),
|
||||
ft.TextButton(
|
||||
"Редактировать",
|
||||
on_click=lambda e, aid=article["id"]: page.go(f"/admin/articles/{aid}"),
|
||||
),
|
||||
],
|
||||
spacing=12,
|
||||
vertical_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
),
|
||||
padding=ft.padding.symmetric(vertical=10, horizontal=4),
|
||||
border=ft.Border(bottom=ft.BorderSide(1, d.DIVIDER)),
|
||||
)
|
||||
|
||||
|
||||
# ─── Общие admin-компоненты ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _admin_appbar(page: ft.Page, title: str) -> ft.AppBar:
|
||||
return ft.AppBar(
|
||||
title=ft.Text(title, size=18, weight=ft.FontWeight.W_600, color=ft.colors.WHITE),
|
||||
bgcolor=d.ADMIN_SIDEBAR,
|
||||
color=ft.colors.WHITE,
|
||||
actions=[
|
||||
ft.TextButton(
|
||||
"На сайт",
|
||||
icon=ft.icons.OPEN_IN_NEW,
|
||||
style=ft.ButtonStyle(color=ft.colors.WHITE70),
|
||||
on_click=lambda e: page.launch_url("/"),
|
||||
),
|
||||
ft.IconButton(
|
||||
ft.icons.LOGOUT,
|
||||
tooltip="Выйти",
|
||||
icon_color=ft.colors.WHITE70,
|
||||
on_click=lambda e: _logout(page),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def _logout(page: ft.Page):
|
||||
page.session.remove("token")
|
||||
page.session.remove("username")
|
||||
page.go("/admin/login")
|
||||
|
||||
|
||||
def _sidebar(page: ft.Page, active: str = "") -> ft.Container:
|
||||
items = [
|
||||
(ft.icons.DASHBOARD, "Главная", "/admin"),
|
||||
(ft.icons.ARTICLE, "Статьи", "/admin/articles"),
|
||||
(ft.icons.CATEGORY, "Категории", "/admin/categories"),
|
||||
(ft.icons.PERM_MEDIA, "Медиа", "/admin/media"),
|
||||
]
|
||||
|
||||
def nav_item(icon, label, route):
|
||||
is_active = active == route
|
||||
return ft.Container(
|
||||
content=ft.Row(
|
||||
[
|
||||
ft.Icon(icon, color=d.ADMIN_SIDEBAR_TEXT if not is_active else d.ACCENT, size=20),
|
||||
ft.Text(label, color=d.ADMIN_SIDEBAR_TEXT, size=14, weight=ft.FontWeight.W_500 if is_active else ft.FontWeight.NORMAL),
|
||||
],
|
||||
spacing=12,
|
||||
),
|
||||
bgcolor=d.ADMIN_SIDEBAR_ACTIVE if is_active else None,
|
||||
border_radius=8,
|
||||
padding=ft.padding.symmetric(horizontal=16, vertical=12),
|
||||
on_click=lambda e, r=route: page.go(r),
|
||||
ink=True,
|
||||
)
|
||||
|
||||
return ft.Container(
|
||||
content=ft.Column(
|
||||
[ft.Container(height=16)] + [nav_item(i, l, r) for i, l, r in items],
|
||||
spacing=2,
|
||||
),
|
||||
bgcolor=d.ADMIN_SIDEBAR,
|
||||
width=220,
|
||||
padding=ft.padding.symmetric(horizontal=8, vertical=8),
|
||||
)
|
||||
Reference in New Issue
Block a user