import flet as ft import designer as d import api_client as api from views.dashboard import _admin_appbar, _sidebar class ArticlesListView: def __init__(self, page: ft.Page): self.page = page self._table_col = ft.Column(spacing=0, expand=True) self._filter = "all" def build_page_view(self) -> ft.View: filter_tabs = ft.Tabs( selected_index=0, on_change=self._on_filter, tabs=[ ft.Tab(text="Все"), ft.Tab(text="Опубликовано"), ft.Tab(text="Черновики"), ], ) return ft.View( route="/admin/articles", bgcolor=d.BACKGROUND, appbar=_admin_appbar(self.page, "Статьи"), controls=[ ft.Row( [ _sidebar(self.page, active="/admin/articles"), ft.Container( content=ft.Column( [ ft.Row( [ d.headline("Статьи", size=22), ft.Row( [ d.btn_primary( "Новая статья", icon=ft.icons.ADD, on_click=lambda e: self.page.go("/admin/articles/new"), ) ] ), ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN, ), filter_tabs, self._table_col, ], expand=True, scroll=ft.ScrollMode.AUTO, spacing=16, ), expand=True, padding=24, ), ], expand=True, spacing=0, ) ], ) async def load(self): await self._load_articles() async def _on_filter(self, e): idx = e.control.selected_index self._filter = ["all", "published", "draft"][idx] await self._load_articles() async def _load_articles(self): token = self.page.session.get("token") status = None if self._filter == "all" else self._filter data = await api.admin_list_articles(token, limit=200, status=status) items = data.get("items", []) if data else [] self._render(items) def _render(self, items: list): if not items: self._table_col.controls = [ ft.Container( content=d.empty_state(ft.icons.ARTICLE_OUTLINED, "Статей пока нет"), alignment=ft.alignment.center, padding=40, ) ] self.page.update() return header = ft.Container( content=ft.Row( [ ft.Text("Заголовок", size=12, color=d.TEXT_SECONDARY, expand=3), ft.Text("Категория", size=12, color=d.TEXT_SECONDARY, expand=1), ft.Text("Статус", size=12, color=d.TEXT_SECONDARY, width=110), ft.Text("Дата", size=12, color=d.TEXT_SECONDARY, width=110), ft.Text("Действия", size=12, color=d.TEXT_SECONDARY, width=130), ], spacing=8, ), bgcolor=d.BACKGROUND, padding=ft.padding.symmetric(horizontal=16, vertical=10), border_radius=ft.BorderRadius(8, 8, 0, 0), border=ft.Border(bottom=ft.BorderSide(1, d.DIVIDER)), ) rows = [header] + [self._build_row(a) for a in items] self._table_col.controls = [ ft.Container( content=ft.Column(rows, spacing=0), bgcolor=d.SURFACE, border_radius=8, border=ft.border.all(1, d.DIVIDER), ) ] self.page.update() def _build_row(self, article: dict) -> ft.Container: pub_date = "" if article.get("published_at"): pub_date = article["published_at"][:10] elif article.get("created_at"): pub_date = article["created_at"][:10] cat_name = article.get("category", {}) cat_name = (cat_name or {}).get("name", "—") return ft.Container( content=ft.Row( [ ft.Text( article["title"], size=14, expand=3, max_lines=2, overflow=ft.TextOverflow.ELLIPSIS, ), ft.Text(cat_name, size=13, color=d.TEXT_SECONDARY, expand=1), ft.Container(content=d.status_badge(article.get("status", "draft")), width=110), ft.Text(pub_date, size=12, color=d.TEXT_SECONDARY, width=110), ft.Row( [ d.btn_icon( ft.icons.EDIT_OUTLINED, "Редактировать", on_click=lambda e, aid=article["id"]: self.page.go(f"/admin/articles/{aid}"), color=d.PRIMARY, ), *([ d.btn_icon( ft.icons.PUBLISH, "Опубликовать", on_click=lambda e, aid=article["id"]: self.page.run_task(self._publish, aid), color=d.SUCCESS, ) ] if article.get("status") == "draft" else []), d.btn_icon( ft.icons.DELETE_OUTLINE, "Удалить", on_click=lambda e, a=article: self._confirm_delete(a), color=d.ERROR, ), ], spacing=0, width=130, ), ], spacing=8, vertical_alignment=ft.CrossAxisAlignment.CENTER, ), padding=ft.padding.symmetric(horizontal=16, vertical=12), border=ft.Border(bottom=ft.BorderSide(1, d.DIVIDER)), ink=True, ) def _confirm_delete(self, article: dict): dlg = d.confirm_dialog( self.page, "Удалить статью?", f'"{article["title"]}" будет удалена навсегда.', lambda: self.page.run_task(self._delete, article["id"]), ) self.page.dialog = dlg dlg.open = True self.page.update() async def _delete(self, article_id: str): token = self.page.session.get("token") ok = await api.admin_delete_article(token, article_id) if ok: d.snack(self.page, "Статья удалена") await self._load_articles() else: d.snack(self.page, "Ошибка удаления", error=True) async def _publish(self, article_id: str): token = self.page.session.get("token") ok = await api.admin_publish_article(token, article_id) if ok: d.snack(self.page, "Статья опубликована") await self._load_articles() else: d.snack(self.page, "Ошибка публикации", error=True)