rename users

This commit is contained in:
2026-03-31 18:10:12 +05:00
parent 59b3bcd0f4
commit 4e9b89fbe5
22 changed files with 1262 additions and 163 deletions

View File

@@ -2,11 +2,88 @@ import flet as ft
import designer as ds
class MainPageUser(ft.Column):
def __init__(self):
def __init__(self, page: ft.Page):
self._page = page
self.spacing_groop = 6
self.border_radius = 15
super().__init__(
spacing=16,
alignment=ft.MainAxisAlignment.CENTER,
spacing=self.spacing_groop,
controls=[
ft.Text("Добро пожаловать в COPP!", color=ds.clolors.ferst, size=24, weight=ft.FontWeight.BOLD),
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
spacing=self.spacing_groop,
controls=[
ft.Container(
border_radius=self.border_radius,
height=200,
width=200,
alignment=ft.Alignment(0.0, 0.0),
content=ft.Column(
alignment=ft.MainAxisAlignment.CENTER,
controls=[
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[
ft.Image(
src="icons/clipboard-question_16542596.svg",
width=120,
height=120,
)
]
),
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[
ds.CastomText(text="Все тесты")
]
)
]
),
bgcolor=ds.clolors.laer2,
on_click=self.take_test,
ink=True,
),
ft.Container(
border_radius=self.border_radius,
height=200,
width=200,
alignment=ft.Alignment(0.0, 0.0),
content=ft.Column(
alignment=ft.MainAxisAlignment.CENTER,
controls=[
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[
ft.Image(
src="icons/leaderboard-trophy_14227576.svg",
width=120,
height=120,
)
]
),
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[
ds.CastomText(text="Мои тесты")
]
)
]
),
bgcolor=ds.clolors.laer2,
on_click=self.my_test,
ink=True,
)
]
),
],
)
)
def take_test(self, e):
url_key = self._page.session.store.get("url_key") or ""
self._page.go(f"/take_test/{url_key}")
def my_test(self, e):
url_key = self._page.session.store.get("url_key") or ""
self._page.go(f"/my_tests/{url_key}")

View File

@@ -0,0 +1,89 @@
import asyncio
import flet as ft
import designer as ds
import api_client
class MyTestsPage(ft.Column):
def __init__(self, page: ft.Page):
self._page = page
self._loading = ft.ProgressRing(width=40, height=40, color=ds.clolors.laer1)
super().__init__(
alignment=ft.MainAxisAlignment.START,
horizontal_alignment=ft.CrossAxisAlignment.STRETCH,
spacing=8,
controls=[
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[self._loading],
)
],
)
async def load(self):
user_id = self._page.session.store.get("user_id")
if not user_id:
self.controls = [ds.CastomText(text="Нет данных пользователя", size=14)]
self.update()
return
responses = await api_client.get_user_responses(user_id)
if not responses:
self.controls = [ds.CastomText(text="Вы ещё не проходили тесты", size=14)]
self.update()
return
# Подгружаем названия опросов параллельно
poll_ids = list({r["poll_id"] for r in responses})
polls_list = await asyncio.gather(*[api_client.get_poll(pid) for pid in poll_ids])
polls_map = {p["id"]: p for p in polls_list if p}
self._render(responses, polls_map)
self.update()
def _render(self, responses: list, polls_map: dict):
url_key = self._page.session.store.get("url_key") or ""
# Сортируем по дате — новые вверху
responses = sorted(responses, key=lambda r: r["submitted_at"], reverse=True)
cards = []
for r in responses:
poll = polls_map.get(r["poll_id"])
title = poll["title"] if poll else "Неизвестный тест"
submitted_at = r["submitted_at"][:19].replace("T", " ")
cards.append(
ft.Container(
border_radius=12,
padding=16,
bgcolor=ds.clolors.laer2,
content=ft.Row(
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
controls=[
ft.Column(
spacing=4,
controls=[
ds.CastomText(
text=title,
size=15,
weight=ft.FontWeight.BOLD,
),
ds.CastomText(
text=f"Пройден: {submitted_at}",
size=12,
),
],
),
ft.Icon(ft.Icons.CHEVRON_RIGHT, color=ds.clolors.ferst),
],
),
on_click=lambda e, rid=r["id"]: self._page.go(
f"/response/{url_key}/{rid}"
),
ink=True,
)
)
self.controls = cards

View File

