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

224 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/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()