import asyncio import flet as ft import designer as ds import api_client import theme_manager as tm class ProfilePage(ft.Column): def __init__(self, page: ft.Page): self._page = page self._orgs: list = [] self._first_name = ds.CastomTextField_input( label="Имя", hint_text="Введите имя...", prefix_icon=ft.Icons.PERSON, ) self._last_name = ds.CastomTextField_input( label="Фамилия", hint_text="Введите фамилию...", prefix_icon=ft.Icons.PERSON_OUTLINE, ) self._org_dropdown = ds.CastomDropdown( label="Организация", hint_text="Загрузка...", ) self._org_dropdown.on_select = self._on_org_change self._group_dropdown = ds.CastomDropdown( label="Группа", hint_text="Загрузка...", ) # ── Выбор темы ───────────────────────────────────────────────── self._theme_dropdown = ds.CastomDropdown( label="Тема интерфейса", hint_text="Выберите тему...", options=[ ft.DropdownOption(key=t["id"], text=t["name"]) for t in tm.theme_list() ], ) self._theme_dropdown.on_select = self._on_theme_preview # ── Слайдер масштаба ─────────────────────────────────────────── self._scale_value = ds.SCALE self._scale_label = ft.Text( value=f"Масштаб: {ds.SCALE:.1f}×", size=ds.s(13), color=ds.colors.text_secondary, ) self._scale_slider = ft.Slider( min=tm.SCALE_MIN, max=tm.SCALE_MAX, divisions=round((tm.SCALE_MAX - tm.SCALE_MIN) / tm.SCALE_STEP), value=ds.SCALE, label="{value}×", active_color=ds.colors.primary, inactive_color=ds.colors.surface_alt, thumb_color=ds.colors.primary, on_change=self._on_scale_preview, on_change_end=self._on_scale_apply, ) self._loading = ft.ProgressRing(visible=False, width=ds.s(24), height=ds.s(24)) self._status = ft.Text(value="", size=ds.s(14)) super().__init__( spacing=ds.s(16), controls=[ ds.CastomText("Редактировать профиль", size=18, weight=ft.FontWeight.BOLD), self._first_name, self._last_name, self._org_dropdown, self._group_dropdown, ft.Divider(height=ds.s(8), color=ds.colors.border), ds.CastomText("Внешний вид", size=16, weight=ft.FontWeight.BOLD), self._theme_dropdown, self._scale_label, self._scale_slider, ft.Row( spacing=ds.s(12), controls=[ ds.CastomButton(text="Сохранить", on_click=self._save), self._loading, ], ), self._status, ], ) # ── загрузка данных ───────────────────────────────────────────────────── async def load(self): token = self._page.session.store.get("auth_token") or "" me, orgs, groups = await asyncio.gather( api_client.get_me(token), api_client.get_organizations(), api_client.get_groups(), ) self._orgs = orgs or [] _opt_style = ft.ButtonStyle(color=ds.colors.text_primary) self._org_dropdown.options = [ ft.DropdownOption(key=o["id"], text=o["name_organization"], style=_opt_style) for o in self._orgs ] self._org_dropdown.hint_text = "Выберите организацию..." self._group_dropdown.options = [ ft.DropdownOption(key=g["id"], text=g["name_group"], style=_opt_style) for g in (groups or []) ] self._group_dropdown.hint_text = "Выберите группу..." # устанавливаем текущие настройки внешнего вида self._theme_dropdown.value = tm.load_theme_id(self._page) saved_scale = tm.load_scale(self._page) self._scale_slider.value = saved_scale self._scale_value = saved_scale self._scale_label.value = f"Масштаб: {saved_scale:.1f}×" if me: self._first_name.value = me.get("first_name", "") self._last_name.value = me.get("last_name", "") self._org_dropdown.value = me.get("organization_id") self._group_dropdown.value = me.get("group_id") self.update() # ── обработчики ───────────────────────────────────────────────────────── def _on_org_change(self, e): selected_id = e.control.value for o in self._orgs: if o["id"] == selected_id: self._group_dropdown.value = o.get("group_id") break self.update() def _on_theme_preview(self, e): """Применить тему предварительно (только bgcolor страницы).""" theme_id = e.control.value if theme_id: tm.apply_theme(self._page, theme_id) self._page.update() def _on_scale_preview(self, e): """Обновить подпись слайдера во время перетаскивания.""" v = round(e.control.value, 1) self._scale_value = v self._scale_label.value = f"Масштаб: {v:.1f}×" self._scale_label.update() def _on_scale_apply(self, e): """Применить масштаб сразу при отпускании слайдера.""" scale = round(e.control.value, 1) self._scale_value = scale theme_id = self._theme_dropdown.value or tm.load_theme_id(self._page) tm.save_preferences(self._page, theme_id, scale) tm.apply_scale(self._page, scale) tm.apply_theme(self._page, theme_id) url_key = self._page.session.store.get("url_key") or "" self._page.go(f"/profile/{url_key}") # ── сохранение ────────────────────────────────────────────────────────── async def _save(self, e): self._loading.visible = True self._status.value = "" self.update() token = self._page.session.store.get("auth_token") or "" user_id = self._page.session.store.get("user_id") or "" fn = (self._first_name.value or "").strip() or None ln = (self._last_name.value or "").strip() or None data, err = await api_client.update_user( user_id=user_id, token=token, first_name=fn, last_name=ln, group_id=self._group_dropdown.value, organization_id=self._org_dropdown.value, ) self._loading.visible = False if err: self._status.value = err self._status.color = "red" self.update() return # всё успешно — сохраняем тему и масштаб в localStorage theme_id = self._theme_dropdown.value or "default" scale = self._scale_value tm.save_preferences(self._page, theme_id, scale) tm.apply_scale(self._page, scale) tm.apply_theme(self._page, theme_id) if data: self._page.session.store.set("group_id", data.get("group_id") or "") self._page.session.store.set("org_id", data.get("organization_id") or "") # перезагружаем текущую страницу — пересоздаёт все виджеты # с новым SCALE и новыми цветами url_key = self._page.session.store.get("url_key") or "" self._page.go(f"/profile/{url_key}")