Files
Dispatch/Dispatch_V0.1.1/ui/dialogs/base_document_dialog.py
2026-04-29 08:18:54 +04:00

219 lines
8.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
# hub/ticket/ui/dialogs/base_document_dialog.py
"""Базовый UI-диалог документов Ticket без файловой и доменной логики."""
from __future__ import annotations
from gui.components import Button, Dialog, Label, TextInput
from gui.containers import HContainer, SContainer, VContainer
from domain import TicketTaskSnapshot, parse_location_parts
from ui.task_view_formatters import build_specialist_card_info
class BaseDocumentDialog(Dialog):
"""Базовый modal-диалог для ввода данных документа Ticket."""
def __init__(
self,
task: TicketTaskSnapshot,
title: str,
submit_text: str,
parent=None,
):
self._task = task
self._cancel_button: Button | None = None
self._submit_button: Button | None = None
self._form_text_edits: list[TextInput] = []
super().__init__(
title=title,
width=540,
height=740,
modal=True,
parent=parent,
)
self._setup_ui(submit_text)
self._connect_signals()
self._refresh_submit_state()
def _setup_ui(self, submit_text: str) -> None:
# Root-контейнер документа: собирает все основные зоны окна сверху вниз.
root = VContainer(margin=[24, 20, 24, 20], spacing=0)
self.add_widget(root)
root.add_widget(self._build_summary_shell())
root.add_widget(self._build_divider())
root.add_widget(self._build_form_shell())
root.add_widget(self._build_actions_section(submit_text))
def _build_summary_shell(self) -> SContainer:
# Summary-shell: верхняя область окна с базовой карточкой контекста документа.
summary_shell = SContainer(
height_percent=24,
orientation="v",
content_fit=False,
)
# Summary-content: вертикальный стек строк внутри summary-shell.
summary_content = VContainer(spacing=10, content_fit=True, parent=summary_shell)
for row in self._build_summary_rows():
summary_content.add_widget(row)
return summary_shell
def _build_summary_rows(self) -> list[HContainer]:
institution, room, device = parse_location_parts(self._task.location)
rows: list[HContainer] = []
for title, value in (
("Учреждение", institution or "Локация не указана"),
("Оборудование", device or "Аппарат не указан"),
("Кабинет", room or "Кабинет не указан"),
("Специалист", self._build_specialist_summary()),
):
rows.append(self._build_summary_row(title, value))
return rows
def _build_summary_row(self, title: str, value: str) -> HContainer:
# Summary-row: горизонтальная строка для пары "заголовок поля + значение".
row = HContainer(spacing=10, content_fit=True)
title_label = Label(
f"{title}:",
alignment="left",
style="TICKET_DETAILS_SUMMARY_TITLE",
)
value_label = Label(
value,
alignment="left",
style="TICKET_DETAILS_SUMMARY_VALUE",
)
value_label.set_tooltip(value)
row.add_widget(title_label)
row.add_widget_with_stretch(value_label, 1)
return row
def _build_divider(self) -> Label:
return Label(
"",
style="TICKET_DETAILS_DIVIDER",
height_percent=1,
)
def _build_form_shell(self) -> SContainer:
# Form-shell: центральная рабочая зона, куда наследник добавляет поля документа.
form_shell = SContainer(
height_percent=58,
orientation="v",
spacing=10,
content_fit=False,
)
self._build_form(form_shell)
return form_shell
def _build_actions_section(self, submit_text: str) -> HContainer:
# Actions-row: нижняя линия действий с фиксированной процентной сеткой.
actions = HContainer(
height_percent=11,
spacing=0,
content_fit=False,
)
# Левый spacer-контейнер: формирует стартовый отступ перед кнопкой отмены.
SContainer(
width_percent=10,
height_percent=100,
parent=actions,
)
self._cancel_button = Button(
"Отмена",
width_percent=26,
height_percent=100,
margin=0,
style="TICKET_DOCUMENT_CANCEL_BUTTON",
content_fit=False,
)
actions.add_widget(self._cancel_button)
# Центральный spacer-контейнер: удерживает зазор между двумя action-кнопками.
SContainer(
width_percent=4,
height_percent=100,
parent=actions,
)
self._submit_button = Button(
submit_text,
width_percent=50,
height_percent=100,
margin=0,
style="TICKET_DOCUMENT_SUBMIT_BUTTON",
content_fit=False,
)
actions.add_widget(self._submit_button)
# Правый spacer-контейнер: завершает строку действий симметричным отступом.
SContainer(
width_percent=10,
height_percent=100,
parent=actions,
)
self._submit_button.set_enabled(False)
return actions
def _connect_signals(self) -> None:
if self._cancel_button is not None:
self._cancel_button.clicked.connect(self.reject)
if self._submit_button is not None:
self._submit_button.clicked.connect(self.accept)
for text_edit in self._form_text_edits:
text_edit.text_changed.connect(self._refresh_submit_state)
self._connect_form_signals()
def build_payload(self) -> dict[str, str]:
"""Вернуть данные формы. Реализуется в наследниках."""
raise NotImplementedError
def _build_form(self, container: SContainer) -> None:
"""Построить специфическую часть формы."""
raise NotImplementedError
def _is_ready(self) -> bool:
"""Проверить, что форма готова к отправке."""
raise NotImplementedError
def _connect_form_signals(self) -> None:
"""Подключить сигналы элементов формы, если кроме текстовых полей есть другие элементы."""
return None
def _populate_text_block(
self,
field_shell: SContainer,
title: str,
placeholder: str,
) -> TextInput:
# Field-shell: ожидает внутри себя заголовок поля и текстовую область конкретной секции.
field_shell.add_widget(
Label(
title,
alignment="left",
style="TICKET_DETAILS_SECTION_TITLE",
)
)
text_edit = TextInput(
placeholder=placeholder,
style="TICKET_DOCUMENT_TEXTAREA",
multiline=True,
content_fit=False,
)
self._form_text_edits.append(text_edit)
field_shell.add_widget_with_stretch(text_edit, 1)
return text_edit
def _refresh_submit_state(self) -> None:
if self._submit_button is not None:
self._submit_button.set_enabled(self._is_ready())
def _build_specialist_summary(self) -> str:
specialist_name = self._task.assigned_specialist.strip()
if not specialist_name:
return "Не назначен"
specialist_info = build_specialist_card_info(specialist_name)
short_name = specialist_info["short_name"].strip() or specialist_name
position = specialist_info["position"].strip()
if not position:
return short_name
return f"{short_name}, {position}"