Add Dispatch_V0.1.1
This commit is contained in:
223
Dispatch_V0.1.1/ui/dialogs/specialist_dialog.py
Normal file
223
Dispatch_V0.1.1/ui/dialogs/specialist_dialog.py
Normal file
@@ -0,0 +1,223 @@
|
||||
# -*- 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()
|
||||
Reference in New Issue
Block a user