feat: simplify engagement tab - single sorted list with activity bars
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Replace 3-section active/at-risk/dormant design with one clear table showing all members sorted by activity level. Green/yellow/gray bars, human-readable last login dates, inactive users dimmed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0b5ffdcc76
commit
cfcee7af73
@ -633,73 +633,47 @@ def _tab_engagement(db, start_date, days):
|
||||
int(dur30) / 60 * 2 + conv30 * 10 + search30 * 2)
|
||||
score = _log_engagement_score(raw)
|
||||
|
||||
# WoW change
|
||||
wow = None
|
||||
if pv_prev > 0:
|
||||
wow = round((pv_cur - pv_prev) / pv_prev * 100)
|
||||
elif pv_cur > 0:
|
||||
wow = 100
|
||||
|
||||
# Engagement status (improved logic)
|
||||
# Last login label
|
||||
days_since_login = None
|
||||
if user.last_login:
|
||||
days_since_login = (date.today() - user.last_login.date()).days
|
||||
|
||||
if days_since_login is None:
|
||||
status = 'dormant'
|
||||
elif days_since_login <= 7:
|
||||
status = 'active' if score >= 10 else 'at_risk'
|
||||
elif days_since_login <= 30:
|
||||
status = 'at_risk'
|
||||
else:
|
||||
status = 'dormant'
|
||||
|
||||
# Sparkline from batch data
|
||||
user_spark = sparkline_map.get(uid, {})
|
||||
sparkline = [user_spark.get(d, 0) for d in spark_days]
|
||||
|
||||
# Last activity label
|
||||
if days_since_login is not None:
|
||||
if days_since_login == 0:
|
||||
last_activity = 'Dziś'
|
||||
last_login = 'Dziś'
|
||||
elif days_since_login == 1:
|
||||
last_activity = 'Wczoraj'
|
||||
last_login = 'Wczoraj'
|
||||
elif days_since_login <= 7:
|
||||
last_activity = f'{days_since_login} dni temu'
|
||||
last_login = f'{days_since_login} dni temu'
|
||||
elif days_since_login <= 30:
|
||||
weeks = days_since_login // 7
|
||||
last_activity = f'{weeks} tyg. temu'
|
||||
last_login = f'{weeks} tyg. temu'
|
||||
else:
|
||||
last_activity = f'{days_since_login} dni temu'
|
||||
months = days_since_login // 30
|
||||
if months > 0:
|
||||
last_login = f'{months} mies. temu'
|
||||
else:
|
||||
last_login = f'{days_since_login} dni temu'
|
||||
else:
|
||||
last_activity = 'Nigdy'
|
||||
last_login = 'Nigdy'
|
||||
|
||||
if sessions_cur > 0 or score > 0:
|
||||
engagement_list.append({
|
||||
'user': user,
|
||||
'score': score,
|
||||
'sessions': sessions_cur,
|
||||
'page_views': pv_cur,
|
||||
'status': status,
|
||||
'last_activity': last_activity,
|
||||
'days_since': days_since_login,
|
||||
})
|
||||
engagement_list.append({
|
||||
'user': user,
|
||||
'score': score,
|
||||
'sessions': sessions_cur,
|
||||
'page_views': pv_cur,
|
||||
'last_login': last_login,
|
||||
'days_since': days_since_login,
|
||||
})
|
||||
|
||||
engagement_list.sort(key=lambda x: x['score'], reverse=True)
|
||||
|
||||
# Group by status
|
||||
active_list = [e for e in engagement_list if e['status'] == 'active']
|
||||
at_risk_list = [e for e in engagement_list if e['status'] == 'at_risk']
|
||||
dormant_list = [e for e in engagement_list if e['status'] == 'dormant']
|
||||
|
||||
return {
|
||||
'active_7d': active_7d,
|
||||
'at_risk': at_risk,
|
||||
'dormant': dormant,
|
||||
'new_this_month': new_this_month,
|
||||
'active_list': active_list[:25],
|
||||
'at_risk_list': at_risk_list[:25],
|
||||
'dormant_list': dormant_list[:25],
|
||||
'engagement_list': engagement_list,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -66,10 +66,7 @@
|
||||
|
||||
/* Engagement bars */
|
||||
.engagement-bar-wrap { height: 8px; background: var(--background); border-radius: 4px; overflow: hidden; }
|
||||
.engagement-bar { height: 100%; border-radius: 4px; min-width: 4px; transition: width 0.3s ease; }
|
||||
.engagement-bar-active { background: #16a34a; }
|
||||
.engagement-bar-at_risk { background: #f59e0b; }
|
||||
.engagement-bar-dormant { background: #ef4444; }
|
||||
.engagement-bar { height: 100%; border-radius: 4px; min-width: 2px; transition: width 0.3s ease; }
|
||||
|
||||
/* Horizontal bars */
|
||||
.bar-container { height: 20px; background: var(--background); border-radius: var(--radius-sm); overflow: hidden; min-width: 100px; }
|
||||
@ -448,99 +445,70 @@
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card success">
|
||||
<div class="stat-value">{{ data.active_7d }}</div>
|
||||
<div class="stat-label">Aktywni ({% if period == 'day' %}dziś{% elif period == 'week' %}7 dni{% else %}30 dni{% endif %})</div>
|
||||
</div>
|
||||
<div class="stat-card warning">
|
||||
<div class="stat-value">{{ data.at_risk }}</div>
|
||||
<div class="stat-label">Zagrożeni (8-30d)</div>
|
||||
</div>
|
||||
<div class="stat-card error">
|
||||
<div class="stat-value">{{ data.dormant }}</div>
|
||||
<div class="stat-label">Uśpieni (30d+)</div>
|
||||
<div class="stat-label">Aktywni (ostatnie 7 dni)</div>
|
||||
</div>
|
||||
<div class="stat-card info">
|
||||
<div class="stat-value">{{ data.new_this_month }}</div>
|
||||
<div class="stat-label">Nowi (ten miesiąc)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% macro engagement_table(users, empty_msg) %}
|
||||
{% if users %}
|
||||
<div class="table-scroll">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Użytkownik</th>
|
||||
<th>Zaangażowanie</th>
|
||||
<th>Sesje</th>
|
||||
<th>Odsłony</th>
|
||||
<th>Ostatnia aktywność</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for e in users %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="user-cell">
|
||||
<div class="user-avatar">{{ e.user.name[0] if e.user.name else '?' }}</div>
|
||||
<a href="{{ url_for('admin.user_insights_profile', user_id=e.user.id, ref_tab=tab, ref_period=period) }}">{{ e.user.name or e.user.email }}</a>
|
||||
</div>
|
||||
</td>
|
||||
<td style="min-width: 120px;">
|
||||
<div class="engagement-bar-wrap">
|
||||
<div class="engagement-bar engagement-bar-{{ e.status }}" style="width: {{ [e.score, 100]|min }}%;"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ e.sessions }}</td>
|
||||
<td>{{ e.page_views }}</td>
|
||||
<td style="white-space: nowrap; color: var(--text-secondary);">{{ e.last_activity }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p style="text-align: center; padding: var(--spacing-md); color: var(--text-muted);">{{ empty_msg }}</p>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% if data.active_list %}
|
||||
<div class="section-card">
|
||||
<h2 style="color: #166534;">
|
||||
<span style="display: inline-block; width: 10px; height: 10px; background: #16a34a; border-radius: 50%; margin-right: 8px;"></span>
|
||||
Aktywni ({{ data.active_list|length }})
|
||||
</h2>
|
||||
{{ engagement_table(data.active_list, '') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if data.at_risk_list %}
|
||||
<div class="section-card">
|
||||
<h2 style="color: #92400e;">
|
||||
<span style="display: inline-block; width: 10px; height: 10px; background: #f59e0b; border-radius: 50%; margin-right: 8px;"></span>
|
||||
Zagrożeni ({{ data.at_risk_list|length }})
|
||||
</h2>
|
||||
{{ engagement_table(data.at_risk_list, '') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if data.dormant_list %}
|
||||
<div class="section-card">
|
||||
<h2 style="color: #991b1b;">
|
||||
<span style="display: inline-block; width: 10px; height: 10px; background: #ef4444; border-radius: 50%; margin-right: 8px;"></span>
|
||||
Uśpieni ({{ data.dormant_list|length }})
|
||||
</h2>
|
||||
{{ engagement_table(data.dormant_list, '') }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not data.active_list and not data.at_risk_list and not data.dormant_list %}
|
||||
<div class="section-card">
|
||||
<div style="text-align: center; padding: var(--spacing-xl); color: var(--text-muted);">
|
||||
<p>Brak danych o zaangażowaniu w wybranym okresie.</p>
|
||||
<div class="stat-card warning">
|
||||
<div class="stat-value">{{ data.at_risk }}</div>
|
||||
<div class="stat-label">Nie logowali się 8-30 dni</div>
|
||||
</div>
|
||||
<div class="stat-card error">
|
||||
<div class="stat-value">{{ data.dormant }}</div>
|
||||
<div class="stat-label">Nie logowali się 30+ dni</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="section-card">
|
||||
<h2>Aktywność członków</h2>
|
||||
|
||||
{% if data.engagement_list %}
|
||||
<div class="table-scroll">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Członek</th>
|
||||
<th style="min-width: 140px;">Aktywność</th>
|
||||
<th>Sesje</th>
|
||||
<th>Odsłony</th>
|
||||
<th>Ostatnie logowanie</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for e in data.engagement_list %}
|
||||
<tr{% if e.score == 0 %} style="opacity: 0.5;"{% endif %}>
|
||||
<td style="font-weight: 600; color: var(--text-muted); width: 40px;">{{ loop.index }}</td>
|
||||
<td>
|
||||
<div class="user-cell">
|
||||
<div class="user-avatar">{{ e.user.name[0] if e.user.name else '?' }}</div>
|
||||
<a href="{{ url_for('admin.user_insights_profile', user_id=e.user.id, ref_tab=tab, ref_period=period) }}">{{ e.user.name or e.user.email }}</a>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="engagement-bar-wrap">
|
||||
{% set bar_color = '#16a34a' if e.score >= 40 else '#f59e0b' if e.score >= 10 else '#d1d5db' %}
|
||||
<div class="engagement-bar" style="width: {{ [e.score, 100]|min }}%; background: {{ bar_color }};"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ e.sessions }}</td>
|
||||
<td>{{ e.page_views }}</td>
|
||||
<td style="white-space: nowrap; color: {% if e.last_login == 'Nigdy' %}var(--danger){% elif e.days_since is not none and e.days_since > 30 %}var(--text-muted){% else %}var(--text-secondary){% endif %};">
|
||||
{{ e.last_login }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div style="text-align: center; padding: var(--spacing-xl); color: var(--text-muted);">
|
||||
<p>Brak danych o aktywności członków.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TAB: PAGE MAP -->
|
||||
|
||||
Loading…
Reference in New Issue
Block a user