# -*- coding: utf-8 -*- # hub/ticket/ui/dialogs/specialist_dialog.py """UI-диалог выбора специалиста Ticket.""" from __future__ import annotations from collections.abc import Sequence from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QMouseEvent from gui.components import Button, Dialog, Label from gui.containers import HContainer, ScrollContainer, SContainer, VContainer from ui.cards.task_card_pixmap_factory import ( build_placeholder_avatar_pixmap, load_avatar_pixmap, ) from ui.task_view_formatters import ( build_specialist_card_info, build_specialist_photo_path, ) class _SpecialistRow(SContainer): """Строка выбора специалиста с фото, именем и должностью.""" clicked = Signal(str) activated = Signal(str) def __init__(self, specialist_name: str, parent=None): super().__init__( margin=0, spacing=0, content_fit=True, parent=parent, style="TICKET_SPECIALIST_ITEM", active_style="TICKET_SPECIALIST_ITEM_SELECTED", is_active=False, ) self._specialist_name = specialist_name self._info = build_specialist_card_info(specialist_name) self._name_label: Label | None = None self._role_label: Label | None = None self._avatar_label: Label | None = None self.setObjectName("ticket_specialist_row") self.setCursor(Qt.CursorShape.PointingHandCursor) self.set_min_height(88) self._setup_ui() @property def specialist_name(self) -> str: return self._specialist_name def set_selected(self, selected: bool) -> None: self.style(is_active=selected) def mousePressEvent(self, event: QMouseEvent) -> None: if event.button() == Qt.MouseButton.LeftButton: self.clicked.emit(self._specialist_name) super().mousePressEvent(event) def mouseDoubleClickEvent(self, event: QMouseEvent) -> None: if event.button() == Qt.MouseButton.LeftButton: self.clicked.emit(self._specialist_name) self.activated.emit(self._specialist_name) super().mouseDoubleClickEvent(event) def _setup_ui(self) -> None: # Body-row карточки специалиста: фото слева и текстовый блок справа. body = HContainer(margin=[12, 10, 12, 10], spacing=16, content_fit=True, parent=self) body.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True) self._avatar_label = Label("", style="TICKET_TASK_CARD_AVATAR_IMAGE") self._avatar_label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True) self._avatar_label.set_fixed_size(72, 72) self._avatar_label.set_pixmap(self._build_avatar_pixmap()) self._name_label = Label( self._specialist_name, alignment="left", style="TICKET_DETAILS_SECTION_TITLE", ) self._name_label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True) self._role_label = Label( self._info.get("position", "").strip() or "Специалист", alignment="left", style="TICKET_SPECIALIST_ROLE", ) self._role_label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True) # Text-block специалиста: вертикальный контейнер имени и должности. text_block = VContainer(spacing=2, content_fit=True) text_block.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True) text_block.add_widget(self._name_label) text_block.add_widget(self._role_label) body.add_widget(self._avatar_label) body.add_widget_with_stretch(text_block, 1) def _build_avatar_pixmap(self): photo_path = build_specialist_photo_path( self._specialist_name, self._info.get("photo", ""), ) avatar = load_avatar_pixmap(photo_path, 72, 72, padding=2) if avatar is None: return build_placeholder_avatar_pixmap(72) return avatar class SpecialistDialog(Dialog): """Диалог выбора специалиста без application-логики.""" def __init__( self, specialists: Sequence[str], parent=None, ): self._specialists = [str(item).strip() for item in specialists if str(item).strip()] self._selected_specialist = "" self._rows: dict[str, _SpecialistRow] = {} self._cancel_button: Button | None = None self._submit_button: Button | None = None super().__init__( title="Выбор специалиста", width=540, height=740, modal=True, parent=parent, ) self._setup_ui() self._connect_signals() self._refresh_submit_state() @property def selected_specialist(self) -> str: return self._selected_specialist def _setup_ui(self) -> None: # Root-контейнер диалога: подсказка, список специалистов и action-строка. main_container = VContainer(margin=[22, 20, 22, 20], spacing=16) self.add_widget(main_container) main_container.add_widget( Label( "Выберите специалиста для назначения на\nзадачу:", alignment="left", style="TICKET_SPECIALIST_HINT", ) ) main_container.add_widget_with_stretch(self._build_list(), 1) main_container.add_widget(self._build_actions()) def _build_list(self) -> ScrollContainer: # Scroll-list специалистов: прокручиваемая область с вариантами назначения. scroll = ScrollContainer( spacing=12, orientation="v", content_margins=[0, 0, 0, 0], vertical_scroll_bar_policy="as_needed", horizontal_scroll_bar_policy="always_off", style="SCROLL_CONTAINER", ) for specialist_name in self._specialists: row = _SpecialistRow(specialist_name) self._rows[specialist_name] = row scroll.add_widget(row) return scroll 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._cancel_button.set_min_width(160) self._cancel_button.set_min_height(56) self._submit_button = Button( "Выбрать", style="TICKET_DOCUMENT_SUBMIT_BUTTON", content_fit=True, ) self._submit_button.set_min_width(180) self._submit_button.set_min_height(56) actions.add_widget(self._cancel_button) actions.add_widget(self._submit_button) return actions def _connect_signals(self) -> None: for row in self._rows.values(): row.clicked.connect(self._on_row_clicked) row.activated.connect(self._on_row_activated) if self._submit_button is not None: self._submit_button.clicked.connect(self._handle_accept) if self._cancel_button is not None: self._cancel_button.clicked.connect(self.reject) def _refresh_submit_state(self) -> None: if self._submit_button is not None: self._submit_button.set_enabled(bool(self._selected_specialist)) def _set_selected_specialist(self, specialist_name: str) -> None: self._selected_specialist = specialist_name if specialist_name in self._rows else "" for row_name, row in self._rows.items(): row.set_selected(row_name == self._selected_specialist) self._refresh_submit_state() def _handle_accept(self) -> None: if not self._selected_specialist: return self.accept() def _on_row_clicked(self, specialist_name: str) -> None: self._set_selected_specialist(specialist_name) def _on_row_activated(self, specialist_name: str) -> None: self._set_selected_specialist(specialist_name) self._handle_accept()