128 lines
4.9 KiB
Python
128 lines
4.9 KiB
Python
"""
|
|
Dashboard tab: at-a-glance counts of what's come in.
|
|
|
|
Kept intentionally simple (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: active workload health (fulfilled tickets have moved to the Done tab)
|
|
top_row = QHBoxLayout()
|
|
self.total_card = StatCard("Active Orders")
|
|
self.cancelled_card = StatCard("Cancelled")
|
|
self.cancelled_card.setStyleSheet("QLabel { color: #b00020; }")
|
|
self.carryover_card = StatCard("Carryover (still open from an earlier day)")
|
|
self.carryover_card.setStyleSheet("QLabel { color: #b06a00; }")
|
|
self.past_cutoff_card = StatCard("Arrived Past Cutoff Today")
|
|
self.past_cutoff_card.setStyleSheet("QLabel { color: #b06a00; }")
|
|
for card in (self.total_card, self.cancelled_card, self.carryover_card, self.past_cutoff_card):
|
|
top_row.addWidget(card)
|
|
outer.addLayout(top_row)
|
|
|
|
# Second row: today's completions + tracking pull progress
|
|
outer.addWidget(self._section_label("Today's Progress"))
|
|
progress_row = QHBoxLayout()
|
|
self.fulfilled_today_card = StatCard("Fulfilled Today (moved to Done)")
|
|
self.fulfilled_today_card.setStyleSheet("QLabel { color: #1a7a1a; }")
|
|
self.tracking_received_card = StatCard("Active Tickets With Tracking Pulled")
|
|
progress_row.addWidget(self.fulfilled_today_card)
|
|
progress_row.addWidget(self.tracking_received_card)
|
|
outer.addLayout(progress_row)
|
|
|
|
# Third row: per-company totals (grid so adding a 3rd company later just works)
|
|
outer.addWidget(self._section_label("Active Orders By Company"))
|
|
self.company_grid = QGridLayout()
|
|
self.company_cards: dict[str, StatCard] = {}
|
|
outer.addLayout(self.company_grid)
|
|
|
|
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.cancelled_card.set_value(stats.get("cancelled_count", 0))
|
|
self.carryover_card.set_value(stats.get("carryover_count", 0))
|
|
self.past_cutoff_card.set_value(stats.get("past_cutoff_today_count", 0))
|
|
self.fulfilled_today_card.set_value(stats.get("fulfilled_today_count", 0))
|
|
self.tracking_received_card.set_value(stats.get("tracking_received_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)
|