diff --git a/server.cjs b/server.cjs index 0d6c6bd..a09c26e 100644 --- a/server.cjs +++ b/server.cjs @@ -777,6 +777,65 @@ function viewerDashboard() { order by date asc `).all(thirtyDaysSince) + const activityLocationRows = db.prepare(` + select + date(occurred_at) as date, + country, + city, + region, + timezone, + count(distinct visitor_id) as visitors + from viewer_events + where occurred_at >= ? + and ( + country != '' + or city != '' + or region != '' + or (timezone != '' and timezone != 'Not shared') + ) + group by date(occurred_at), country, city, region, timezone + order by date asc, visitors desc + `).all(thirtyDaysSince) + + const locationsByDate = new Map() + for (const row of activityLocationRows) { + const label = [row.city, row.region, row.country || row.timezone] + .filter(Boolean) + .join(', ') + if (!label) continue + const current = locationsByDate.get(row.date) || [] + if (current.length < 4) current.push({ label, visitors: row.visitors || 0 }) + locationsByDate.set(row.date, current) + } + + const activityClientRows = db.prepare(` + select + date(occurred_at) as date, + browser, + os, + device, + count(distinct visitor_id) as visitors, + count(*) as events + from viewer_events + where occurred_at >= ? + group by date(occurred_at), browser, os, device + order by date asc, visitors desc, events desc + `).all(thirtyDaysSince) + + const clientsByDate = new Map() + for (const row of activityClientRows) { + const label = `${row.browser} on ${row.os}${row.device ? ` (${row.device})` : ''}` + const current = clientsByDate.get(row.date) || [] + if (current.length < 4) current.push({ label, visitors: row.visitors || 0, events: row.events || 0 }) + clientsByDate.set(row.date, current) + } + + const activityWithLocations = activity30d.map((row) => ({ + ...row, + client_labels: clientsByDate.get(row.date) || [], + location_labels: locationsByDate.get(row.date) || [], + })) + const countries = db.prepare(` select country, @@ -841,7 +900,7 @@ function viewerDashboard() { top_pages: topPages, clients, clients_30d: clients30d, - activity_30d: activity30d, + activity_30d: activityWithLocations, countries, locations, totals: { diff --git a/src/App.jsx b/src/App.jsx index c29e1f4..9dc103f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2060,6 +2060,7 @@ function MiniLineChart({ accent = 'text-fury-cyan', data, label, metric, stroke const height = 112 const padding = 12 const maxValue = Math.max(1, ...data.map((item) => Number(item[metric] || 0))) + const midValue = Math.round(maxValue / 2) const points = data.map((item, index) => { const x = padding + (index / Math.max(1, data.length - 1)) * (width - padding * 2) const y = height - padding - (Number(item[metric] || 0) / maxValue) * (height - padding * 2) @@ -2080,8 +2081,16 @@ function MiniLineChart({ accent = 'text-fury-cyan', data, label, metric, stroke
{formatNumber(latest)}
-