Files
Order-Manager/app/ui/widgets/dashboard.py
T
2026-08-26 09:15:35 -05:00

125 lines
4.6 KiB
Python

"""
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)