""" 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): 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, expand=True, ) @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 if self.page: self.update() @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, )