214 lines
8.4 KiB
Python
214 lines
8.4 KiB
Python
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}")
|
||
|