feat(dashboard): add pending as separate stat card and progress bar segment

This commit is contained in:
2026-07-28 07:46:42 +08:00
parent 64adbba302
commit 12de30d91f
3 changed files with 18 additions and 4 deletions
+6 -4
View File
@@ -593,7 +593,8 @@ def dashboard(request: Request, user: User = Depends(get_current_user_web), db:
all_cards.extend(cards) all_cards.extend(cards)
total = len(all_cards) total = len(all_cards)
sent = sum(1 for c in all_cards if c.status in ("pending", "sent")) pending = sum(1 for c in all_cards if c.status == "pending")
sent = sum(1 for c in all_cards if c.status == "sent")
delivered = sum(1 for c in all_cards if c.status == "delivered") delivered = sum(1 for c in all_cards if c.status == "delivered")
received = sum(1 for c in all_cards if c.status == "received") received = sum(1 for c in all_cards if c.status == "received")
countries = len({c.country_to for c in all_cards if c.country_to}) countries = len({c.country_to for c in all_cards if c.country_to})
@@ -604,11 +605,12 @@ def dashboard(request: Request, user: User = Depends(get_current_user_web), db:
# status distribution for progress bar # status distribution for progress bar
if total > 0: if total > 0:
pending_pct = round(pending / total * 100)
sent_pct = round(sent / total * 100) sent_pct = round(sent / total * 100)
delivered_pct = round(delivered / total * 100) delivered_pct = round(delivered / total * 100)
received_pct = round(received / total * 100) received_pct = round(received / total * 100)
else: else:
sent_pct = delivered_pct = received_pct = 0 pending_pct = sent_pct = delivered_pct = received_pct = 0
# country stats split by sent/received # country stats split by sent/received
sent_country_counts: dict[str, int] = {} sent_country_counts: dict[str, int] = {}
@@ -626,9 +628,9 @@ def dashboard(request: Request, user: User = Depends(get_current_user_web), db:
from datetime import datetime, timezone as _tz from datetime import datetime, timezone as _tz
return _render(request, "dashboard.html", {"user": user, "profiles": profiles, return _render(request, "dashboard.html", {"user": user, "profiles": profiles,
"total": total, "sent": sent, "delivered": delivered, "received": received, "total": total, "pending": pending, "sent": sent, "delivered": delivered, "received": received,
"countries": countries, "recent": recent, "countries": countries, "recent": recent,
"sent_pct": sent_pct, "delivered_pct": delivered_pct, "received_pct": received_pct, "pending_pct": pending_pct, "sent_pct": sent_pct, "delivered_pct": delivered_pct, "received_pct": received_pct,
"sent_country_stats": sent_country_stats, "received_country_stats": received_country_stats, "sent_country_stats": sent_country_stats, "received_country_stats": received_country_stats,
"now": datetime.now(_tz.utc) + timedelta(hours=8), "now": datetime.now(_tz.utc) + timedelta(hours=8),
}) })
+3
View File
@@ -186,17 +186,20 @@ code { background: var(--bg); padding: .15rem .4rem; border-radius: 4px; font-si
.dash-stat-body { display: flex; flex-direction: column; } .dash-stat-body { display: flex; flex-direction: column; }
.dash-stat-value { font-size: 1.15rem; font-weight: 700; line-height: 1.2; } .dash-stat-value { font-size: 1.15rem; font-weight: 700; line-height: 1.2; }
.dash-stat-label { font-size: .68rem; color: var(--text-muted); } .dash-stat-label { font-size: .68rem; color: var(--text-muted); }
.dash-pending { color: var(--primary); }
.dash-sent { color: var(--sent); } .dash-sent { color: var(--sent); }
.dash-delivered { color: var(--delivered); } .dash-delivered { color: var(--delivered); }
.dash-received { color: var(--received); } .dash-received { color: var(--received); }
.dash-progress { margin-bottom: 2rem; } .dash-progress { margin-bottom: 2rem; }
.progress-bar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; background: var(--border); } .progress-bar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; background: var(--border); }
.progress-seg { height: 100%; } .progress-seg { height: 100%; }
.progress-pending { background: var(--primary); }
.progress-sent { background: var(--sent); } .progress-sent { background: var(--sent); }
.progress-delivered { background: var(--delivered); } .progress-delivered { background: var(--delivered); }
.progress-received { background: var(--received); } .progress-received { background: var(--received); }
.progress-legend { display: flex; gap: 1.25rem; margin-top: .5rem; font-size: .8rem; color: var(--text-muted); } .progress-legend { display: flex; gap: 1.25rem; margin-top: .5rem; font-size: .8rem; color: var(--text-muted); }
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: .25rem; vertical-align: middle; } .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: .25rem; vertical-align: middle; }
.dot-pending { background: var(--primary); }
.dot-sent { background: var(--sent); } .dot-sent { background: var(--sent); }
.dot-delivered { background: var(--delivered); } .dot-delivered { background: var(--delivered); }
.dot-received { background: var(--received); } .dot-received { background: var(--received); }
+9
View File
@@ -14,6 +14,13 @@
<span class="dash-stat-label">{{ 'dash.total_cards'|t(user.language) }}</span> <span class="dash-stat-label">{{ 'dash.total_cards'|t(user.language) }}</span>
</div> </div>
</div> </div>
<div class="dash-stat-card">
<div class="dash-stat-icon">📋</div>
<div class="dash-stat-body">
<span class="dash-stat-value dash-pending">{{ pending }}</span>
<span class="dash-stat-label">{{ 'status.pending'|t(user.language) }}</span>
</div>
</div>
<div class="dash-stat-card"> <div class="dash-stat-card">
<div class="dash-stat-icon">📤</div> <div class="dash-stat-icon">📤</div>
<div class="dash-stat-body"> <div class="dash-stat-body">
@@ -47,11 +54,13 @@
{% if total > 0 %} {% if total > 0 %}
<div class="dash-progress"> <div class="dash-progress">
<div class="progress-bar"> <div class="progress-bar">
<div class="progress-seg progress-pending" style="width:{{ pending_pct }}%"></div>
<div class="progress-seg progress-sent" style="width:{{ sent_pct }}%"></div> <div class="progress-seg progress-sent" style="width:{{ sent_pct }}%"></div>
<div class="progress-seg progress-delivered" style="width:{{ delivered_pct }}%"></div> <div class="progress-seg progress-delivered" style="width:{{ delivered_pct }}%"></div>
<div class="progress-seg progress-received" style="width:{{ received_pct }}%"></div> <div class="progress-seg progress-received" style="width:{{ received_pct }}%"></div>
</div> </div>
<div class="progress-legend"> <div class="progress-legend">
<span><i class="dot dot-pending"></i>{{ 'status.pending'|t(user.language) }} {{ pending_pct }}%</span>
<span><i class="dot dot-sent"></i>{{ 'dash.sent'|t(user.language) }} {{ sent_pct }}%</span> <span><i class="dot dot-sent"></i>{{ 'dash.sent'|t(user.language) }} {{ sent_pct }}%</span>
<span><i class="dot dot-delivered"></i>{{ 'dash.delivered'|t(user.language) }} {{ delivered_pct }}%</span> <span><i class="dot dot-delivered"></i>{{ 'dash.delivered'|t(user.language) }} {{ delivered_pct }}%</span>
<span><i class="dot dot-received"></i>{{ 'dash.received'|t(user.language) }} {{ received_pct }}%</span> <span><i class="dot dot-received"></i>{{ 'dash.received'|t(user.language) }} {{ received_pct }}%</span>