sanity-plugin-ga-dashboard 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,880 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
17
+
18
+ // src/plugin.ts
19
+ import { definePlugin } from "sanity";
20
+ import { BarChartIcon } from "@sanity/icons";
21
+
22
+ // src/components/dashboard.tsx
23
+ import { useState, useEffect, useCallback } from "react";
24
+ import {
25
+ AreaChart,
26
+ Area,
27
+ BarChart,
28
+ Bar,
29
+ XAxis,
30
+ YAxis,
31
+ CartesianGrid,
32
+ Tooltip,
33
+ ResponsiveContainer,
34
+ PieChart,
35
+ Pie,
36
+ Cell,
37
+ Legend
38
+ } from "recharts";
39
+
40
+ // src/utils/ga-api.ts
41
+ async function fetchAnalyticsData(apiUrl, dateRange) {
42
+ const res = await fetch(`${apiUrl}?range=${dateRange}`, { cache: "no-store" });
43
+ if (!res.ok) {
44
+ const err = await res.json().catch(() => null);
45
+ throw new Error(
46
+ (err == null ? void 0 : err.error) || `Analytics API error: ${res.status} ${res.statusText}`
47
+ );
48
+ }
49
+ return parseResponse(await res.json());
50
+ }
51
+ var mv = (row, i) => {
52
+ var _a, _b, _c;
53
+ return parseFloat((_c = (_b = (_a = row == null ? void 0 : row.metricValues) == null ? void 0 : _a[i]) == null ? void 0 : _b.value) != null ? _c : "0");
54
+ };
55
+ var dv = (row, i) => {
56
+ var _a, _b, _c;
57
+ return (_c = (_b = (_a = row == null ? void 0 : row.dimensionValues) == null ? void 0 : _a[i]) == null ? void 0 : _b.value) != null ? _c : "";
58
+ };
59
+ function totalOf(data) {
60
+ if (!(data == null ? void 0 : data.rows)) return 0;
61
+ return data.rows.reduce((s, r) => s + mv(r, 0), 0);
62
+ }
63
+ function parseOverview(d) {
64
+ var _a;
65
+ const row = (_a = d == null ? void 0 : d.rows) == null ? void 0 : _a[0];
66
+ if (!row) return { totalUsers: 0, newUsers: 0, sessions: 0, pageViews: 0, avgSessionDuration: 0, bounceRate: 0, engagedSessions: 0, engagementRate: 0, pagesPerSession: 0, eventsPerSession: 0 };
67
+ return {
68
+ totalUsers: mv(row, 0),
69
+ newUsers: mv(row, 1),
70
+ sessions: mv(row, 2),
71
+ pageViews: mv(row, 3),
72
+ avgSessionDuration: mv(row, 4),
73
+ bounceRate: mv(row, 5),
74
+ engagedSessions: mv(row, 6),
75
+ engagementRate: mv(row, 7),
76
+ pagesPerSession: mv(row, 8),
77
+ eventsPerSession: mv(row, 9)
78
+ };
79
+ }
80
+ function parseTimeSeries(d) {
81
+ if (!(d == null ? void 0 : d.rows)) return [];
82
+ return d.rows.map((row) => {
83
+ const s = dv(row, 0);
84
+ return {
85
+ date: `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}`,
86
+ displayDate: `${s.slice(4, 6)}/${s.slice(6, 8)}`,
87
+ users: mv(row, 0),
88
+ sessions: mv(row, 1),
89
+ pageViews: mv(row, 2)
90
+ };
91
+ });
92
+ }
93
+ function parseHourly(d) {
94
+ if (!(d == null ? void 0 : d.rows)) return [];
95
+ return d.rows.map((row) => {
96
+ const h = parseInt(dv(row, 0), 10);
97
+ const suffix = h < 12 ? "AM" : "PM";
98
+ const display = h === 0 ? "12AM" : h <= 12 ? `${h}${suffix}` : `${h - 12}${suffix}`;
99
+ return { hour: dv(row, 0), label: display, users: mv(row, 0), sessions: mv(row, 1) };
100
+ });
101
+ }
102
+ function parseTopPages(d) {
103
+ if (!(d == null ? void 0 : d.rows)) return [];
104
+ return d.rows.map((row) => ({ path: dv(row, 0), pageViews: mv(row, 0), users: mv(row, 1) }));
105
+ }
106
+ function parseLandingPages(d) {
107
+ if (!(d == null ? void 0 : d.rows)) return [];
108
+ return d.rows.map((row) => ({
109
+ path: dv(row, 0),
110
+ sessions: mv(row, 0),
111
+ users: mv(row, 1),
112
+ bounceRate: mv(row, 2)
113
+ }));
114
+ }
115
+ function parseWithPercentage(d, dimIdx, metricIdx) {
116
+ if (!(d == null ? void 0 : d.rows)) return [];
117
+ const total = totalOf(d);
118
+ return d.rows.map((row) => {
119
+ const sessions = mv(row, metricIdx);
120
+ return { name: dv(row, dimIdx), sessions, percentage: total > 0 ? Math.round(sessions / total * 1e3) / 10 : 0 };
121
+ });
122
+ }
123
+ function parseDevices(d) {
124
+ return parseWithPercentage(d, 0, 0).map((x) => ({ device: x.name, sessions: x.sessions, percentage: x.percentage }));
125
+ }
126
+ function parseBrowsers(d) {
127
+ return parseWithPercentage(d, 0, 0).map((x) => ({ browser: x.name, sessions: x.sessions, percentage: x.percentage }));
128
+ }
129
+ function parseOS(d) {
130
+ return parseWithPercentage(d, 0, 0).map((x) => ({ os: x.name, sessions: x.sessions, percentage: x.percentage }));
131
+ }
132
+ function parseCountries(d) {
133
+ if (!(d == null ? void 0 : d.rows)) return [];
134
+ return d.rows.map((row) => ({ country: dv(row, 0), users: mv(row, 0), sessions: mv(row, 1) }));
135
+ }
136
+ function parseCities(d) {
137
+ if (!(d == null ? void 0 : d.rows)) return [];
138
+ return d.rows.map((row) => ({ city: dv(row, 0), country: dv(row, 1), users: mv(row, 0), sessions: mv(row, 1) }));
139
+ }
140
+ function parseSources(d) {
141
+ if (!(d == null ? void 0 : d.rows)) return [];
142
+ return d.rows.map((row) => ({
143
+ source: [dv(row, 0), dv(row, 1)].filter(Boolean).join(" / "),
144
+ sessions: mv(row, 0),
145
+ users: mv(row, 1)
146
+ }));
147
+ }
148
+ function parseChannels(d) {
149
+ if (!(d == null ? void 0 : d.rows)) return [];
150
+ return d.rows.map((row) => ({
151
+ channel: dv(row, 0),
152
+ sessions: mv(row, 0),
153
+ users: mv(row, 1),
154
+ engagementRate: mv(row, 2)
155
+ }));
156
+ }
157
+ function parseNewVsReturning(d) {
158
+ if (!(d == null ? void 0 : d.rows)) return [];
159
+ const total = totalOf(d);
160
+ return d.rows.map((row) => {
161
+ const users = mv(row, 0);
162
+ return {
163
+ type: dv(row, 0),
164
+ users,
165
+ percentage: total > 0 ? Math.round(users / total * 1e3) / 10 : 0
166
+ };
167
+ });
168
+ }
169
+ function parseEvents(d) {
170
+ if (!(d == null ? void 0 : d.rows)) return [];
171
+ return d.rows.map((row) => ({ name: dv(row, 0), count: mv(row, 0), usersCount: mv(row, 1) }));
172
+ }
173
+ function parseReferrers(d) {
174
+ if (!(d == null ? void 0 : d.rows)) return [];
175
+ return d.rows.map((row) => ({ referrer: dv(row, 0) || "(direct)", sessions: mv(row, 0), users: mv(row, 1) }));
176
+ }
177
+ function parseActiveUsers(d) {
178
+ var _a, _b, _c, _d, _e;
179
+ return parseInt((_e = (_d = (_c = (_b = (_a = d == null ? void 0 : d.rows) == null ? void 0 : _a[0]) == null ? void 0 : _b.metricValues) == null ? void 0 : _c[0]) == null ? void 0 : _d.value) != null ? _e : "0", 10);
180
+ }
181
+ function parseResponse(raw) {
182
+ return {
183
+ activeUsers: parseActiveUsers(raw.activeUsers),
184
+ overview: parseOverview(raw.overview),
185
+ timeSeries: parseTimeSeries(raw.timeSeries),
186
+ hourlyToday: parseHourly(raw.hourlyToday),
187
+ topPages: parseTopPages(raw.topPages),
188
+ landingPages: parseLandingPages(raw.landingPages),
189
+ devices: parseDevices(raw.devices),
190
+ browsers: parseBrowsers(raw.browsers),
191
+ operatingSystems: parseOS(raw.operatingSystems),
192
+ countries: parseCountries(raw.countries),
193
+ cities: parseCities(raw.cities),
194
+ trafficSources: parseSources(raw.trafficSources),
195
+ channels: parseChannels(raw.channels),
196
+ newVsReturning: parseNewVsReturning(raw.newVsReturning),
197
+ topEvents: parseEvents(raw.topEvents),
198
+ referrers: parseReferrers(raw.referrers)
199
+ };
200
+ }
201
+
202
+ // src/components/dashboard.tsx
203
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
204
+ var DATE_RANGES = [
205
+ { label: "7d", value: "7" },
206
+ { label: "14d", value: "14" },
207
+ { label: "30d", value: "30" },
208
+ { label: "90d", value: "90" }
209
+ ];
210
+ var PALETTE = ["#4f8ef7", "#3ecf8e", "#f6b93b", "#e55353", "#a55eea", "#26c0d3", "#fd9644", "#20bf6b"];
211
+ var DEVICE_COLORS = {
212
+ desktop: "#4f8ef7",
213
+ mobile: "#3ecf8e",
214
+ tablet: "#f6b93b"
215
+ };
216
+ var CHANNEL_COLORS = {
217
+ "Organic Search": "#3ecf8e",
218
+ "Direct": "#4f8ef7",
219
+ "Referral": "#f6b93b",
220
+ "Organic Social": "#e55353",
221
+ "Email": "#a55eea",
222
+ "Paid Search": "#26c0d3",
223
+ "Display": "#fd9644"
224
+ };
225
+ var METRIC_INFO = {
226
+ "Total Users": "Unique users who visited your site in the selected period.",
227
+ "New Users": "First-time visitors \u2014 users who have never visited before.",
228
+ "Sessions": "Total browsing sessions. One user can have multiple sessions.",
229
+ "Page Views": "Total pages viewed, including repeated views of the same page.",
230
+ "Avg. Duration": "Average time a user spends in a single session.",
231
+ "Bounce Rate": "Percentage of sessions where the user left without any interaction.",
232
+ "Engaged Sessions": "Sessions lasting 10+ seconds, or with a conversion, or 2+ page views.",
233
+ "Engagement Rate": "Percentage of total sessions that were engaged sessions.",
234
+ "Pages / Session": "Average number of pages a user views per session.",
235
+ "Events / Session": "Average number of events (clicks, scrolls, etc.) fired per session."
236
+ };
237
+ var SECTION_INFO = {
238
+ "Users & Sessions Over Time": "Daily breakdown of users, sessions and page views over the selected date range.",
239
+ "Traffic by Hour \u2014 Today": "How traffic is distributed across each hour of today.",
240
+ "Channel Grouping": "Which marketing channels are driving sessions (Organic, Direct, Social, etc.).",
241
+ "Top Pages": "Most visited pages ranked by page view count.",
242
+ "Top Landing Pages": "First pages users arrive on, ranked by session count. High bounce rate may indicate poor landing experience.",
243
+ "Devices": "Share of sessions split by device type (desktop, mobile, tablet).",
244
+ "New vs Returning": "Ratio of first-time visitors to users who have visited before.",
245
+ "Browsers": "Which web browsers your visitors are using.",
246
+ "Operating Systems": "Which operating systems your visitors are running.",
247
+ "Top Events": "All events fired on your site (clicks, scrolls, form submits, custom events).",
248
+ "Top Countries": "Countries your users are visiting from, ranked by user count.",
249
+ "Top Cities": "Cities your users are visiting from, ranked by user count.",
250
+ "Traffic Sources": "Source and medium pairs showing where your traffic originates.",
251
+ "Top Referrers": "Specific pages on other websites that linked to your site."
252
+ };
253
+ var TABS = [
254
+ { id: "overview", label: "Overview", icon: "\u25A6" },
255
+ { id: "traffic", label: "Traffic", icon: "\u2197" },
256
+ { id: "content", label: "Content", icon: "\u2261" },
257
+ { id: "audience", label: "Audience", icon: "\u25C9" },
258
+ { id: "geography", label: "Geography", icon: "\u2295" },
259
+ { id: "events", label: "Events", icon: "\u26A1" },
260
+ { id: "acquisition", label: "Acquisition", icon: "\u2934" }
261
+ ];
262
+ var fNum = (n) => n >= 1e6 ? `${(n / 1e6).toFixed(1)}M` : n >= 1e3 ? `${(n / 1e3).toFixed(1)}K` : n.toLocaleString();
263
+ var fDur = (s) => {
264
+ const m = Math.floor(s / 60);
265
+ const sec = Math.round(s % 60);
266
+ return m > 0 ? `${m}m ${sec}s` : `${sec}s`;
267
+ };
268
+ var fPct = (v) => `${(v * 100).toFixed(1)}%`;
269
+ var fEngPct = (v) => `${(v * 100).toFixed(0)}%`;
270
+ var GLOBAL_CSS = `
271
+ @keyframes ga-spin { to { transform: rotate(360deg); } }
272
+ @keyframes ga-pulse { 0%,100%{box-shadow:0 0 0 3px rgba(62,207,142,.3)} 50%{box-shadow:0 0 0 7px rgba(62,207,142,.08)} }
273
+ @keyframes ga-fadein { from{opacity:0;transform:translateY(4px)} to{opacity:1;transform:translateY(0)} }
274
+ @keyframes ga-shimmer { 0%{background-position:-600px 0} 100%{background-position:600px 0} }
275
+ .ga-skel {
276
+ background:linear-gradient(90deg,#f1f5f9 25%,#e8eef4 50%,#f1f5f9 75%);
277
+ background-size:1200px 100%; animation:ga-shimmer 1.4s infinite linear; border-radius:4px;
278
+ }
279
+ .ga-tooltip-wrap { position:relative; display:inline-flex; align-items:center; }
280
+ .ga-tooltip-wrap .ga-tip {
281
+ visibility:hidden; opacity:0; pointer-events:none;
282
+ position:absolute; bottom:calc(100% + 6px); left:50%; transform:translateX(-50%);
283
+ background:#1e293b; color:#f1f5f9; font-size:12px; line-height:1.5;
284
+ padding:8px 12px; border-radius:7px; white-space:normal; width:220px;
285
+ box-shadow:0 4px 16px rgba(0,0,0,.18); z-index:9999; transition:opacity .15s;
286
+ }
287
+ .ga-tooltip-wrap .ga-tip::after {
288
+ content:''; position:absolute; top:100%; left:50%; transform:translateX(-50%);
289
+ border:5px solid transparent; border-top-color:#1e293b;
290
+ }
291
+ .ga-tooltip-wrap:hover .ga-tip { visibility:visible; opacity:1; }
292
+ .ga-info-btn {
293
+ width:16px; height:16px; border-radius:50%; border:1.5px solid #94a3b8;
294
+ background:transparent; color:#94a3b8; font-size:10px; font-weight:700;
295
+ cursor:default; display:inline-flex; align-items:center; justify-content:center;
296
+ margin-left:6px; flex-shrink:0; line-height:1; padding:0;
297
+ }
298
+ .ga-info-btn:hover { border-color:#4f8ef7; color:#4f8ef7; }
299
+ .ga-tab-btn { border:none; background:none; cursor:pointer; width:100%; padding:0; }
300
+ .ga-tab-btn:focus { outline:none; }
301
+ .ga-date-btn { border:none; cursor:pointer; }
302
+ .ga-retry-btn { border:none; cursor:pointer; }
303
+ `;
304
+ function InfoIcon({ text }) {
305
+ return /* @__PURE__ */ jsxs("span", { className: "ga-tooltip-wrap", children: [
306
+ /* @__PURE__ */ jsx("button", { className: "ga-info-btn", tabIndex: -1, children: "i" }),
307
+ /* @__PURE__ */ jsx("span", { className: "ga-tip", children: text })
308
+ ] });
309
+ }
310
+ function Skel({ w, h = 14, r = 4, style }) {
311
+ return /* @__PURE__ */ jsx("div", { className: "ga-skel", style: __spreadValues({ width: w != null ? w : "100%", height: h, borderRadius: r, flexShrink: 0 }, style) });
312
+ }
313
+ function Panel({ title, children, info, style }) {
314
+ return /* @__PURE__ */ jsxs("div", { style: __spreadValues({
315
+ background: "#fff",
316
+ border: "1px solid #e2e8f0",
317
+ borderRadius: 10,
318
+ padding: "20px 22px",
319
+ boxSizing: "border-box"
320
+ }, style), children: [
321
+ title && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", marginBottom: 16 }, children: [
322
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 13, fontWeight: 600, color: "#1e293b", letterSpacing: "0.01em" }, children: title }),
323
+ info && /* @__PURE__ */ jsx(InfoIcon, { text: info })
324
+ ] }),
325
+ children
326
+ ] });
327
+ }
328
+ function SectionLabel({ children }) {
329
+ return /* @__PURE__ */ jsx("div", { style: { fontSize: 11, fontWeight: 700, color: "#94a3b8", textTransform: "uppercase", letterSpacing: "0.08em", marginBottom: 14 }, children });
330
+ }
331
+ function DataTable({ headers, rows, empty = "No data", loading, skeletonRows = 6 }) {
332
+ return /* @__PURE__ */ jsx("div", { style: { width: "100%", overflowX: "auto" }, children: /* @__PURE__ */ jsxs("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 13, tableLayout: "fixed" }, children: [
333
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: headers.map((h, i) => {
334
+ var _a;
335
+ return /* @__PURE__ */ jsx("th", { style: {
336
+ padding: "6px 10px",
337
+ textAlign: (_a = h.align) != null ? _a : "left",
338
+ color: "#94a3b8",
339
+ fontWeight: 600,
340
+ fontSize: 11,
341
+ borderBottom: "1px solid #f1f5f9",
342
+ textTransform: "uppercase",
343
+ letterSpacing: "0.05em",
344
+ width: h.width,
345
+ whiteSpace: "nowrap"
346
+ }, children: h.label }, i);
347
+ }) }) }),
348
+ /* @__PURE__ */ jsx("tbody", { children: loading ? Array.from({ length: skeletonRows }).map((_, ri) => /* @__PURE__ */ jsx("tr", { style: { borderBottom: "1px solid #f8fafc" }, children: headers.map((_h, ci) => /* @__PURE__ */ jsx("td", { style: { padding: "9px 10px" }, children: /* @__PURE__ */ jsx(Skel, { w: ci === 0 ? `${60 + ri * 7 % 30}%` : "70%", h: 12 }) }, ci)) }, ri)) : rows.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: headers.length, style: { padding: "28px", textAlign: "center", color: "#cbd5e1", fontSize: 13 }, children: empty }) }) : rows.map((row, ri) => /* @__PURE__ */ jsx(
349
+ "tr",
350
+ {
351
+ style: { borderBottom: "1px solid #f8fafc", transition: "background .1s" },
352
+ onMouseEnter: (e) => e.currentTarget.style.background = "#f8fafc",
353
+ onMouseLeave: (e) => e.currentTarget.style.background = "",
354
+ children: row.map((cell, ci) => {
355
+ var _a, _b;
356
+ return /* @__PURE__ */ jsx("td", { style: {
357
+ padding: "9px 10px",
358
+ textAlign: (_b = (_a = headers[ci]) == null ? void 0 : _a.align) != null ? _b : "left",
359
+ color: ci === 0 ? "#334155" : ci === 1 ? "#1e293b" : "#94a3b8",
360
+ fontWeight: ci === 1 ? 600 : 400,
361
+ verticalAlign: "middle"
362
+ }, children: cell }, ci);
363
+ })
364
+ },
365
+ ri
366
+ )) })
367
+ ] }) });
368
+ }
369
+ function Donut({ data, colors, info, loading }) {
370
+ if (loading) return /* @__PURE__ */ jsxs("div", { children: [
371
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", padding: "16px 0" }, children: /* @__PURE__ */ jsx("div", { className: "ga-skel", style: { width: 120, height: 120, borderRadius: "50%" } }) }),
372
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 8, marginTop: 4 }, children: Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
373
+ /* @__PURE__ */ jsx("div", { className: "ga-skel", style: { width: 8, height: 8, borderRadius: 2, flexShrink: 0 } }),
374
+ /* @__PURE__ */ jsx(Skel, { w: `${50 + i * 12}%`, h: 11 }),
375
+ /* @__PURE__ */ jsx(Skel, { w: 30, h: 11, style: { marginLeft: "auto" } })
376
+ ] }, i)) })
377
+ ] });
378
+ if (data.length === 0) return /* @__PURE__ */ jsx("div", { style: { color: "#cbd5e1", fontSize: 13, padding: "20px 0", textAlign: "center" }, children: "No data" });
379
+ return /* @__PURE__ */ jsxs("div", { children: [
380
+ /* @__PURE__ */ jsx("div", { style: { height: 180 }, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(PieChart, { children: [
381
+ /* @__PURE__ */ jsx(
382
+ Pie,
383
+ {
384
+ data,
385
+ dataKey: "value",
386
+ nameKey: "name",
387
+ cx: "50%",
388
+ cy: "50%",
389
+ innerRadius: 48,
390
+ outerRadius: 76,
391
+ paddingAngle: 2,
392
+ children: data.map((_, i) => /* @__PURE__ */ jsx(Cell, { fill: colors[i % colors.length] }, i))
393
+ }
394
+ ),
395
+ /* @__PURE__ */ jsx(
396
+ Tooltip,
397
+ {
398
+ formatter: (v) => fNum(Number(v)),
399
+ contentStyle: { background: "#fff", border: "1px solid #e2e8f0", borderRadius: 7, fontSize: 13 }
400
+ }
401
+ )
402
+ ] }) }) }),
403
+ info && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#94a3b8", textAlign: "center", marginBottom: 12 }, children: info }),
404
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 7 }, children: data.map((d, i) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
405
+ /* @__PURE__ */ jsx("div", { style: { width: 9, height: 9, borderRadius: 2, background: colors[i % colors.length], flexShrink: 0 } }),
406
+ /* @__PURE__ */ jsx("span", { style: { flex: 1, fontSize: 13, color: "#475569", textTransform: "capitalize" }, children: d.name }),
407
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: "#cbd5e1" }, children: [
408
+ d.percentage,
409
+ "%"
410
+ ] }),
411
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 13, fontWeight: 600, color: "#1e293b", minWidth: 40, textAlign: "right" }, children: fNum(d.value) })
412
+ ] }, d.name)) })
413
+ ] });
414
+ }
415
+ function MetricCard({ label, value, sub, color, info, loading }) {
416
+ return /* @__PURE__ */ jsxs(
417
+ "div",
418
+ {
419
+ style: {
420
+ flex: "1 1 140px",
421
+ minWidth: 132,
422
+ background: "#fff",
423
+ border: "1px solid #e2e8f0",
424
+ borderRadius: 10,
425
+ padding: "16px 18px",
426
+ boxSizing: "border-box",
427
+ transition: "box-shadow .15s"
428
+ },
429
+ onMouseEnter: (e) => e.currentTarget.style.boxShadow = "0 4px 14px rgba(0,0,0,.07)",
430
+ onMouseLeave: (e) => e.currentTarget.style.boxShadow = "",
431
+ children: [
432
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", marginBottom: 10 }, children: [
433
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: "#94a3b8", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.06em", flex: 1 }, children: label }),
434
+ info && /* @__PURE__ */ jsx(InfoIcon, { text: info })
435
+ ] }),
436
+ loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
437
+ /* @__PURE__ */ jsx(Skel, { w: "55%", h: 22, r: 5 }),
438
+ sub && /* @__PURE__ */ jsx(Skel, { w: "40%", h: 11, style: { marginTop: 6 } })
439
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
440
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 26, fontWeight: 700, color, lineHeight: 1, marginBottom: sub ? 4 : 0 }, children: value }),
441
+ sub && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#94a3b8", marginTop: 3 }, children: sub })
442
+ ] })
443
+ ]
444
+ }
445
+ );
446
+ }
447
+ function ActiveBadge({ count, loading }) {
448
+ return /* @__PURE__ */ jsxs("div", { style: {
449
+ display: "inline-flex",
450
+ alignItems: "center",
451
+ gap: 10,
452
+ background: "#f0fdf9",
453
+ border: "1px solid #a7f3d0",
454
+ borderRadius: 10,
455
+ padding: "12px 20px",
456
+ marginBottom: 20
457
+ }, children: [
458
+ /* @__PURE__ */ jsx("div", { style: {
459
+ width: 10,
460
+ height: 10,
461
+ borderRadius: "50%",
462
+ background: "#10b981",
463
+ animation: "ga-pulse 2s infinite",
464
+ flexShrink: 0
465
+ } }),
466
+ loading ? /* @__PURE__ */ jsx(Skel, { w: 80, h: 20, r: 5 }) : /* @__PURE__ */ jsx("span", { style: { fontSize: 22, fontWeight: 700, color: "#065f46" }, children: count.toLocaleString() }),
467
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 13, color: "#047857" }, children: "users active right now" }),
468
+ /* @__PURE__ */ jsx(InfoIcon, { text: "Real-time count of users active on your site in the last 30 minutes." })
469
+ ] });
470
+ }
471
+ function PathCell({ path }) {
472
+ return /* @__PURE__ */ jsx("span", { title: path, style: {
473
+ display: "block",
474
+ overflow: "hidden",
475
+ textOverflow: "ellipsis",
476
+ whiteSpace: "nowrap",
477
+ color: "#4f8ef7",
478
+ fontSize: 13
479
+ }, children: path });
480
+ }
481
+ function EmptyTab() {
482
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 300, color: "#cbd5e1", fontSize: 14 }, children: "No data to display" });
483
+ }
484
+ function SkeletonChart({ height }) {
485
+ return /* @__PURE__ */ jsxs("div", { style: { position: "relative", height, overflow: "hidden" }, children: [
486
+ /* @__PURE__ */ jsx(Skel, { w: "100%", h: height, r: 6 }),
487
+ /* @__PURE__ */ jsx("div", { style: {
488
+ position: "absolute",
489
+ bottom: 0,
490
+ left: 0,
491
+ right: 0,
492
+ display: "flex",
493
+ justifyContent: "space-between",
494
+ padding: "0 4px 6px",
495
+ gap: 4,
496
+ pointerEvents: "none"
497
+ }, children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ jsx(Skel, { w: 32, h: 10, r: 3 }, i)) })
498
+ ] });
499
+ }
500
+ function OverviewTab({ data, loading }) {
501
+ return /* @__PURE__ */ jsxs("div", { style: { animation: "ga-fadein .25s ease", overflowX: "hidden" }, children: [
502
+ /* @__PURE__ */ jsx(ActiveBadge, { count: data.activeUsers, loading }),
503
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Key Metrics" }),
504
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 10, flexWrap: "wrap", marginBottom: 24 }, children: [
505
+ ["Total Users", fNum(data.overview.totalUsers), "#4f8ef7"],
506
+ ["New Users", fNum(data.overview.newUsers), "#3ecf8e"],
507
+ ["Sessions", fNum(data.overview.sessions), "#f6b93b"],
508
+ ["Page Views", fNum(data.overview.pageViews), "#e55353"],
509
+ ["Avg. Duration", fDur(data.overview.avgSessionDuration), "#a55eea"],
510
+ ["Bounce Rate", fPct(data.overview.bounceRate), "#fd9644"]
511
+ ].map(([l, v, c]) => /* @__PURE__ */ jsx(MetricCard, { label: l, value: v, color: c, info: METRIC_INFO[l], loading }, l)) }),
512
+ /* @__PURE__ */ jsx(SectionLabel, { children: "Engagement" }),
513
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 10, flexWrap: "wrap" }, children: [
514
+ ["Engaged Sessions", fNum(data.overview.engagedSessions), "#26c0d3"],
515
+ ["Engagement Rate", fEngPct(data.overview.engagementRate), "#3ecf8e"],
516
+ ["Pages / Session", data.overview.pagesPerSession.toFixed(1), "#8b5cf6"],
517
+ ["Events / Session", data.overview.eventsPerSession.toFixed(1), "#f59e0b"]
518
+ ].map(([l, v, c]) => /* @__PURE__ */ jsx(MetricCard, { label: l, value: v, color: c, info: METRIC_INFO[l], loading }, l)) })
519
+ ] });
520
+ }
521
+ function TrafficTab({ data, loading }) {
522
+ const channelBar = data.channels.map((c) => ({ name: c.channel, Sessions: c.sessions, Users: c.users }));
523
+ return /* @__PURE__ */ jsxs("div", { style: { animation: "ga-fadein .25s ease", display: "flex", flexDirection: "column", gap: 20 }, children: [
524
+ /* @__PURE__ */ jsx(Panel, { title: "Users, Sessions & Page Views", info: SECTION_INFO["Users & Sessions Over Time"], children: loading ? /* @__PURE__ */ jsx(SkeletonChart, { height: 300 }) : /* @__PURE__ */ jsx("div", { style: { height: 300 }, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(AreaChart, { data: data.timeSeries, margin: { top: 4, right: 8, left: 0, bottom: 0 }, children: [
525
+ /* @__PURE__ */ jsx("defs", { children: [["gU", "#4f8ef7"], ["gS", "#3ecf8e"], ["gP", "#e55353"]].map(([id, c]) => /* @__PURE__ */ jsxs("linearGradient", { id, x1: "0", y1: "0", x2: "0", y2: "1", children: [
526
+ /* @__PURE__ */ jsx("stop", { offset: "5%", stopColor: c, stopOpacity: 0.18 }),
527
+ /* @__PURE__ */ jsx("stop", { offset: "95%", stopColor: c, stopOpacity: 0 })
528
+ ] }, id)) }),
529
+ /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "#f1f5f9" }),
530
+ /* @__PURE__ */ jsx(XAxis, { dataKey: "displayDate", fontSize: 11, tick: { fill: "#94a3b8" }, axisLine: false, tickLine: false }),
531
+ /* @__PURE__ */ jsx(YAxis, { fontSize: 11, tick: { fill: "#94a3b8" }, axisLine: false, tickLine: false, width: 36 }),
532
+ /* @__PURE__ */ jsx(Tooltip, { contentStyle: { background: "#fff", border: "1px solid #e2e8f0", borderRadius: 7, fontSize: 13 }, cursor: { stroke: "#f1f5f9" } }),
533
+ /* @__PURE__ */ jsx(Legend, { wrapperStyle: { fontSize: 13, paddingTop: 10 } }),
534
+ /* @__PURE__ */ jsx(Area, { type: "monotone", dataKey: "users", name: "Users", stroke: "#4f8ef7", fill: "url(#gU)", strokeWidth: 2, dot: false }),
535
+ /* @__PURE__ */ jsx(Area, { type: "monotone", dataKey: "sessions", name: "Sessions", stroke: "#3ecf8e", fill: "url(#gS)", strokeWidth: 2, dot: false }),
536
+ /* @__PURE__ */ jsx(Area, { type: "monotone", dataKey: "pageViews", name: "Pages", stroke: "#e55353", fill: "url(#gP)", strokeWidth: 2, dot: false })
537
+ ] }) }) }) }),
538
+ /* @__PURE__ */ jsx(Panel, { title: "Traffic by Hour \u2014 Today", info: SECTION_INFO["Traffic by Hour \u2014 Today"], children: loading ? /* @__PURE__ */ jsx(SkeletonChart, { height: 220 }) : /* @__PURE__ */ jsx("div", { style: { height: 220 }, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(BarChart, { data: data.hourlyToday, margin: { top: 4, right: 8, left: 0, bottom: 0 }, barSize: 12, barGap: 3, children: [
539
+ /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "#f1f5f9", vertical: false }),
540
+ /* @__PURE__ */ jsx(XAxis, { dataKey: "label", fontSize: 11, tick: { fill: "#94a3b8" }, axisLine: false, tickLine: false, interval: 1 }),
541
+ /* @__PURE__ */ jsx(YAxis, { fontSize: 11, tick: { fill: "#94a3b8" }, axisLine: false, tickLine: false, width: 32 }),
542
+ /* @__PURE__ */ jsx(Tooltip, { contentStyle: { background: "#fff", border: "1px solid #e2e8f0", borderRadius: 7, fontSize: 13 } }),
543
+ /* @__PURE__ */ jsx(Legend, { wrapperStyle: { fontSize: 13, paddingTop: 8 } }),
544
+ /* @__PURE__ */ jsx(Bar, { dataKey: "users", name: "Users", fill: "#4f8ef7", radius: [3, 3, 0, 0] }),
545
+ /* @__PURE__ */ jsx(Bar, { dataKey: "sessions", name: "Sessions", fill: "#3ecf8e", radius: [3, 3, 0, 0] })
546
+ ] }) }) }) }),
547
+ /* @__PURE__ */ jsx(Panel, { title: "Channel Grouping", info: SECTION_INFO["Channel Grouping"], children: loading ? /* @__PURE__ */ jsx(SkeletonChart, { height: 200 }) : /* @__PURE__ */ jsx("div", { style: { height: Math.max(200, data.channels.length * 46) }, children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxs(BarChart, { data: channelBar, layout: "vertical", margin: { top: 4, right: 24, left: 8, bottom: 0 }, barSize: 11, children: [
548
+ /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "#f1f5f9", horizontal: false }),
549
+ /* @__PURE__ */ jsx(XAxis, { type: "number", fontSize: 11, tick: { fill: "#94a3b8" }, axisLine: false, tickLine: false }),
550
+ /* @__PURE__ */ jsx(YAxis, { type: "category", dataKey: "name", fontSize: 12, tick: { fill: "#475569" }, axisLine: false, tickLine: false, width: 130 }),
551
+ /* @__PURE__ */ jsx(Tooltip, { contentStyle: { background: "#fff", border: "1px solid #e2e8f0", borderRadius: 7, fontSize: 13 } }),
552
+ /* @__PURE__ */ jsx(Legend, { wrapperStyle: { fontSize: 13, paddingTop: 8 } }),
553
+ /* @__PURE__ */ jsx(Bar, { dataKey: "Sessions", radius: [0, 3, 3, 0], children: channelBar.map((e, i) => {
554
+ var _a;
555
+ return /* @__PURE__ */ jsx(Cell, { fill: (_a = CHANNEL_COLORS[e.name]) != null ? _a : PALETTE[i % PALETTE.length] }, i);
556
+ }) })
557
+ ] }) }) }) })
558
+ ] });
559
+ }
560
+ function ContentTab({ data, loading }) {
561
+ return /* @__PURE__ */ jsxs("div", { style: { animation: "ga-fadein .25s ease", display: "flex", flexDirection: "column", gap: 20 }, children: [
562
+ /* @__PURE__ */ jsx(Panel, { title: "Top Pages", info: SECTION_INFO["Top Pages"], children: /* @__PURE__ */ jsx(
563
+ DataTable,
564
+ {
565
+ loading,
566
+ headers: [{ label: "Page Path" }, { label: "Views", align: "right", width: "72px" }, { label: "Users", align: "right", width: "62px" }],
567
+ rows: data.topPages.map((p) => [/* @__PURE__ */ jsx(PathCell, { path: p.path }, p.path), fNum(p.pageViews), fNum(p.users)])
568
+ }
569
+ ) }),
570
+ /* @__PURE__ */ jsx(Panel, { title: "Top Landing Pages", info: SECTION_INFO["Top Landing Pages"], children: /* @__PURE__ */ jsx(
571
+ DataTable,
572
+ {
573
+ loading,
574
+ headers: [{ label: "Landing Page" }, { label: "Sessions", align: "right", width: "80px" }, { label: "Bounce", align: "right", width: "68px" }],
575
+ rows: data.landingPages.map((p) => [/* @__PURE__ */ jsx(PathCell, { path: p.path }, p.path), fNum(p.sessions), fPct(p.bounceRate)])
576
+ }
577
+ ) })
578
+ ] });
579
+ }
580
+ function AudienceTab({ data, loading }) {
581
+ return /* @__PURE__ */ jsx("div", { style: { animation: "ga-fadein .25s ease", display: "flex", flexDirection: "column", gap: 20 }, children: /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "minmax(0,1fr) minmax(0,1fr)", gap: 12 }, children: [
582
+ /* @__PURE__ */ jsx(Panel, { title: "Devices", info: SECTION_INFO["Devices"], children: /* @__PURE__ */ jsx(
583
+ Donut,
584
+ {
585
+ loading,
586
+ data: data.devices.map((d) => ({ name: d.device, value: d.sessions, percentage: d.percentage })),
587
+ colors: data.devices.map((d) => {
588
+ var _a;
589
+ return (_a = DEVICE_COLORS[d.device.toLowerCase()]) != null ? _a : "#4f8ef7";
590
+ })
591
+ }
592
+ ) }),
593
+ /* @__PURE__ */ jsx(Panel, { title: "New vs Returning", info: SECTION_INFO["New vs Returning"], children: /* @__PURE__ */ jsx(
594
+ Donut,
595
+ {
596
+ loading,
597
+ data: data.newVsReturning.map((d) => ({ name: d.type, value: d.users, percentage: d.percentage })),
598
+ colors: ["#4f8ef7", "#3ecf8e"]
599
+ }
600
+ ) }),
601
+ /* @__PURE__ */ jsx(Panel, { title: "Browsers", info: SECTION_INFO["Browsers"], children: /* @__PURE__ */ jsx(
602
+ Donut,
603
+ {
604
+ loading,
605
+ data: data.browsers.map((d) => ({ name: d.browser, value: d.sessions, percentage: d.percentage })),
606
+ colors: PALETTE
607
+ }
608
+ ) }),
609
+ /* @__PURE__ */ jsx(Panel, { title: "Operating Systems", info: SECTION_INFO["Operating Systems"], children: /* @__PURE__ */ jsx(
610
+ Donut,
611
+ {
612
+ loading,
613
+ data: data.operatingSystems.map((d) => ({ name: d.os, value: d.sessions, percentage: d.percentage })),
614
+ colors: PALETTE
615
+ }
616
+ ) })
617
+ ] }) });
618
+ }
619
+ function GeographyTab({ data, loading }) {
620
+ return /* @__PURE__ */ jsxs("div", { style: { animation: "ga-fadein .25s ease", display: "flex", flexDirection: "column", gap: 20 }, children: [
621
+ /* @__PURE__ */ jsx(Panel, { title: "Top Countries", info: SECTION_INFO["Top Countries"], children: /* @__PURE__ */ jsx(
622
+ DataTable,
623
+ {
624
+ loading,
625
+ headers: [{ label: "Country" }, { label: "Users", align: "right", width: "70px" }, { label: "Sessions", align: "right", width: "78px" }],
626
+ rows: data.countries.map((c) => [c.country, fNum(c.users), fNum(c.sessions)])
627
+ }
628
+ ) }),
629
+ /* @__PURE__ */ jsx(Panel, { title: "Top Cities", info: SECTION_INFO["Top Cities"], children: /* @__PURE__ */ jsx(
630
+ DataTable,
631
+ {
632
+ loading,
633
+ headers: [{ label: "City" }, { label: "Country", width: "110px" }, { label: "Users", align: "right", width: "65px" }, { label: "Sessions", align: "right", width: "75px" }],
634
+ rows: data.cities.map((c) => [
635
+ c.city,
636
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: "#94a3b8" }, children: c.country }, c.city),
637
+ fNum(c.users),
638
+ fNum(c.sessions)
639
+ ])
640
+ }
641
+ ) })
642
+ ] });
643
+ }
644
+ function EventsTab({ data, loading }) {
645
+ if (!loading && data.topEvents.length === 0) return /* @__PURE__ */ jsx(EmptyTab, {});
646
+ return /* @__PURE__ */ jsx("div", { style: { animation: "ga-fadein .25s ease" }, children: /* @__PURE__ */ jsx(Panel, { title: "Top Events", info: SECTION_INFO["Top Events"], children: /* @__PURE__ */ jsx(
647
+ DataTable,
648
+ {
649
+ loading,
650
+ headers: [{ label: "Event Name" }, { label: "Count", align: "right", width: "80px" }, { label: "Users", align: "right", width: "70px" }],
651
+ rows: data.topEvents.map((e) => [e.name, fNum(e.count), fNum(e.usersCount)])
652
+ }
653
+ ) }) });
654
+ }
655
+ function AcquisitionTab({ data, loading }) {
656
+ return /* @__PURE__ */ jsxs("div", { style: { animation: "ga-fadein .25s ease", display: "flex", flexDirection: "column", gap: 20 }, children: [
657
+ /* @__PURE__ */ jsx(Panel, { title: "Traffic Sources", info: SECTION_INFO["Traffic Sources"], children: /* @__PURE__ */ jsx(
658
+ DataTable,
659
+ {
660
+ loading,
661
+ headers: [{ label: "Source / Medium" }, { label: "Sessions", align: "right", width: "82px" }, { label: "Users", align: "right", width: "65px" }],
662
+ rows: data.trafficSources.map((s) => [s.source || "(direct)", fNum(s.sessions), fNum(s.users)])
663
+ }
664
+ ) }),
665
+ /* @__PURE__ */ jsx(Panel, { title: "Top Referrers", info: SECTION_INFO["Top Referrers"], children: /* @__PURE__ */ jsx(
666
+ DataTable,
667
+ {
668
+ loading,
669
+ headers: [{ label: "Referrer" }, { label: "Sessions", align: "right", width: "82px" }, { label: "Users", align: "right", width: "65px" }],
670
+ rows: data.referrers.map((r) => [
671
+ /* @__PURE__ */ jsx("span", { title: r.referrer, style: { display: "block", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontSize: 13 }, children: r.referrer }, r.referrer),
672
+ fNum(r.sessions),
673
+ fNum(r.users)
674
+ ])
675
+ }
676
+ ) })
677
+ ] });
678
+ }
679
+ function Dashboard({ apiUrl }) {
680
+ var _a;
681
+ const [data, setData] = useState(null);
682
+ const [loading, setLoading] = useState(true);
683
+ const [error, setError] = useState(null);
684
+ const [dateRange, setDateRange] = useState("30");
685
+ const [activeTab, setActiveTab] = useState("overview");
686
+ const loadData = useCallback(async () => {
687
+ try {
688
+ setLoading(true);
689
+ setError(null);
690
+ setData(await fetchAnalyticsData(apiUrl, dateRange));
691
+ } catch (err) {
692
+ console.error("Failed to fetch analytics data", err);
693
+ setError(err instanceof Error ? err.message : "Failed to fetch analytics");
694
+ } finally {
695
+ setLoading(false);
696
+ }
697
+ }, [apiUrl, dateRange]);
698
+ useEffect(() => {
699
+ loadData();
700
+ }, [loadData]);
701
+ const H = "calc(100vh - 51px)";
702
+ if (loading && !data) return /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "100vh", flexDirection: "column", gap: 14, background: "#f8fafc" }, children: [
703
+ /* @__PURE__ */ jsx("div", { style: { width: 36, height: 36, border: "3px solid #e2e8f0", borderTopColor: "#4f8ef7", borderRadius: "50%", animation: "ga-spin .8s linear infinite" } }),
704
+ /* @__PURE__ */ jsx("div", { style: { color: "#94a3b8", fontSize: 14 }, children: "Loading analytics\u2026" }),
705
+ /* @__PURE__ */ jsx("style", { children: GLOBAL_CSS })
706
+ ] });
707
+ if (error && !data) return /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "center", alignItems: "center", height: H, background: "#f8fafc" }, children: [
708
+ /* @__PURE__ */ jsx("style", { children: GLOBAL_CSS }),
709
+ /* @__PURE__ */ jsxs("div", { style: { maxWidth: 480, width: "100%", background: "#fff", border: "1px solid #e2e8f0", borderRadius: 12, padding: 32 }, children: [
710
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 700, color: "#dc2626", marginBottom: 8, fontSize: 15 }, children: "Failed to load analytics" }),
711
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 13, color: "#64748b", marginBottom: 20, lineHeight: 1.6 }, children: error }),
712
+ /* @__PURE__ */ jsx(
713
+ "button",
714
+ {
715
+ className: "ga-retry-btn",
716
+ onClick: loadData,
717
+ style: { padding: "9px 22px", background: "#4f8ef7", color: "#fff", borderRadius: 7, cursor: "pointer", fontSize: 13, fontWeight: 600 },
718
+ children: "Retry"
719
+ }
720
+ )
721
+ ] })
722
+ ] });
723
+ if (!data) return null;
724
+ return /* @__PURE__ */ jsxs("div", { style: {
725
+ height: H,
726
+ background: "#f8fafc",
727
+ overflow: "hidden",
728
+ fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif',
729
+ display: "flex",
730
+ justifyContent: "center",
731
+ alignItems: "stretch"
732
+ }, children: [
733
+ /* @__PURE__ */ jsx("style", { children: GLOBAL_CSS }),
734
+ /* @__PURE__ */ jsxs("div", { style: {
735
+ display: "flex",
736
+ width: "100%",
737
+ height: "100%",
738
+ overflow: "hidden",
739
+ boxShadow: "0 0 0 1px #e2e8f0"
740
+ }, children: [
741
+ /* @__PURE__ */ jsxs("div", { style: {
742
+ width: 180,
743
+ background: "#fff",
744
+ borderRight: "1px solid #e2e8f0",
745
+ display: "flex",
746
+ flexDirection: "column",
747
+ flexShrink: 0,
748
+ overflow: "hidden"
749
+ }, children: [
750
+ /* @__PURE__ */ jsxs("div", { style: { padding: "20px 16px 16px", borderBottom: "1px solid #f1f5f9" }, children: [
751
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 13, fontWeight: 700, color: "#1e293b", letterSpacing: "0.01em" }, children: "Analytics" }),
752
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 11, color: "#94a3b8", marginTop: 2 }, children: "Google Analytics 4" })
753
+ ] }),
754
+ /* @__PURE__ */ jsxs("div", { style: { padding: "12px 12px 8px" }, children: [
755
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 10, fontWeight: 700, color: "#cbd5e1", textTransform: "uppercase", letterSpacing: "0.07em", marginBottom: 6 }, children: "Date Range" }),
756
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 4, flexWrap: "wrap" }, children: DATE_RANGES.map((r) => /* @__PURE__ */ jsx(
757
+ "button",
758
+ {
759
+ className: "ga-date-btn",
760
+ onClick: () => setDateRange(r.value),
761
+ style: {
762
+ padding: "4px 8px",
763
+ fontSize: 11,
764
+ fontWeight: dateRange === r.value ? 700 : 400,
765
+ borderRadius: 5,
766
+ border: dateRange === r.value ? "1.5px solid #4f8ef7" : "1.5px solid #e2e8f0",
767
+ background: dateRange === r.value ? "#eff6ff" : "#fff",
768
+ color: dateRange === r.value ? "#4f8ef7" : "#64748b",
769
+ cursor: "pointer"
770
+ },
771
+ children: r.label
772
+ },
773
+ r.value
774
+ )) })
775
+ ] }),
776
+ /* @__PURE__ */ jsxs("nav", { style: { flex: 1, overflow: "auto", padding: "8px 8px" }, children: [
777
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 10, fontWeight: 700, color: "#cbd5e1", textTransform: "uppercase", letterSpacing: "0.07em", marginBottom: 6, paddingLeft: 8 }, children: "Sections" }),
778
+ TABS.map((tab) => {
779
+ const active = activeTab === tab.id;
780
+ return /* @__PURE__ */ jsxs(
781
+ "button",
782
+ {
783
+ className: "ga-tab-btn",
784
+ onClick: () => setActiveTab(tab.id),
785
+ style: {
786
+ display: "flex",
787
+ alignItems: "center",
788
+ gap: 9,
789
+ padding: "9px 10px",
790
+ borderRadius: 7,
791
+ marginBottom: 2,
792
+ background: active ? "#eff6ff" : "transparent",
793
+ color: active ? "#4f8ef7" : "#64748b",
794
+ fontWeight: active ? 600 : 400,
795
+ fontSize: 13
796
+ },
797
+ children: [
798
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 14, opacity: 0.8 }, children: tab.icon }),
799
+ /* @__PURE__ */ jsx("span", { children: tab.label }),
800
+ active && /* @__PURE__ */ jsx("span", { style: { marginLeft: "auto", width: 4, height: 4, borderRadius: "50%", background: "#4f8ef7" } })
801
+ ]
802
+ },
803
+ tab.id
804
+ );
805
+ })
806
+ ] }),
807
+ /* @__PURE__ */ jsx("div", { style: { padding: "12px", borderTop: "1px solid #f1f5f9" }, children: /* @__PURE__ */ jsx(
808
+ "button",
809
+ {
810
+ className: "ga-retry-btn",
811
+ onClick: loadData,
812
+ style: {
813
+ width: "100%",
814
+ padding: "8px",
815
+ background: "#f8fafc",
816
+ border: "1px solid #e2e8f0",
817
+ borderRadius: 7,
818
+ fontSize: 12,
819
+ color: loading ? "#94a3b8" : "#475569",
820
+ cursor: "pointer",
821
+ display: "flex",
822
+ alignItems: "center",
823
+ justifyContent: "center",
824
+ gap: 6,
825
+ fontWeight: 500
826
+ },
827
+ children: loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
828
+ /* @__PURE__ */ jsx("div", { style: { width: 12, height: 12, border: "2px solid #e2e8f0", borderTopColor: "#4f8ef7", borderRadius: "50%", animation: "ga-spin .8s linear infinite" } }),
829
+ "Refreshing\u2026"
830
+ ] }) : /* @__PURE__ */ jsx(Fragment, { children: "\u21BB Refresh" })
831
+ }
832
+ ) })
833
+ ] }),
834
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, overflow: "auto", padding: "24px 28px" }, children: [
835
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 22 }, children: [
836
+ /* @__PURE__ */ jsxs("div", { children: [
837
+ /* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: 18, fontWeight: 700, color: "#1e293b" }, children: (_a = TABS.find((t) => t.id === activeTab)) == null ? void 0 : _a.label }),
838
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: "#94a3b8", marginTop: 3 }, children: [
839
+ "Last ",
840
+ dateRange,
841
+ " days"
842
+ ] })
843
+ ] }),
844
+ error && /* @__PURE__ */ jsx("div", { style: { background: "#fffbeb", border: "1px solid #fcd34d", borderRadius: 7, padding: "8px 14px", fontSize: 12, color: "#92400e" }, children: "\u26A0 Showing cached data" })
845
+ ] }),
846
+ activeTab === "overview" && /* @__PURE__ */ jsx(OverviewTab, { data, loading }),
847
+ activeTab === "traffic" && /* @__PURE__ */ jsx(TrafficTab, { data, loading }),
848
+ activeTab === "content" && /* @__PURE__ */ jsx(ContentTab, { data, loading }),
849
+ activeTab === "audience" && /* @__PURE__ */ jsx(AudienceTab, { data, loading }),
850
+ activeTab === "geography" && /* @__PURE__ */ jsx(GeographyTab, { data, loading }),
851
+ activeTab === "events" && /* @__PURE__ */ jsx(EventsTab, { data, loading }),
852
+ activeTab === "acquisition" && /* @__PURE__ */ jsx(AcquisitionTab, { data, loading })
853
+ ] })
854
+ ] })
855
+ ] });
856
+ }
857
+
858
+ // src/google-analytics-tool.tsx
859
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
860
+ function GoogleAnalyticsTool({ apiUrl }) {
861
+ return /* @__PURE__ */ jsx2(Dashboard, { apiUrl: apiUrl || "/api/analytics" });
862
+ }
863
+
864
+ // src/plugin.ts
865
+ var googleAnalyticsPlugin = (config = {}) => definePlugin({
866
+ name: "ga-dashboard",
867
+ tools: (prev) => [
868
+ ...prev,
869
+ {
870
+ name: "ga-dashboard",
871
+ title: "Google Analytics",
872
+ icon: BarChartIcon,
873
+ component: () => GoogleAnalyticsTool(config)
874
+ }
875
+ ]
876
+ })();
877
+ export {
878
+ googleAnalyticsPlugin
879
+ };
880
+ //# sourceMappingURL=index.esm.js.map