@@ -0,0 +1,184 @@
import flet as ft
import designer as ds
import api_client
class PollPage(ft.Column):
def __init__(self, page: ft.Page, poll_id: str):
self._page = page
self._poll_id = poll_id
self._answers: dict[str, str] = {} # question_id -> choice_id
self._error_text = ds.CastomText(text="", size=13, color="#FF6B6B")
self._submit_btn = ds.CastomButton(
text="Отправить ответы",
on_click=self._submit,
)
self._submit_btn.visible = False
self._loading = ft.ProgressRing(width=40, height=40, color=ds.clolors.laer1)
super().__init__(
alignment=ft.MainAxisAlignment.START,
horizontal_alignment=ft.CrossAxisAlignment.STRETCH,
spacing=10,
controls=[
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[self._loading],
)
],
)
async def load(self):
poll = await api_client.get_poll(self._poll_id)
if poll:
self._render_poll(poll)
else:
self.controls = [ds.CastomText(text="Тест не найден", size=16, color="#FF6B6B")]
self.update()
def _render_poll(self, poll: dict):
self._poll = poll
question_cards = []
for q in poll.get("questions", []):
choices = q.get("choices", [])
q_type = q.get("type", "single")
if q_type == "single":
radio_controls = [
ft.Radio(
value=c["id"],
label=c["text"],
fill_color=ds.clolors.laer1,
label_style=ft.TextStyle(color=ds.clolors.ferst, size=14),
)
for c in choices
]
input_widget = ft.RadioGroup(
content=ft.Column(controls=radio_controls, spacing=4),
on_change=lambda e, qid=q["id"]: self._on_single_answer(qid, e.control.value),
)
else:
# multiple / text — показываем как single если нет multiple в API
radio_controls = [
ft.Radio(
value=c["id"],
label=c["text"],
fill_color=ds.clolors.laer1,
label_style=ft.TextStyle(color=ds.clolors.ferst, size=14),
)
for c in choices
]
input_widget = ft.RadioGroup(
content=ft.Column(controls=radio_controls, spacing=4),
on_change=lambda e, qid=q["id"]: self._on_single_answer(qid, e.control.value),
)
card = ft.Container(
border_radius=12,
padding=16,
margin=ft.margin.only(bottom=8),
bgcolor=ds.clolors.laer2,
content=ft.Column(
spacing=10,
controls=[
ds.CastomText(
text=q.get("text", ""),
size=15,
weight=ft.FontWeight.BOLD,
),
input_widget,
],
),
)
question_cards.append(card)
self.controls = [
ft.Container(
padding=ft.padding.only(bottom=4),
content=ft.Column(
spacing=4,
controls=[
ds.CastomText(
text=poll.get("title", ""),
size=20,
weight=ft.FontWeight.BOLD,
),
ds.CastomText(
text=poll.get("description") or "",
size=13,
),
],
),
),
*question_cards,
self._error_text,
ft.Row(
alignment=ft.MainAxisAlignment.END,
controls=[self._submit_btn],
),
]
self._submit_btn.visible = True
def _on_single_answer(self, question_id: str, choice_id: str):
self._answers[question_id] = choice_id
async def _submit(self, e):
poll = getattr(self, "_poll", None)
if not poll:
return
questions = poll.get("questions", [])
unanswered = [q for q in questions if q["id"] not in self._answers]
if unanswered:
self._error_text.value = f"Ответьте на все вопросы ({len(unanswered)} осталось)"
self._error_text.update()
return
self._error_text.value = ""
self._submit_btn.disabled = True
self._submit_btn.update()
answers = [
{"question_id": qid, "choice_id": cid, "text": None}
for qid, cid in self._answers.items()
]
user_id = self._page.session.store.get("user_id")
group_id = self._page.session.store.get("group_id") or None
org_id = self._page.session.store.get("org_id") or None
response_id, error = await api_client.submit_response(
self._poll_id, answers, user_id, group_id, org_id
)
if error:
self._error_text.value = error
self._submit_btn.disabled = False
self._submit_btn.update()
self._error_text.update()
else:
url_key = self._page.session.store.get("url_key") or ""
# Заменяем контент на сообщение об успехе
self.controls = [
ft.Container(
alignment=ft.Alignment(0, 0),
padding=40,
content=ft.Column(
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
spacing=16,
controls=[
ft.Icon(ft.Icons.CHECK_CIRCLE_OUTLINE, size=64, color=ds.clolors.laer1),
ds.CastomText(
text="Ответы отправлены!",
size=20,
weight=ft.FontWeight.BOLD,
),
ds.CastomButton(
text="Вернуться к тестам",
on_click=lambda _: self._page.go(f"/take_test/{url_key}"),
),
],
),
)
]
self.update()

