# -*- coding: utf-8 -*- # hub/ticket/ui/dialogs/task_refusal_dialog.py """Диалог подтверждения отказа задачи Ticket.""" from __future__ import annotations from gui.components import Button, Dialog, Label, TextInput from gui.containers import HContainer, VContainer from domain import TicketTaskSnapshot, parse_location_parts class TaskRefusalDialog(Dialog): """Диалог ввода обязательной причины отказа по задаче.""" def __init__(self, task: TicketTaskSnapshot, parent=None): self._task = task self._reason_input: TextInput | None = None self._cancel_button: Button | None = None self._submit_button: Button | None = None super().__init__( title="Отказ в обслуживании", width=500, height=460, modal=True, parent=parent, ) self._setup_ui() self._connect_signals() self._refresh_submit_state() @property def refusal_reason(self) -> str: if self._reason_input is None: return "" return self._reason_input.get_text().strip() def _setup_ui(self) -> None: # Root-контейнер окна отказа: предупреждение, контекст задачи, поле причины и actions. main_container = VContainer(margin=[24, 20, 24, 20], spacing=16) self.add_widget(main_container) main_container.add_widget( Label( "Вы уверены, что хотите отказать в обслуживании?", alignment="left", style="TICKET_REFUSAL_HEADING", ) ) main_container.add_widget(self._build_location_row()) main_container.add_widget( Label( 'Задача будет перемещена в колонку "Отказ"', alignment="left", style="TICKET_REFUSAL_WARNING", ) ) main_container.add_widget( Label( "Причина отказа", alignment="left", style="TICKET_REFUSAL_HEADING", ) ) main_container.add_widget_with_stretch(self._build_reason_input(), 1) main_container.add_widget(self._build_actions()) def _build_location_row(self) -> HContainer: # Location-row: показывает локацию задачи, чтобы отказ происходил в явном контексте. row = HContainer(spacing=10, content_fit=True) row.add_widget( Label( "Локация и кабинет:", alignment="left", style="TICKET_REFUSAL_LOCATION_TITLE", ) ) value_label = Label( self._build_location_text(), alignment="left", style="TICKET_REFUSAL_LOCATION_VALUE", ) value_label.set_tooltip(self._build_location_text()) row.add_widget_with_stretch(value_label, 1) return row def _build_reason_input(self) -> TextInput: self._reason_input = TextInput( placeholder="Укажите причину отказа", style="TICKET_DOCUMENT_TEXTAREA", multiline=True, ) self._reason_input.set_min_height(170) return self._reason_input def _build_actions(self) -> HContainer: # Actions-row окна отказа: собирает кнопки отмены и финального подтверждения. actions = HContainer(spacing=18, content_fit=True) actions.add_stretch() self._cancel_button = Button( "Отмена", style="TICKET_DOCUMENT_CANCEL_BUTTON", content_fit=True, ) self._submit_button = Button( "Подтвердить отказ", style="TICKET_DOCUMENT_SUBMIT_BUTTON", content_fit=True, ) actions.add_widget(self._cancel_button) actions.add_widget(self._submit_button) return actions def _connect_signals(self) -> None: if self._reason_input is not None: self._reason_input.text_changed.connect(self._refresh_submit_state) 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._handle_accept) def _refresh_submit_state(self) -> None: if self._submit_button is not None: self._submit_button.set_enabled(bool(self.refusal_reason)) def _handle_accept(self) -> None: if not self.refusal_reason: return self.accept() def _build_location_text(self) -> str: institution, room, _ = parse_location_parts(self._task.location or "") normalized_institution = institution or "Локация не указана" normalized_room = room or "Кабинет не указан" return f"{normalized_institution}, {normalized_room}"