"""Vista Overview: tarjeta con tags clave en vivo (Sprint 6).""" from __future__ import annotations from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QFrame, QGridLayout, QHBoxLayout, QLabel, QScrollArea, QVBoxLayout, QWidget, ) from vmssailor.studio.theme import ( C_CYAN, C_FOAM, C_FOG, C_MIDNIGHT, C_OK, C_STEEL, C_WARN, mono_font, ui_font, ) class _TagTile(QFrame): """Tarjeta visual con valor en vivo de un tag.""" def __init__(self, tag_id: str, description: str, unit: str) -> None: super().__init__() self._tag_id = tag_id self._unit = unit self.setObjectName("tagTile") self.setStyleSheet( f"#tagTile {{ background: {C_MIDNIGHT}; " f"border: 1px solid {C_STEEL}; border-radius: 8px; }}" ) layout = QVBoxLayout(self) layout.setContentsMargins(14, 12, 14, 12) layout.setSpacing(2) self._desc_label = QLabel(description or tag_id) self._desc_label.setFont(ui_font(9)) self._desc_label.setStyleSheet(f"color: {C_FOG}; letter-spacing: 1.5px;") layout.addWidget(self._desc_label) self._id_label = QLabel(tag_id) self._id_label.setFont(mono_font(9)) self._id_label.setStyleSheet(f"color: {C_CYAN};") layout.addWidget(self._id_label) self._value_label = QLabel("—") self._value_label.setFont(mono_font(20)) self._value_label.setStyleSheet(f"color: {C_FOAM}; font-weight: 600;") layout.addWidget(self._value_label) self._meta_label = QLabel("sin datos") self._meta_label.setFont(mono_font(8)) self._meta_label.setStyleSheet(f"color: {C_FOG};") layout.addWidget(self._meta_label) def update_value(self, value, quality: str, timestamp: str) -> None: value_str = f"{value:.2f}" if isinstance(value, float) else str(value) self._value_label.setText(f"{value_str} {self._unit}") ts_short = timestamp[-12:-3] if timestamp else "—" self._meta_label.setText(f"q={quality} · {ts_short}") color = C_OK if quality == "good" else C_WARN self._meta_label.setStyleSheet(f"color: {color};") class OverviewView(QWidget): """Grid de tiles con tags clave.""" def __init__(self, parent: QWidget | None = None) -> None: super().__init__(parent) self._tiles: dict[str, _TagTile] = {} outer = QVBoxLayout(self) outer.setContentsMargins(20, 20, 20, 20) outer.setSpacing(12) title_row = QHBoxLayout() self._title = QLabel("Overview") self._title.setFont(ui_font(20)) self._title.setStyleSheet(f"color: {C_FOAM}; font-weight: 600;") title_row.addWidget(self._title) title_row.addStretch(1) self._tag_count_label = QLabel("0 tags") self._tag_count_label.setFont(mono_font(10)) self._tag_count_label.setStyleSheet(f"color: {C_FOG};") title_row.addWidget(self._tag_count_label) outer.addLayout(title_row) scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setFrameShape(QFrame.Shape.NoFrame) outer.addWidget(scroll, 1) grid_wrapper = QWidget() self._grid = QGridLayout(grid_wrapper) self._grid.setSpacing(10) scroll.setWidget(grid_wrapper) self._empty_label = QLabel("Conecta al Runtime para ver tags en vivo (panel Conexión).") self._empty_label.setFont(ui_font(11)) self._empty_label.setStyleSheet(f"color: {C_FOG};") self._empty_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self._grid.addWidget(self._empty_label, 0, 0) def set_project(self, project: dict) -> None: # Pre-popula tiles a partir de stats del proyecto (sin valores aún) self._title.setText(f"Overview · {project.get('name', '')}") def handle_event(self, event: dict) -> None: if event.get("type") != "tag_update": return tag_id = event.get("tag_id") if not tag_id: return if tag_id not in self._tiles: # Crear nueva tile dinámicamente if self._empty_label is not None: self._empty_label.setVisible(False) tile = _TagTile(tag_id, description="", unit="") row = len(self._tiles) // 4 col = len(self._tiles) % 4 self._grid.addWidget(tile, row, col) self._tiles[tag_id] = tile self._tag_count_label.setText(f"{len(self._tiles)} tags") self._tiles[tag_id].update_value( event.get("value"), event.get("quality", "—"), event.get("timestamp", ""), )