add analytics v2 plan, demo seed data, and improved funnel display
- analytics-v2 plan with prioritised improvements (comparison mode, filtering, CSV export, entry/exit pages) - seed script generating ~35k realistic events over 12 months (weighted traffic, referrers, devices, e-commerce funnel) - funnel chart now shows overall conversion rate from product views instead of step-to-step percentages - summary line with overall conversion rate and revenue Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -361,20 +361,24 @@ defmodule BerrypodWeb.Admin.Analytics do
|
||||
{"Purchase", assigns.funnel.purchases}
|
||||
]
|
||||
|
||||
max_val = steps |> Enum.map(&elem(&1, 1)) |> Enum.max(fn -> 1 end)
|
||||
top_count = assigns.funnel.product_views
|
||||
|
||||
conversion_rate =
|
||||
if top_count > 0,
|
||||
do: Float.round(assigns.funnel.purchases / top_count * 100, 1),
|
||||
else: 0.0
|
||||
|
||||
steps_with_rates =
|
||||
steps
|
||||
|> Enum.with_index()
|
||||
|> Enum.map(fn {{label, count}, i} ->
|
||||
prev_count = if i > 0, do: elem(Enum.at(steps, i - 1), 1), else: count
|
||||
rate = if prev_count > 0, do: round(count / prev_count * 100), else: 0
|
||||
width_pct = if max_val > 0, do: max(count / max_val * 100, 5), else: 5
|
||||
overall_rate = if top_count > 0, do: Float.round(count / top_count * 100, 1), else: 0.0
|
||||
width_pct = if top_count > 0, do: max(count / top_count * 100, 5), else: 5
|
||||
|
||||
%{label: label, count: count, rate: rate, width_pct: width_pct, index: i}
|
||||
%{label: label, count: count, overall_rate: overall_rate, width_pct: width_pct, index: i}
|
||||
end)
|
||||
|
||||
assigns = assign(assigns, steps: steps_with_rates)
|
||||
assigns = assign(assigns, steps: steps_with_rates, conversion_rate: conversion_rate)
|
||||
|
||||
~H"""
|
||||
<div
|
||||
@@ -390,18 +394,24 @@ defmodule BerrypodWeb.Admin.Analytics do
|
||||
</div>
|
||||
<div style={"flex: 0 0 #{step.width_pct}%; height: 2rem; background: var(--color-primary, #4f46e5); border-radius: 0.25rem; opacity: #{1 - step.index * 0.15}; display: flex; align-items: center; padding-left: 0.5rem;"}>
|
||||
<span style="font-size: 0.75rem; font-weight: 600; color: white;">
|
||||
{step.count}
|
||||
{format_number(step.count)}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
:if={step.index > 0}
|
||||
style="font-size: 0.75rem; color: color-mix(in oklch, var(--color-base-content) 60%, transparent);"
|
||||
style="font-size: 0.8125rem; font-weight: 600;"
|
||||
>
|
||||
{step.rate}%
|
||||
{step.overall_rate}%
|
||||
</span>
|
||||
</div>
|
||||
<div :if={@revenue > 0} style="margin-top: 0.5rem; font-size: 0.875rem; font-weight: 600;">
|
||||
Revenue: {Cart.format_price(@revenue)}
|
||||
<div style="margin-top: 0.75rem; font-size: 0.875rem; display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
||||
<span style="font-weight: 600;">{@conversion_rate}% overall conversion</span>
|
||||
<span
|
||||
:if={@revenue > 0}
|
||||
style="color: color-mix(in oklch, var(--color-base-content) 60%, transparent);"
|
||||
>
|
||||
· Revenue: {Cart.format_price(@revenue)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user