# -*- coding: utf-8 -*- # hub/ticket/ui/pages/archive_page.py """Самостоятельная страница архива Ticket.""" from __future__ import annotations from PySide6.QtCore import Qt from PySide6.QtWidgets import QSizePolicy from gui.components import Label, TextInput from gui.containers import HContainer, SContainer, ScrollContainer, VContainer from application import TaskApplicationService from domain import ArchiveRecordSnapshot, TicketDocumentSnapshot from ui.cards import TaskCardView from .archive_view_helpers import ( build_preview_lines, cycle_token_from_record, record_to_task_snapshot, ) # ------------------------------------------------------------------------- # Страница архива # ------------------------------------------------------------------------- class ArchivePage(SContainer): """Страница архива с карточками в стиле доски задач и панелью просмотра.""" def __init__(self, application: TaskApplicationService, parent=None): super().__init__(width_percent=100, height_percent=100, parent=parent) self._application = application self._records: dict[str, ArchiveRecordSnapshot] = {} self._selected_record_key: str | None = None self._card_host: VContainer | None = None self._preview: TextInput | None = None self._empty_list_label: Label | None = None self._cards: dict[str, TaskCardView] = {} self._card_key_by_task_id: dict[int, str] = {} self._setup_ui() self._connect_signals() self._reload_records() # -- UI ---------------------------------------------------------------- def _setup_ui(self) -> None: board_row = HContainer( margin=[0, 0, 0, 0], height_percent=100, spacing=16, style="TICKET_SURFACE_HOST", parent=self, ) board_row.add_widget(self._build_list_column()) board_row.add_widget(self._build_preview_column()) def _build_list_column(self) -> SContainer: column = SContainer(spacing=12, parent=None) header = HContainer( height_percent=5.37, margin=0, spacing=16, content_fit=False, style="TICKET_BOARD_COLUMN_HEADER", ) header.add_widget( Label("Архив задач", margin=[12, 0, 0, 0], style="TICKET_BOARD_COLUMN_TITLE"), ) header.add_stretch() body = SContainer( margin=0, style="TICKET_REPORT_COLUMN_BODY", ) scroll = ScrollContainer( margin=0, spacing=0, orientation="v", vertical_scroll_bar_policy="always_off", horizontal_scroll_bar_policy="always_off", style="SCROLL_CONTAINER", parent=body, ) scroll.scroll_area.verticalScrollBar().setSingleStep(48) self._card_host = VContainer( spacing=12, content_fit=False, parent=scroll, ) self._card_host.set_size_policy( QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed, ) column.add_widget(header) column.add_widget(body) return column def _build_preview_column(self) -> SContainer: column = SContainer(spacing=12, width_percent=79.83, parent=None) header = HContainer( height_percent=5.37, margin=0, spacing=16, content_fit=False, style="TICKET_BOARD_COLUMN_HEADER", ) header.add_widget( Label("Просмотр", margin=[12, 0, 0, 0], style="TICKET_BOARD_COLUMN_TITLE"), ) header.add_stretch() body = SContainer( margin=0, style="TICKET_REPORT_PREVIEW_BODY", ) preview_inner = VContainer(margin=0, spacing=0, parent=body) self._preview = TextInput(style="TICKET_REPORT_PREVIEW_AREA", multiline=True) self._preview.set_read_only(True) self._preview._input.setVerticalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff, ) preview_inner.add_widget_with_stretch(self._preview, 1) column.add_widget(header) column.add_widget(body) return column # -- Signals ----------------------------------------------------------- def _connect_signals(self) -> None: self._application.task_updated.connect(self._reload_records) self._application.task_removed.connect(self._reload_records) self._application.state_loaded.connect(self._reload_records) # -- Data refresh ------------------------------------------------------ @staticmethod def _record_key(record: ArchiveRecordSnapshot) -> str: token = cycle_token_from_record(record) return f"{record.task_id}_{token}" if token else str(record.task_id) def _reload_records(self, *_args) -> None: records = self._application.list_archive_records() self._records = {self._record_key(r): r for r in records} self._rebuild_cards(records) if self._selected_record_key in self._records: self._select_record(self._selected_record_key, update_preview=True) return self._selected_record_key = None self._clear_preview() def _rebuild_cards(self, records: list[ArchiveRecordSnapshot]) -> None: if self._card_host is None: return for card in list(self._cards.values()): self._card_host.remove_widget(card) card.setParent(None) self._cards.clear() self._card_key_by_task_id.clear() if self._empty_list_label is not None: self._card_host.remove_widget(self._empty_list_label) self._empty_list_label.setParent(None) self._empty_list_label = None if not records: self._empty_list_label = Label( "Архивные задачи пока отсутствуют.", alignment="left", style="TICKET_REPORT_EMPTY_LABEL", ) self._card_host.insert_widget(0, self._empty_list_label) return for index, record in enumerate(records): key = self._record_key(record) synthetic_task = record_to_task_snapshot(record) card = TaskCardView(synthetic_task) card.card_clicked.connect(self._on_card_clicked) self._cards[key] = card self._card_key_by_task_id[record.task_id] = key self._card_host.insert_widget(index, card) # -- Selection --------------------------------------------------------- def _select_record(self, key: str | None, update_preview: bool) -> None: normalized = key if key in self._records else None self._selected_record_key = normalized if not update_preview: return record = self._records.get(normalized) if normalized is not None else None if record is None: self._clear_preview() return self._render_record(record) def _render_record(self, record: ArchiveRecordSnapshot) -> None: if self._preview is None: return documents = self._load_cycle_documents(record) lines = build_preview_lines(record, documents) self._preview.set_text("\n".join(lines)) def _load_cycle_documents( self, record: ArchiveRecordSnapshot, ) -> list[TicketDocumentSnapshot]: """Загрузить только документы текущего цикла задачи.""" cycle_token = cycle_token_from_record(record) all_docs = self._application.list_documents() return [ d for d in all_docs if d.task_id == record.task_id and cycle_token in d.document_id ] def _clear_preview(self) -> None: if self._preview is not None: self._preview.set_text("") def _on_card_clicked(self, card_id: object) -> None: if isinstance(card_id, int): key = self._card_key_by_task_id.get(card_id) if key is not None: self._select_record(key, update_preview=True) def showEvent(self, event) -> None: super().showEvent(event) self._reload_records()