Files
api-copp/web/views/content_users/poll_page.py
2026-04-01 14:18:52 +05:00

240 lines
8.7 KiB
Python

import base64
import flet as ft
import designer as ds
import api_client
from radar_chart import RadarChart
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.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[ft.ProgressRing(width=40, height=40, color=ds.clolors.laer1)],
)
]
self.update()
radar = await api_client.get_radar_result(response_id)
success_controls: list[ft.Control] = [
ft.Icon(ft.Icons.CHECK_CIRCLE_OUTLINE, size=48, color=ds.clolors.laer1),
ds.CastomText(text="Тест пройден!", size=20, weight=ft.FontWeight.BOLD),
]
if radar and radar.get("items"):
items = sorted(
radar["items"],
key=lambda x: (
x.get("dimension_position") is None,
x.get("dimension_position", 0),
),
)
actual_max = max((it["value"] for it in items), default=1.0)
max_val = max(actual_max, 14.0)
success_controls.append(
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[RadarChart(items, max_val)],
)
)
async def _download(_evt, _rid=response_id):
svg_bytes = await api_client.get_radar_svg_bytes(_rid)
if svg_bytes:
b64 = base64.b64encode(svg_bytes).decode()
uri = f"data:image/svg+xml;base64,{b64}"
self._page.launch_url(uri, web_window_name="_blank")
success_controls.append(
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
controls=[
ft.ElevatedButton(
text="Сохранить диаграмму",
icon=ft.Icons.DOWNLOAD,
on_click=_download,
style=ft.ButtonStyle(
color=ds.clolors.ferst,
bgcolor=ds.clolors.laer2,
),
)
],
)
)
success_controls.append(
ds.CastomButton(
text="Вернуться к тестам",
on_click=lambda _: self._page.go(f"/take_test/{url_key}"),
)
)
self.controls = [
ft.Container(
padding=20,
content=ft.Column(
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
spacing=16,
controls=success_controls,
),
)
]
self.update()