Files
api-copp/stats/designer.py

224 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
designer.py для stats-приложения.
Та же палитра и компоненты что в web/designer.py, адаптированные для дашборда.
"""
import flet as ft
SCALE: float = 1.0
def s(v: int | float) -> int:
return round(v * SCALE)
class colors:
primary = "#F85A40"
primary_dark = "#D94530"
primary_light = "#FFEAB7"
background = "#F4F6F8"
surface = "#FFFFFF"
surface_alt = "#F0F2F5"
accent = "#FFC845"
info = "#007FBD"
success = "#85C446"
text_primary = "#1E2A3A"
text_secondary = "#5A6779"
text_on_primary = "#FFFFFF"
border = "#DDE3EA"
class SearchableDropdown(ft.Container):
"""Выпадающий список с поисковой строкой."""
def __init__(self, label: str = "", hint_text: str = "", options=None, on_select=None, width: int | None = None):
self._label_str = label
self._hint_str = hint_text
self._options: list = options or []
self.on_select = on_select
self._value = None
self._value_text = ft.Text(
hint_text,
color=colors.border,
size=s(14),
italic=True,
expand=True,
no_wrap=True,
overflow=ft.TextOverflow.ELLIPSIS,
)
super().__init__(
content=ft.Column(
spacing=s(2),
tight=True,
controls=[
ft.Text(label, color=colors.text_secondary, size=s(12), weight=ft.FontWeight.W_500),
ft.Row(
controls=[
self._value_text,
ft.Icon(ft.Icons.ARROW_DROP_DOWN, color=colors.text_secondary, size=s(20)),
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
vertical_alignment=ft.CrossAxisAlignment.CENTER,
),
],
),
bgcolor=colors.surface,
border=ft.border.all(1, colors.border),
border_radius=s(10),
padding=ft.padding.symmetric(horizontal=s(12), vertical=s(10)),
on_click=self._open_dialog,
ink=True,
width=width or s(220),
)
@property
def value(self):
return self._value
@value.setter
def value(self, val):
self._value = val
if val is None:
self._value_text.value = self._hint_str
self._value_text.color = colors.border
self._value_text.italic = True
else:
for opt in self._options:
if opt.key == val:
self._value_text.value = opt.text
self._value_text.color = colors.text_primary
self._value_text.italic = False
break
try:
if self.page:
self.update()
except RuntimeError:
pass
@property
def options(self):
return self._options
@options.setter
def options(self, opts):
self._options = opts or []
@property
def hint_text(self):
return self._hint_str
@hint_text.setter
def hint_text(self, val):
self._hint_str = val
if self._value is None:
self._value_text.value = val
if self.page:
self.update()
def _open_dialog(self, e):
search_field = ft.TextField(
label="Поиск",
hint_text="Введите для поиска...",
prefix_icon=ft.Icons.SEARCH,
autofocus=True,
filled=True,
bgcolor=colors.surface,
border_color=colors.border,
focused_border_color=colors.primary,
border_radius=s(8),
text_style=ft.TextStyle(color=colors.text_primary, size=s(14)),
)
items_list = ft.ListView(spacing=0, expand=True)
def _build_items(query: str = ""):
q = query.lower()
items_list.controls = [
ft.ListTile(
title=ft.Text(opt.text, color=colors.text_primary, size=s(13)),
hover_color=colors.primary_light,
on_click=lambda ev, o=opt: _select(o),
)
for opt in self._options
if not q or q in opt.text.lower()
]
def _on_search(ev):
_build_items(search_field.value or "")
items_list.update()
def _select(option):
self._value = option.key
self._value_text.value = option.text
self._value_text.color = colors.text_primary
self._value_text.italic = False
dlg.open = False
self.page.update()
self.update()
if self.on_select:
self.on_select(type("_E", (), {"control": self})())
search_field.on_change = _on_search
_build_items()
dlg = ft.AlertDialog(
title=ft.Text(self._label_str, color=colors.text_primary, size=s(15), weight=ft.FontWeight.W_600),
content=ft.Container(
content=ft.Column(
controls=[search_field, ft.Divider(color=colors.border), items_list],
spacing=s(8),
tight=True,
),
height=s(320),
width=s(380),
),
bgcolor=colors.surface,
shape=ft.RoundedRectangleBorder(radius=s(12)),
)
self.page.overlay.append(dlg)
dlg.open = True
self.page.update()
def stat_card(title: str, value: str, icon: str, color: str) -> ft.Container:
"""Карточка с одной цифрой для summary-панели."""
return ft.Container(
content=ft.Column(
spacing=s(6),
tight=True,
controls=[
ft.Row(
spacing=s(8),
controls=[
ft.Icon(icon, color=color, size=s(20)),
ft.Text(title, color=colors.text_secondary, size=s(12)),
],
),
ft.Text(value, color=colors.text_primary, size=s(28), weight=ft.FontWeight.BOLD),
],
),
bgcolor=colors.surface,
border_radius=s(12),
padding=ft.padding.all(s(16)),
border=ft.border.all(1, colors.border),
expand=True,
)
def chart_card(title: str, body: ft.Control) -> ft.Container:
"""Обёртка-карточка для графика."""
return ft.Container(
content=ft.Column(
spacing=s(12),
controls=[
ft.Text(title, color=colors.text_primary, size=s(15), weight=ft.FontWeight.W_600),
body,
],
),
bgcolor=colors.surface,
border_radius=s(12),
padding=ft.padding.all(s(16)),
border=ft.border.all(1, colors.border),
expand=True,
)