feat: remove Paths tab, move session length distribution to Overview
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
Paths tab (entry/exit pages, transitions, drop-off) provided little actionable value for association admin. Session length distribution moved to Overview as compact card layout. Menu: 6 tabs -> 5. Old /paths URL redirects to /overview. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7a83719647
commit
289b8d6f31
@ -83,7 +83,9 @@ def user_insights():
|
||||
elif tab == 'pages':
|
||||
data = _tab_pages(db, start_date, days)
|
||||
elif tab == 'paths':
|
||||
data = _tab_paths(db, start_date, days)
|
||||
# Redirect old paths tab to overview
|
||||
from flask import redirect
|
||||
return redirect(url_for('admin.user_insights', tab='overview', period=period))
|
||||
elif tab == 'overview':
|
||||
data = _tab_overview(db, start_date, days)
|
||||
elif tab == 'chat':
|
||||
@ -1497,6 +1499,30 @@ def _tab_overview(db, start_date, days):
|
||||
})
|
||||
companies_attention.sort(key=lambda x: x['issue_count'], reverse=True)
|
||||
|
||||
# Session length distribution (exclude bots)
|
||||
session_length_sql = text("""
|
||||
SELECT
|
||||
CASE
|
||||
WHEN pv_count = 1 THEN '1 strona'
|
||||
WHEN pv_count = 2 THEN '2 strony'
|
||||
WHEN pv_count BETWEEN 3 AND 5 THEN '3-5 stron'
|
||||
WHEN pv_count BETWEEN 6 AND 10 THEN '6-10 stron'
|
||||
ELSE '10+ stron'
|
||||
END as bucket,
|
||||
COUNT(*) as cnt
|
||||
FROM (
|
||||
SELECT pv.session_id, COUNT(*) as pv_count
|
||||
FROM page_views pv
|
||||
JOIN user_sessions us ON pv.session_id = us.id
|
||||
WHERE pv.viewed_at >= :start_dt AND pv.session_id IS NOT NULL AND us.is_bot = false
|
||||
GROUP BY pv.session_id
|
||||
) session_counts
|
||||
GROUP BY bucket
|
||||
ORDER BY MIN(pv_count)
|
||||
""")
|
||||
session_lengths = db.execute(session_length_sql, {'start_dt': start_30d}).fetchall()
|
||||
max_sl = max((r.cnt for r in session_lengths), default=1) or 1
|
||||
|
||||
return {
|
||||
'filter_type': filter_type,
|
||||
'kpi': kpi,
|
||||
@ -1520,6 +1546,7 @@ def _tab_overview(db, start_date, days):
|
||||
'max_company_views': max_company,
|
||||
'companies_attention': companies_attention[:20],
|
||||
'companies_attention_total': len(companies_attention),
|
||||
'session_lengths': [{'bucket': r.bucket, 'count': r.cnt, 'bar_pct': int(r.cnt / max_sl * 100)} for r in session_lengths],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -192,7 +192,6 @@
|
||||
<a href="{{ url_for('admin.user_insights', tab='overview', period=period) }}" class="tab-link {% if tab == 'overview' %}active{% endif %}">Przegląd</a>
|
||||
<a href="{{ url_for('admin.user_insights', tab='engagement', period=period) }}" class="tab-link {% if tab == 'engagement' %}active{% endif %}">Zaangażowanie</a>
|
||||
<a href="{{ url_for('admin.user_insights', tab='pages', period=period) }}" class="tab-link {% if tab == 'pages' %}active{% endif %}">Strony</a>
|
||||
<a href="{{ url_for('admin.user_insights', tab='paths', period=period) }}" class="tab-link {% if tab == 'paths' %}active{% endif %}">Ścieżki</a>
|
||||
<a href="{{ url_for('admin.user_insights', tab='problems', period=period) }}" class="tab-link {% if tab == 'problems' %}active{% endif %}">Problemy</a>
|
||||
<a href="{{ url_for('admin.user_insights', tab='chat', period=period) }}" class="tab-link {% if tab == 'chat' %}active{% endif %}">Chat & Konwersje</a>
|
||||
</div>
|
||||
@ -710,117 +709,6 @@
|
||||
</div>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TAB: PATHS -->
|
||||
<!-- ============================================================ -->
|
||||
{% elif tab == 'paths' %}
|
||||
|
||||
<div class="two-columns">
|
||||
<!-- Entry pages -->
|
||||
<div class="section-card">
|
||||
<h2>Strony wejściowe (top 10)</h2>
|
||||
{% for p in data.entry_pages %}
|
||||
<div class="bar-chart-row">
|
||||
<div class="bar-chart-label" style="width: 200px; min-width: 200px; font-size: var(--font-size-xs); word-break: break-all;" title="{{ p.path }}">{{ p.label }}</div>
|
||||
<div class="bar-chart-bar">
|
||||
<div class="bar-chart-fill" style="width: {{ p.bar_pct }}%; background: #22c55e;">
|
||||
<span>{{ p.count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Exit pages -->
|
||||
<div class="section-card">
|
||||
<h2>Strony wyjściowe (top 10)</h2>
|
||||
{% for p in data.exit_pages %}
|
||||
<div class="bar-chart-row">
|
||||
<div class="bar-chart-label" style="width: 200px; min-width: 200px; font-size: var(--font-size-xs); word-break: break-all;" title="{{ p.path }}">{{ p.label }}</div>
|
||||
<div class="bar-chart-bar">
|
||||
<div class="bar-chart-fill" style="width: {{ p.bar_pct }}%; background: #ef4444;">
|
||||
<span>{{ p.count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top transitions -->
|
||||
<div class="section-card">
|
||||
<h2>Popularne przejścia (top 30)</h2>
|
||||
<div class="table-scroll" style="max-height: 400px;">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ze strony</th>
|
||||
<th style="width: 40px;"></th>
|
||||
<th>Na stronę</th>
|
||||
<th style="width: 80px;">Liczba</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for t in data.transitions %}
|
||||
<tr>
|
||||
<td style="font-size: var(--font-size-xs);" title="{{ t.from }}">{{ t.from_label }}</td>
|
||||
<td class="transition-arrow" style="text-align: center;">→</td>
|
||||
<td style="font-size: var(--font-size-xs);" title="{{ t.to }}">{{ t.to_label }}</td>
|
||||
<td style="font-weight: 600; text-align: right;">{{ t.count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="two-columns">
|
||||
<!-- Drop-off pages -->
|
||||
<div class="section-card">
|
||||
<h2>Strony z odpływem</h2>
|
||||
<div class="table-scroll">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Strona</th>
|
||||
<th>Odsłony</th>
|
||||
<th>Wyjścia</th>
|
||||
<th>Wsp. wyjścia</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for d in data.dropoff %}
|
||||
<tr>
|
||||
<td style="font-size: var(--font-size-xs);" title="{{ d.path }}">{{ d.label }}</td>
|
||||
<td>{{ d.views }}</td>
|
||||
<td>{{ d.exits }}</td>
|
||||
<td>
|
||||
<span {% if d.exit_rate > 70 %}style="color: var(--error); font-weight: 600;"{% elif d.exit_rate > 50 %}style="color: #d97706;"{% endif %}>
|
||||
{{ d.exit_rate }}%
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Session length distribution -->
|
||||
<div class="section-card">
|
||||
<h2>Rozkład długości sesji</h2>
|
||||
{% for s in data.session_lengths %}
|
||||
<div class="bar-chart-row">
|
||||
<div class="bar-chart-label">{{ s.bucket }}</div>
|
||||
<div class="bar-chart-bar">
|
||||
<div class="bar-chart-fill" style="width: {{ s.bar_pct }}%;">
|
||||
<span>{{ s.count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- TAB: OVERVIEW -->
|
||||
<!-- ============================================================ -->
|
||||
@ -985,6 +873,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Session length distribution -->
|
||||
{% if data.session_lengths %}
|
||||
<div class="section-card">
|
||||
<h2>Ile stron ogląda się w jednej wizycie</h2>
|
||||
<div style="display: flex; gap: var(--spacing-md); flex-wrap: wrap;">
|
||||
{% for s in data.session_lengths %}
|
||||
<div style="flex: 1; min-width: 100px; text-align: center; padding: var(--spacing-md); background: var(--background); border-radius: var(--radius);">
|
||||
<div style="font-size: var(--font-size-2xl); font-weight: 700; color: var(--primary);">{{ s.count }}</div>
|
||||
<div style="font-size: var(--font-size-sm); color: var(--text-secondary);">{{ s.bucket }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Referrer Sources + Company Popularity -->
|
||||
<div class="two-columns">
|
||||
<!-- Referrer Sources -->
|
||||
|
||||
Loading…
Reference in New Issue
Block a user