Add Dispatch_V0.1.1
This commit is contained in:
279
Dispatch_V0.1.1/ui/details/task_details_dialog.py
Normal file
279
Dispatch_V0.1.1/ui/details/task_details_dialog.py
Normal file
@@ -0,0 +1,279 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# hub/ticket/ui/details/task_details_dialog.py
|
||||
|
||||
"""Dialog-экран подробностей задачи Ticket в ecco-геометрии."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from gui.components import Button, Dialog, Label
|
||||
from gui.containers import HContainer, VContainer
|
||||
|
||||
from application.ticket_application_api import TicketApplicationApi
|
||||
from domain import TicketTaskSnapshot
|
||||
from ui.cards.task_card_pixmap_factory import (
|
||||
build_placeholder_avatar_pixmap,
|
||||
load_avatar_pixmap,
|
||||
)
|
||||
from .task_details_actions import TaskDetailsActions
|
||||
from .task_details_view_data import (
|
||||
build_employee_view_data,
|
||||
build_task_stage_rows,
|
||||
build_task_summary_rows,
|
||||
can_archive_task,
|
||||
can_refuse_task,
|
||||
)
|
||||
from .task_stage_action_row import TaskStageActionRow
|
||||
|
||||
|
||||
class TaskDetailsDialog(Dialog):
|
||||
"""Диалог подробностей задачи и доступных действий по этапам."""
|
||||
|
||||
_SUMMARY_TITLES = (
|
||||
"Учреждение",
|
||||
"Оборудование",
|
||||
"Кабинет",
|
||||
"Назначение",
|
||||
)
|
||||
_STAGE_ORDER = (
|
||||
"specialist",
|
||||
"diagnostic",
|
||||
"repair",
|
||||
"acceptance",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
application: TicketApplicationApi,
|
||||
task_id: int,
|
||||
parent=None,
|
||||
):
|
||||
self._application = application
|
||||
self._task_id = task_id
|
||||
self._task: TicketTaskSnapshot | None = None
|
||||
self._actions = TaskDetailsActions(application)
|
||||
self._summary_value_labels: dict[str, Label] = {}
|
||||
self._stage_rows: dict[str, TaskStageActionRow] = {}
|
||||
self._employee_avatar_label: Label | None = None
|
||||
self._employee_name_label: Label | None = None
|
||||
self._employee_role_label: Label | None = None
|
||||
self._refuse_button: Button | None = None
|
||||
super().__init__(
|
||||
title="Подробности",
|
||||
width=360,
|
||||
height=600,
|
||||
modal=True,
|
||||
parent=parent,
|
||||
)
|
||||
self._setup_ui()
|
||||
self._connect_signals()
|
||||
self._reload_task()
|
||||
|
||||
def _setup_ui(self) -> None:
|
||||
# Root-контейнер окна: раскладывает summary, этапы, сотрудника и footer по вертикали.
|
||||
root = VContainer(margin=[24, 20, 24, 20], spacing=20)
|
||||
self.add_widget(root)
|
||||
root.add_widget(self._build_summary_section())
|
||||
root.add_widget(Label("", style="TICKET_DETAILS_DIVIDER"))
|
||||
root.add_widget(self._build_stages_section())
|
||||
root.add_widget(self._build_employee_section())
|
||||
root.add_stretch()
|
||||
root.add_widget(self._build_footer_section())
|
||||
|
||||
def _build_summary_section(self) -> VContainer:
|
||||
# Summary-section: верхняя сводка по задаче с основными идентификационными полями.
|
||||
summary = VContainer(spacing=12, content_fit=True)
|
||||
for title in self._SUMMARY_TITLES:
|
||||
summary.add_widget(self._build_summary_row(title))
|
||||
return summary
|
||||
|
||||
def _build_summary_row(self, title: 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(
|
||||
"",
|
||||
alignment="left",
|
||||
style="TICKET_DETAILS_SUMMARY_VALUE",
|
||||
)
|
||||
self._summary_value_labels[title] = value_label
|
||||
row.add_widget(title_label)
|
||||
row.add_widget_with_stretch(value_label, 1)
|
||||
return row
|
||||
|
||||
def _build_stages_section(self) -> VContainer:
|
||||
# Stages-section: блок со списком шагов выполнения и доступных действий по ним.
|
||||
section = VContainer(spacing=12, content_fit=True)
|
||||
section.add_widget(
|
||||
Label(
|
||||
"Этапы выполнения заявки",
|
||||
alignment="left",
|
||||
style="TICKET_DETAILS_SECTION_TITLE",
|
||||
)
|
||||
)
|
||||
# Stage-list: стек интерактивных строк этапов внутри секции stages.
|
||||
stage_list = VContainer(spacing=12, content_fit=True)
|
||||
for stage_key in self._STAGE_ORDER:
|
||||
stage_row = TaskStageActionRow(stage_key)
|
||||
stage_row.clicked.connect(self._on_stage_clicked)
|
||||
self._stage_rows[stage_key] = stage_row
|
||||
stage_list.add_widget(stage_row)
|
||||
section.add_widget(stage_list)
|
||||
return section
|
||||
|
||||
def _build_employee_section(self) -> VContainer:
|
||||
# Employee-section: отдельный блок ответственного сотрудника и его служебной информации.
|
||||
section = VContainer(spacing=12, content_fit=True)
|
||||
section.add_widget(
|
||||
Label(
|
||||
"Ответственный сотрудник",
|
||||
alignment="left",
|
||||
style="TICKET_DETAILS_SECTION_TITLE",
|
||||
)
|
||||
)
|
||||
|
||||
# Employee-row: горизонтальная строка карточки сотрудника с аватаром и текстом.
|
||||
employee_row = HContainer(spacing=16, content_fit=True)
|
||||
self._employee_avatar_label = Label("", style="TICKET_TASK_CARD_AVATAR_IMAGE")
|
||||
self._employee_avatar_label.set_fixed_size(64, 64)
|
||||
self._employee_name_label = Label(
|
||||
"",
|
||||
alignment="left",
|
||||
style="TICKET_DETAILS_EMPLOYEE_NAME",
|
||||
)
|
||||
self._employee_role_label = Label(
|
||||
"",
|
||||
alignment="left",
|
||||
style="TICKET_DETAILS_EMPLOYEE_ROLE",
|
||||
)
|
||||
|
||||
# Info-column: вертикальный столбец имени и должности рядом с аватаром.
|
||||
info_column = VContainer(spacing=2, content_fit=True)
|
||||
info_column.add_widget(self._employee_name_label)
|
||||
info_column.add_widget(self._employee_role_label)
|
||||
|
||||
employee_row.add_widget(self._employee_avatar_label)
|
||||
employee_row.add_widget_with_stretch(info_column, 1)
|
||||
section.add_widget(employee_row)
|
||||
return section
|
||||
|
||||
def _build_footer_section(self) -> HContainer:
|
||||
# Footer-row: нижняя линия действий с центрированными кнопками.
|
||||
footer = HContainer(spacing=12, content_fit=True)
|
||||
self._refuse_button = Button(
|
||||
"Отказать в обслуживании",
|
||||
style="TICKET_DETAILS_REFUSE_BUTTON",
|
||||
content_fit=True,
|
||||
)
|
||||
self._archive_button = Button(
|
||||
"В архив",
|
||||
style="TICKET_DETAILS_REFUSE_BUTTON",
|
||||
content_fit=True,
|
||||
)
|
||||
footer.add_stretch()
|
||||
footer.add_widget(self._refuse_button)
|
||||
footer.add_widget(self._archive_button)
|
||||
footer.add_stretch()
|
||||
return footer
|
||||
|
||||
def _connect_signals(self) -> None:
|
||||
self._application.task_updated.connect(self._on_task_updated)
|
||||
self._application.task_removed.connect(self._on_task_removed)
|
||||
if self._refuse_button is not None:
|
||||
self._refuse_button.clicked.connect(self._on_refuse_clicked)
|
||||
if self._archive_button is not None:
|
||||
self._archive_button.clicked.connect(self._on_archive_clicked)
|
||||
|
||||
def _reload_task(self) -> None:
|
||||
task = self._application.get_task(self._task_id)
|
||||
if task is None:
|
||||
self.reject()
|
||||
return
|
||||
self._task = task
|
||||
self._update_view(task)
|
||||
|
||||
def _update_view(self, task: TicketTaskSnapshot) -> None:
|
||||
for title, value in build_task_summary_rows(task):
|
||||
label = self._summary_value_labels.get(title)
|
||||
if label is not None:
|
||||
label.set_text(value)
|
||||
label.set_tooltip(value)
|
||||
|
||||
for stage_view in build_task_stage_rows(task):
|
||||
stage_row = self._stage_rows.get(stage_view.key)
|
||||
if stage_row is not None:
|
||||
stage_row.configure(
|
||||
text=stage_view.text,
|
||||
icon_path=stage_view.icon_path,
|
||||
emphasized=stage_view.emphasized,
|
||||
clickable=stage_view.clickable,
|
||||
)
|
||||
|
||||
self._update_employee_section(task)
|
||||
if self._refuse_button is not None:
|
||||
self._refuse_button.set_visible(can_refuse_task(task))
|
||||
if self._archive_button is not None:
|
||||
self._archive_button.set_visible(can_archive_task(task))
|
||||
|
||||
def _update_employee_section(self, task: TicketTaskSnapshot) -> None:
|
||||
employee_data = build_employee_view_data(task)
|
||||
if self._employee_name_label is not None:
|
||||
self._employee_name_label.set_text(employee_data["name"])
|
||||
if self._employee_role_label is not None:
|
||||
role_text = employee_data["position"].strip()
|
||||
self._employee_role_label.set_text(role_text)
|
||||
self._employee_role_label.set_visible(bool(role_text))
|
||||
if self._employee_avatar_label is None:
|
||||
return
|
||||
|
||||
avatar_pixmap = load_avatar_pixmap(
|
||||
employee_data["photo_path"],
|
||||
64,
|
||||
64,
|
||||
padding=2,
|
||||
)
|
||||
if avatar_pixmap is None:
|
||||
avatar_pixmap = build_placeholder_avatar_pixmap(64)
|
||||
self._employee_avatar_label.set_pixmap(avatar_pixmap)
|
||||
|
||||
def _on_task_updated(self, task: TicketTaskSnapshot) -> None:
|
||||
if task.task_id != self._task_id:
|
||||
return
|
||||
self._task = task
|
||||
self._update_view(task)
|
||||
|
||||
def _on_task_removed(self, task_id: int) -> None:
|
||||
if task_id == self._task_id:
|
||||
self.reject()
|
||||
|
||||
def _on_stage_clicked(self, stage_key: str) -> None:
|
||||
if self._task is None:
|
||||
return
|
||||
if stage_key == "specialist":
|
||||
self._actions.assign_specialist(self._task, self)
|
||||
return
|
||||
if stage_key == "diagnostic":
|
||||
self._actions.sign_diagnostic(self._task, self)
|
||||
return
|
||||
if stage_key == "repair":
|
||||
self._actions.sign_repair(self._task, self)
|
||||
return
|
||||
if stage_key == "acceptance":
|
||||
self._actions.sign_acceptance(self._task, self)
|
||||
|
||||
def _on_refuse_clicked(self) -> None:
|
||||
if self._task is None:
|
||||
return
|
||||
snapshot = self._actions.refuse_task(self._task, self)
|
||||
if snapshot is not None:
|
||||
self.accept()
|
||||
|
||||
def _on_archive_clicked(self) -> None:
|
||||
if self._task is None:
|
||||
return
|
||||
snapshot = self._actions.archive_task(self._task, self)
|
||||
if snapshot is not None:
|
||||
self.accept()
|
||||
Reference in New Issue
Block a user