View File

@@ -0,0 +1,131 @@
import asyncio
import flet as ft
import designer as ds
import api_client
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._loading = ft.ProgressRing(visible=False, width=24, height=24)
self._status = ft.Text(value="", size=14)
super().__init__(
spacing=16,
controls=[
ds.CastomText(
text="Редактировать профиль",
size=18,
weight=ft.FontWeight.BOLD,
),
self._first_name,
self._last_name,
self._org_dropdown,
self._group_dropdown,
ft.Row(
spacing=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.clolors.ferst)
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 = "Выберите группу..."
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()
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.strip() if self._first_name.value else None
ln = self._last_name.value.strip() if self._last_name.value else 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"
else:
self._status.value = "Данные успешно сохранены!"
self._status.color = ds.clolors.ferst
# Обновляем сессию для корректного сохранения при прохождении тестов
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 "")
self.update()

View File

@@ -0,0 +1,117 @@
import flet as ft
import designer as ds
import api_client
class ResponsePage(ft.Column):
def __init__(self, page: ft.Page, response_id: str):
self._page = page
self._response_id = response_id
self._loading = ft.ProgressRing(width=40, height=40, color=ds.clolors.laer1)
super().__init__(
alignment=ft.MainAxisAlignment.START,
horizontal_alignment=ft.CrossAxisAlignment.STRETCH,
spacing=8,
controls=[
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[self._loading],
)
],
)
async def load(self):
response = await api_client.get_response(self._response_id)
if not response:
self.controls = [ds.CastomText(text="Результат не найден", size=14, color="#FF6B6B")]
self.update()
return
poll = await api_client.get_poll(response["poll_id"])
if not poll:
self.controls = [ds.CastomText(text="Тест не найден", size=14, color="#FF6B6B")]
self.update()
return
self._render(response, poll)
self.update()
def _render(self, response: dict, poll: dict):
# Строим индексы для быстрого поиска
questions_map = {q["id"]: q for q in poll.get("questions", [])}
choices_map = {
c["id"]: c["text"]
for q in poll.get("questions", [])
for c in q.get("choices", [])
}
submitted_at = response["submitted_at"][:19].replace("T", " ")
answer_cards = []
for answer in response.get("answers", []):
q = questions_map.get(answer["question_id"])
if not q:
continue
selected_text = choices_map.get(answer.get("choice_id") or "") or answer.get("text") or ""
# Варианты с подсветкой выбранного
choice_rows = []
for c in q.get("choices", []):
is_selected = c["id"] == answer.get("choice_id")
choice_rows.append(
ft.Container(
border_radius=8,
padding=ft.padding.symmetric(horizontal=12, vertical=6),
bgcolor=ds.clolors.laer1 if is_selected else ds.clolors.laer3,
content=ft.Row(
spacing=8,
controls=[
ft.Icon(
ft.Icons.CHECK_CIRCLE if is_selected else ft.Icons.RADIO_BUTTON_UNCHECKED,
color=ds.clolors.ferst,
size=16,
),
ds.CastomText(text=c["text"], size=13),
],
),
)
)
answer_cards.append(
ft.Container(
border_radius=12,
padding=16,
bgcolor=ds.clolors.laer2,
content=ft.Column(
spacing=8,
controls=[
ds.CastomText(
text=q.get("text", ""),
size=14,
weight=ft.FontWeight.BOLD,
),
*choice_rows,
],
),
)
)
self.controls = [
ft.Container(
padding=ft.padding.only(bottom=8),
content=ft.Column(
spacing=4,
controls=[
ds.CastomText(
text=poll.get("title", ""),
size=20,
weight=ft.FontWeight.BOLD,
),
ds.CastomText(text=f"Пройден: {submitted_at}", size=12),
],
),
),
*answer_cards,
]

View File

