""" Dashboard tab: at-a-glance counts of what's come in. Kept intentionally simple for now (stat cards, no charts) per the "just a dashboard for now" scope. The stats themselves come from app.workers.get_dashboard_stats(), so adding a new stat later is a matter of adding a key there and a card here - this widget doesn't know anything about how orders are fetched or stored. """ from __future__ import annotations from PyQt6.QtCore import Qt from PyQt6.QtWidgets import ( QWidget, QGridLayout, QVBoxLayout, QHBoxLayout, QLabel, QFrame, QSizePolicy, ) class StatCard(QFrame): def __init__(self, title: str, parent=None): super().__init__(parent) self.setFrameShape(QFrame.Shape.StyledPanel) self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) layout = QVBoxLayout(self) self.value_label = QLabel("0") value_font = self.value_label.font() value_font.setPointSize(28) value_font.setBold(True) self.value_label.setFont(value_font) self.value_label.setAlignment(Qt.AlignmentFlag.AlignCenter) title_label = QLabel(title) title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) title_label.setWordWrap(True) layout.addWidget(self.value_label) layout.addWidget(title_label) def set_value(self, value) -> None: self.value_label.setText(str(value)) class DashboardWidget(QWidget): def __init__(self, parent=None): super().__init__(parent) outer = QVBoxLayout(self) heading = QLabel("Today's Orders at a Glance") heading_font = heading.font() heading_font.setPointSize(16) heading_font.setBold(True) heading.setFont(heading_font) outer.addWidget(heading) # Top row: overall + per-source totals top_row = QHBoxLayout() self.total_card = StatCard("Total Orders") self.jira_card = StatCard("From JIRA") self.shipstation_card = StatCard("From ShipStation") for card in (self.total_card, self.jira_card, self.shipstation_card): top_row.addWidget(card) outer.addLayout(top_row) # Second row: per-company totals (grid so adding a 3rd company later just works) outer.addWidget(self._section_label("By Company")) self.company_grid = QGridLayout() self.company_cards: dict[str, StatCard] = {} outer.addLayout(self.company_grid) # Third row: cross-source matching, since JIRA and ShipStation are pulled independently outer.addWidget(self._section_label("JIRA <-> ShipStation Matching (by ticket #)")) match_row = QHBoxLayout() self.matched_card = StatCard("Matched in Both") self.jira_only_card = StatCard("JIRA Only (not yet in ShipStation)") self.shipstation_only_card = StatCard("ShipStation Only (no matching ticket)") for card in (self.matched_card, self.jira_only_card, self.shipstation_only_card): match_row.addWidget(card) outer.addLayout(match_row) outer.addStretch() @staticmethod def _section_label(text: str) -> QLabel: label = QLabel(text) font = label.font() font.setBold(True) label.setFont(font) return label def update_stats(self, stats: dict) -> None: self.total_card.set_value(stats.get("total", 0)) self.jira_card.set_value(stats.get("by_source", {}).get("jira", 0)) self.shipstation_card.set_value(stats.get("by_source", {}).get("shipstation", 0)) self.matched_card.set_value(stats.get("matched_count", 0)) self.jira_only_card.set_value(stats.get("jira_only_count", 0)) self.shipstation_only_card.set_value(stats.get("shipstation_only_count", 0)) by_company = stats.get("by_company", {}) # Rebuild company cards if the set of companies changed (e.g. a 3rd company added) if set(by_company.keys()) != set(self.company_cards.keys()): self._rebuild_company_cards(by_company.keys()) for company, count in by_company.items(): self.company_cards[company].set_value(count) def _rebuild_company_cards(self, companies) -> None: while self.company_grid.count(): item = self.company_grid.takeAt(0) widget = item.widget() if widget: widget.deleteLater() self.company_cards.clear() for i, company in enumerate(sorted(companies)): card = StatCard(company) self.company_cards[company] = card self.company_grid.addWidget(card, i // 3, i % 3)