rename users
This commit is contained in:
@@ -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}")
|
||||
89
web/views/content_users/my_tests_page.py
Normal file
89
web/views/content_users/my_tests_page.py
Normal 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
|
||||
184
web/views/content_users/poll_page.py
Normal file
184
web/views/content_users/poll_page.py
Normal 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()
|
||||
131
web/views/content_users/profile_page.py
Normal file
131
web/views/content_users/profile_page.py
Normal 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()
|
||||
|
||||
117
web/views/content_users/response_page.py
Normal file
117
web/views/content_users/response_page.py
Normal 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,
|
||||
]
|
||||
53
web/views/content_users/take_test_page.py
Normal file
53
web/views/content_users/take_test_page.py
Normal 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
|
||||
]
|
||||
@@ -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
|
||||
|
||||
46
web/views/my_tests_view.py
Normal file
46
web/views/my_tests_view.py
Normal 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
46
web/views/poll_view.py
Normal 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
46
web/views/profile_view.py
Normal 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()
|
||||
46
web/views/response_view.py
Normal file
46
web/views/response_view.py
Normal 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()
|
||||
44
web/views/take_test_view.py
Normal file
44
web/views/take_test_view.py
Normal 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()
|
||||
@@ -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),
|
||||
]
|
||||
)
|
||||
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user