@@ -0,0 +1,53 @@
import flet as ft
import designer as ds
import api_client
class TakeTestPage(ft.Column):
def __init__(self, page: ft.Page):
self._page = page
self._loading = ft.ProgressRing(width=40, height=40, color=ds.clolors.laer1)
super().__init__(
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
controls=[self._loading],
)
async def load(self):
await self._load_polls()
async def _load_polls(self):
polls = await api_client.get_polls()
if polls:
self._render_polls(polls)
else:
self.controls = [ds.CastomText(text="Тестов пока нет", size=16)]
self.update()
def _render_polls(self, polls: list):
self.controls = [
ft.Container(
border_radius=12,
padding=16,
bgcolor=ds.clolors.laer2,
content=ft.Column(
spacing=6,
controls=[
ds.CastomText(
text=poll.get("title", ""),
size=16,
weight=ft.FontWeight.BOLD,
),
ds.CastomText(
text=poll.get("description") or "",
size=13,
),
],
),
on_click=lambda e, pid=poll["id"]: self._page.go(
f"/poll/{self._page.session.store.get('url_key') or ''}/{pid}"
),
ink=True,
)
for poll in polls
]

View File

@@ -82,8 +82,8 @@ class LoginView(ft.View):
self._show_error(error)
else:
# Успешный вход — переходим на страницу пользователя
session_key = self.page.session.store.get("session_key")
await self.page.push_route(f"/user/{session_key}")
url_key = self.page.session.store.get("url_key")
await self.page.push_route(f"/user/{url_key}")
def _show_error(self, message: str):
self._error.value = message

View File

@@ -0,0 +1,46 @@
import flet as ft
import designer as ds
from views.content_users.my_tests_page import MyTestsPage
class MyTestsView(ft.View):
def __init__(self, page: ft.Page):
url_key = page.session.store.get("url_key") or ""
back_btn = ft.IconButton(
icon=ft.Icons.ARROW_BACK,
icon_color=ds.clolors.ferst,
on_click=lambda _: page.go(f"/user/{url_key}"),
)
self._content = MyTestsPage(page)
super().__init__(
route=f"/my_tests/{url_key}",
bgcolor=ds.clolors.background,
horizontal_alignment=ft.CrossAxisAlignment.STRETCH,
vertical_alignment=ft.MainAxisAlignment.START,
scroll=ft.ScrollMode.AUTO,
controls=[
ft.Column(
expand=True,
spacing=10,
controls=[
ft.Row(
controls=[
back_btn,
ds.CastomText(
text="Мои тесты",
size=22,
weight=ft.FontWeight.BOLD,
),
]
),
self._content,
],
)
],
)
async def load(self):
await self._content.load()

46
web/views/poll_view.py Normal file
View File

@@ -0,0 +1,46 @@
import flet as ft
import designer as ds
from views.content_users.poll_page import PollPage
class PollView(ft.View):
def __init__(self, page: ft.Page, poll_id: str):
url_key = page.session.store.get("url_key") or ""
back_btn = ft.IconButton(
icon=ft.Icons.ARROW_BACK,
icon_color=ds.clolors.ferst,
on_click=lambda _: page.go(f"/take_test/{url_key}"),
)
self._content = PollPage(page, poll_id)
super().__init__(
route=f"/poll/{url_key}/{poll_id}",
bgcolor=ds.clolors.background,
horizontal_alignment=ft.CrossAxisAlignment.STRETCH,
vertical_alignment=ft.MainAxisAlignment.START,
scroll=ft.ScrollMode.AUTO,
controls=[
ft.Column(
expand=True,
spacing=10,
controls=[
ft.Row(
controls=[
back_btn,
ds.CastomText(
text="Прохождение теста",
size=22,
weight=ft.FontWeight.BOLD,
),
]
),
self._content,
],
)
],
)
async def load(self):
await self._content.load()

46
web/views/profile_view.py Normal file
View File

@@ -0,0 +1,46 @@
import flet as ft
import designer as ds
from views.content_users.profile_page import ProfilePage
class ProfileView(ft.View):
def __init__(self, page: ft.Page):
url_key = page.session.store.get("url_key") or ""
back_btn = ft.IconButton(
icon=ft.Icons.ARROW_BACK,
icon_color=ds.clolors.ferst,
on_click=lambda _: page.go(f"/user/{url_key}"),
)
self._content = ProfilePage(page)
super().__init__(
route=f"/profile/{url_key}",
bgcolor=ds.clolors.background,
horizontal_alignment=ft.CrossAxisAlignment.STRETCH,
vertical_alignment=ft.MainAxisAlignment.START,
scroll=ft.ScrollMode.AUTO,
controls=[
ft.Column(
expand=True,
spacing=10,
controls=[
ft.Row(
controls=[
back_btn,
ds.CastomText(
text="Профиль",
size=22,
weight=ft.FontWeight.BOLD,
),
]
),
self._content,
],
)
],
)
async def load(self):
await self._content.load()

View File

