feat(admin): DAU chart — events markers, week separators, weekend coloring
Some checks are pending
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

- Events shown as red 📅(N) above bars with attendee count
- Monday bars have dashed left border as week separator
- Weekend bars (Sat/Sun) in gray to distinguish from weekdays
- Hover tooltip shows event title and attendee count

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-18 08:56:43 +01:00
parent 1a160add8c
commit 96232200f9
2 changed files with 52 additions and 2 deletions

View File

@ -169,6 +169,22 @@ def admin_user_activity():
.all()
)
# Events in this period (for chart markers)
from database import NordaEvent, EventAttendee
events_in_range = db.query(NordaEvent).filter(
NordaEvent.event_date >= start_date,
NordaEvent.event_date <= start_date + timedelta(days=30),
).all()
event_map = {} # date → {'title': ..., 'attendees': ...}
for ev in events_in_range:
att_count = db.query(func.count(EventAttendee.id)).filter(
EventAttendee.event_id == ev.id
).scalar() or 0
event_map[ev.event_date] = {
'title': ev.title[:30] + ('...' if len(ev.title) > 30 else ''),
'attendees': att_count,
}
# Fill in missing days with 0
dau_map = {row.day: row.users for row in dau_q}
daily_active = []
@ -178,10 +194,16 @@ def admin_user_activity():
count = dau_map.get(d, 0)
if count > max_dau:
max_dau = count
is_weekend = d.weekday() >= 5 # 5=Saturday, 6=Sunday
is_monday = d.weekday() == 0
ev = event_map.get(d)
daily_active.append({
'date': d,
'label': d.strftime('%d.%m'),
'count': count,
'is_weekend': is_weekend,
'is_monday': is_monday,
'event': ev,
})
# Add percentage for CSS bar heights

View File

@ -152,6 +152,20 @@
align-items: center;
justify-content: flex-end;
height: 100%;
position: relative;
}
.bar-col.weekend .bar {
background: linear-gradient(to top, #94a3b8, #b0bec5);
}
.bar-col.weekend .bar-label {
color: #94a3b8;
}
.bar-col.monday-start {
border-left: 1px dashed #cbd5e1;
margin-left: 2px;
padding-left: 2px;
}
.bar-value {
@ -159,7 +173,18 @@
font-weight: 600;
color: var(--text-primary);
line-height: 1;
margin-bottom: 4px;
margin-bottom: 2px;
}
.bar-event {
font-size: 8px;
color: #dc2626;
font-weight: 600;
line-height: 1;
margin-bottom: 2px;
white-space: nowrap;
max-width: 40px;
overflow: visible;
}
.bar-wrapper {
@ -254,8 +279,11 @@
<div class="chart-container">
<div class="bar-chart">
{% for day in daily_active %}
<div class="bar-col" title="{{ day.label }}: {{ day.count }} użytkowników">
<div class="bar-col{{ ' weekend' if day.is_weekend }}{{ ' monday-start' if day.is_monday }}" title="{{ day.label }}: {{ day.count }} użytkowników{% if day.event %} | {{ day.event.title }} ({{ day.event.attendees }} zapisanych){% endif %}">
<span class="bar-value">{% if day.count > 0 %}{{ day.count }}{% endif %}</span>
{% if day.event %}
<span class="bar-event">📅({{ day.event.attendees }})</span>
{% endif %}
<div class="bar-wrapper">
<div class="bar" style="height: {{ [day.pct, 5]|max if day.count > 0 else 0 }}%;"></div>
</div>