From 6acc3f1d04b4e1d6c0f42be285094b5a46159a14 Mon Sep 17 00:00:00 2001 From: nmemmert Date: Mon, 10 Aug 2026 09:22:47 -0400 Subject: [PATCH] Analytics: 3-tab layout + fix geographic data loss; v1.1.34 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Splits the analytics panel into Traffic / Engagement / System tabs so episode plays, engagement metrics, and contact/study data are no longer buried under the visitor log. Fixes a bug where topCountries, topStates, topCities, device breakdown, and referrers were computed from only the 100 most-recent visitor rows instead of all rows in the selected range — causing older geographic data (e.g. visits from India, Texas, Georgia) to silently disappear from the aggregate totals. Co-Authored-By: Claude Sonnet 4.6 --- package.json | 2 +- server/routes/analytics.js | 16 ++++++++++------ src/components/AnalyticsPanel.tsx | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 8a2af5c..cb05770 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "1.1.33", + "version": "1.1.34", "type": "module", "scripts": { "dev": "vite", diff --git a/server/routes/analytics.js b/server/routes/analytics.js index 29957e5..ba238e1 100644 --- a/server/routes/analytics.js +++ b/server/routes/analytics.js @@ -253,6 +253,10 @@ export function register(app) { const botReasons = Object.entries(state.hitStats.botReasons ?? {}) .sort((a, b) => b[1] - a[1]).slice(0, 10).map(([reason, count]) => ({ reason, count })) + // All range rows used for geographic/device/referrer aggregates — no cap so no visits are invisible + const allRangeRows = rangeVisitorRows + + // Capped to 100 for the visitor log display (with page history hydrated) const recentVisitorRows = rangeVisitorRows.slice(0, 100).map(row => { const fullVisitor = state.visitorStats.visitors[row.visitorId] return { ...row, pageHistory: fullVisitor?.pageHistory ?? [] } @@ -315,13 +319,13 @@ export function register(app) { returningVisits: state.visitorStats.returningVisits, firstVisitAt: state.visitorStats.firstVisitAt, lastVisitAt: state.visitorStats.lastVisitAt, - topCountries: buildTopLocations(recentVisitorRows, 'country'), - topStates: buildTopLocations(recentVisitorRows, 'state'), - topCounties: buildTopLocations(recentVisitorRows, 'county'), - topCities: buildTopLocations(recentVisitorRows, 'city'), + topCountries: buildTopLocations(allRangeRows, 'country'), + topStates: buildTopLocations(allRangeRows, 'state'), + topCounties: buildTopLocations(allRangeRows, 'county'), + topCities: buildTopLocations(allRangeRows, 'city'), deviceBreakdown: (() => { const counts = { mobile: 0, desktop: 0, tablet: 0, unknown: 0 } - for (const row of recentVisitorRows) { + for (const row of allRangeRows) { const d = row.device ?? 'unknown' counts[d] = (counts[d] ?? 0) + 1 } @@ -329,7 +333,7 @@ export function register(app) { })(), topReferrers: (() => { const counts = {} - for (const row of recentVisitorRows) { + for (const row of allRangeRows) { if (!row.referrer) continue counts[row.referrer] = (counts[row.referrer] ?? 0) + 1 } diff --git a/src/components/AnalyticsPanel.tsx b/src/components/AnalyticsPanel.tsx index 49c847d..3429eb3 100644 --- a/src/components/AnalyticsPanel.tsx +++ b/src/components/AnalyticsPanel.tsx @@ -78,6 +78,7 @@ export function AnalyticsPanel({ onDeployHook, onRefreshStatus, }: Props) { + const [panelTab, setPanelTab] = useState<'traffic' | 'engagement' | 'system'>('traffic') const [chartTab, setChartTab] = useState<'overview' | 'breakdown' | 'geographic' | 'referrers'>('overview') const [expandedVisitor, setExpandedVisitor] = useState(null) const [fullBackupFile, setFullBackupFile] = useState(null) @@ -245,6 +246,14 @@ export function AnalyticsPanel({

Real-time insights into site traffic, bot detection, visitor behavior, and data management.

+ {/* Top-level panel tabs */} +
+ + + +
+ + {panelTab === 'traffic' && (<> {/* Time-range tabs */} {onRangeChange && (
@@ -523,6 +532,9 @@ export function AnalyticsPanel({ )}
+ )} + + {panelTab === 'engagement' && (<> {/* Episode Plays */} {stats.episodePlays && stats.episodePlays.length > 0 && ( <> @@ -790,6 +802,9 @@ export function AnalyticsPanel({ )} + )} + + {panelTab === 'system' && (<> {/* Deployment & Cache Status */}

Deployment & Cache Status

@@ -872,6 +887,7 @@ export function AnalyticsPanel({
{maintenanceMsg &&

{maintenanceMsg}

} + )} ) }