@@ -0,0 +1,46 @@
import flet as ft
import designer as ds
from views.content_users.response_page import ResponsePage
class ResponseView(ft.View):
def __init__(self, page: ft.Page, response_id: str):
url_key = page.session.store.get("url_key") or ""
back_btn = ft.IconButton(
icon=ft.Icons.ARROW_BACK,
icon_color=ds.clolors.ferst,
on_click=lambda _: page.go(f"/my_tests/{url_key}"),
)
self._content = ResponsePage(page, response_id)
super().__init__(
route=f"/response/{url_key}/{response_id}",
bgcolor=ds.clolors.background,
horizontal_alignment=ft.CrossAxisAlignment.STRETCH,
vertical_alignment=ft.MainAxisAlignment.START,
scroll=ft.ScrollMode.AUTO,
controls=[
ft.Column(
expand=True,
spacing=10,
controls=[
ft.Row(
controls=[
back_btn,
ds.CastomText(
text="Результаты теста",
size=22,
weight=ft.FontWeight.BOLD,
),
]
),
self._content,
],
)
],
)
async def load(self):
await self._content.load()

View File

@@ -0,0 +1,44 @@
import flet as ft
import designer as ds
from views.content_users.take_test_page import TakeTestPage
class TakeTestView(ft.View):
def __init__(self, page: ft.Page):
url_key = page.session.store.get("url_key") or ""
back_btn = ft.IconButton(
icon=ft.Icons.ARROW_BACK,
icon_color=ds.clolors.ferst,
on_click=lambda _: page.go(f"/user/{url_key}"),
)
self._content = TakeTestPage(page)
super().__init__(
route=f"/take_test/{url_key}",
bgcolor=ds.clolors.background,
horizontal_alignment=ft.CrossAxisAlignment.START,
vertical_alignment=ft.MainAxisAlignment.START,
controls=[
ft.Column(
expand=True,
controls=[
ft.Row(
controls=[
back_btn,
ds.CastomText(
text="Все тесты",
size=22,
weight=ft.FontWeight.BOLD,
),
]
),
self._content,
],
)
],
)
async def load(self):
await self._content.load()

View File

@@ -1,65 +1,24 @@
import flet as ft
import designer as ds
from info import AuthService
from views.content_users.main_page import MainPageUser
class UserView(ft.View):
def __init__(self, page: ft.Page):
self._auth = AuthService(page)
session_key = page.session.store.get("session_key") or ""
self.spacing_groop = 6
self.border_radius = 15
self.content = ft.Container(
expand=True,
content=ft.Column(
alignment=ft.MainAxisAlignment.CENTER,
spacing=self.spacing_groop,
controls=[
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
spacing=self.spacing_groop,
controls=[
ft.Container(
border_radius=self.border_radius,
height=200,
width=200,
bgcolor=ds.clolors.laer2
),
ft.Container(
border_radius=self.border_radius,
height=200,
width=200,
bgcolor=ds.clolors.laer2
)
]
),
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
spacing=self.spacing_groop,
controls=[
ft.Container(
border_radius=self.border_radius,
height=200,
width=200,
bgcolor=ds.clolors.laer2
),
ft.Container(
border_radius=self.border_radius,
height=200,
width=200,
bgcolor=ds.clolors.laer2
)
]
),
],
)
url_key = page.session.store.get("url_key") or ""
profile_btn = ft.IconButton(
icon=ft.Icons.ACCOUNT_CIRCLE,
icon_color=ds.clolors.ferst,
icon_size=30,
tooltip="Профиль",
on_click=lambda _: page.go(f"/profile/{url_key}"),
)
super().__init__(
route=f"/user/{session_key}",
route=f"/user/{url_key}",
bgcolor=ds.clolors.background,
horizontal_alignment=ft.CrossAxisAlignment.START,
vertical_alignment=ft.MainAxisAlignment.START,
@@ -67,15 +26,16 @@ class UserView(ft.View):
self.controls = [
ft.Column(
expand=True,
alignment=ft.MainAxisAlignment.CENTER,
alignment=ft.MainAxisAlignment.START,
controls=[
ft.Row(
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
controls=[
ft.Text("Добро пожаловать в COPP!", color=ds.clolors.ferst, size=24, weight=ft.FontWeight.BOLD),
ds.CastomText(text="Добро пожаловать в COPP!", size=24, weight=ft.FontWeight.BOLD),
profile_btn,
]
),
self.content
MainPageUser(page),
]
)
]