@healthya-team/pharmacy-analytics 1.0.0 → 1.0.1
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.
- package/dist/client/index.d.mts +5 -4
- package/dist/client/index.d.ts +5 -4
- package/dist/client/index.js +1 -0
- package/dist/client/index.mjs +2 -0
- package/dist/index.d.mts +217 -3
- package/dist/index.d.ts +217 -3
- package/dist/index.js +0 -9373
- package/dist/index.mjs +0 -9359
- package/dist/server/index.d.mts +7 -5
- package/dist/server/index.d.ts +7 -5
- package/dist/server/index.js +30 -1
- package/dist/server/index.mjs +29 -1
- package/dist/studio/index.d.mts +29 -27
- package/dist/studio/index.d.ts +29 -27
- package/dist/studio/index.js +393 -231
- package/dist/studio/index.mjs +340 -178
- package/package.json +3 -3
package/dist/studio/index.js
CHANGED
|
@@ -70,7 +70,7 @@ module.exports = __toCommonJS(studio_exports);
|
|
|
70
70
|
var import_lucide_react5 = require("lucide-react");
|
|
71
71
|
|
|
72
72
|
// src/studio/components/UnifiedDashboard.tsx
|
|
73
|
-
var
|
|
73
|
+
var import_react12 = require("react");
|
|
74
74
|
var import_lucide_react4 = require("lucide-react");
|
|
75
75
|
|
|
76
76
|
// src/studio/utils-ga-api.ts
|
|
@@ -364,6 +364,43 @@ var fDur = (s) => {
|
|
|
364
364
|
};
|
|
365
365
|
var fPct = (v) => `${(v * 100).toFixed(1)}%`;
|
|
366
366
|
var fEngPct = (v) => `${(v * 100).toFixed(0)}%`;
|
|
367
|
+
var MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
368
|
+
var evenTickInterval = (count, maxTicks = 8) => Math.max(0, Math.ceil(count / maxTicks) - 1);
|
|
369
|
+
var formatDateTick = (v) => {
|
|
370
|
+
if (!v || typeof v !== "string") return "";
|
|
371
|
+
if (v.length === 10 && v[4] === "-" && v[7] === "-") {
|
|
372
|
+
const m = parseInt(v.slice(5, 7), 10) - 1;
|
|
373
|
+
const d = parseInt(v.slice(8, 10), 10);
|
|
374
|
+
return `${d} ${MONTH_NAMES[m] || v.slice(5, 7)}`;
|
|
375
|
+
}
|
|
376
|
+
if (v.length === 8 && !v.includes("-") && !v.includes("/")) {
|
|
377
|
+
const m = parseInt(v.slice(4, 6), 10) - 1;
|
|
378
|
+
const d = parseInt(v.slice(6, 8), 10);
|
|
379
|
+
return `${d} ${MONTH_NAMES[m] || v.slice(4, 6)}`;
|
|
380
|
+
}
|
|
381
|
+
if (v.length === 5 && v[2] === "/") {
|
|
382
|
+
const m = parseInt(v.slice(0, 2), 10) - 1;
|
|
383
|
+
const d = parseInt(v.slice(3, 5), 10);
|
|
384
|
+
return `${d} ${MONTH_NAMES[m] || v.slice(0, 2)}`;
|
|
385
|
+
}
|
|
386
|
+
return v;
|
|
387
|
+
};
|
|
388
|
+
var formatTooltipDate = (v) => {
|
|
389
|
+
if (typeof v !== "string") return typeof v === "number" ? String(v) : "";
|
|
390
|
+
if (v.length === 10 && v[4] === "-" && v[7] === "-") {
|
|
391
|
+
const y = v.slice(0, 4);
|
|
392
|
+
const m = parseInt(v.slice(5, 7), 10) - 1;
|
|
393
|
+
const d = parseInt(v.slice(8, 10), 10);
|
|
394
|
+
return `${d} ${MONTH_NAMES[m] || v.slice(5, 7)} ${y}`;
|
|
395
|
+
}
|
|
396
|
+
if (v.length === 8 && !v.includes("-") && !v.includes("/")) {
|
|
397
|
+
const y = v.slice(0, 4);
|
|
398
|
+
const m = parseInt(v.slice(4, 6), 10) - 1;
|
|
399
|
+
const d = parseInt(v.slice(6, 8), 10);
|
|
400
|
+
return `${d} ${MONTH_NAMES[m] || v.slice(4, 6)} ${y}`;
|
|
401
|
+
}
|
|
402
|
+
return v;
|
|
403
|
+
};
|
|
367
404
|
var GLOBAL_CSS = `
|
|
368
405
|
@keyframes ga-spin { to { transform: rotate(360deg); } }
|
|
369
406
|
@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)} }
|
|
@@ -376,13 +413,14 @@ var GLOBAL_CSS = `
|
|
|
376
413
|
.ga-tooltip-wrap { position:relative; display:inline-flex; align-items:center; }
|
|
377
414
|
.ga-tooltip-wrap .ga-tip {
|
|
378
415
|
visibility:hidden; opacity:0; pointer-events:none;
|
|
379
|
-
position:absolute; bottom:calc(100% + 6px);
|
|
416
|
+
position:absolute; bottom:calc(100% + 6px); right:-6px;
|
|
380
417
|
background:#1e293b; color:#f1f5f9; font-size:12px; line-height:1.5;
|
|
381
418
|
padding:8px 12px; border-radius:7px; white-space:normal; width:220px;
|
|
419
|
+
max-width:calc(100vw - 32px);
|
|
382
420
|
box-shadow:0 4px 16px rgba(0,0,0,.18); z-index:9999; transition:opacity .15s;
|
|
383
421
|
}
|
|
384
422
|
.ga-tooltip-wrap .ga-tip::after {
|
|
385
|
-
content:''; position:absolute; top:100%;
|
|
423
|
+
content:''; position:absolute; top:100%; right:10px;
|
|
386
424
|
border:5px solid transparent; border-top-color:#1e293b;
|
|
387
425
|
}
|
|
388
426
|
.ga-tooltip-wrap:hover .ga-tip { visibility:visible; opacity:1; }
|
|
@@ -400,13 +438,84 @@ var GLOBAL_CSS = `
|
|
|
400
438
|
`;
|
|
401
439
|
|
|
402
440
|
// src/studio/components/shared-ui.tsx
|
|
441
|
+
var import_react = require("react");
|
|
442
|
+
var import_react_dom = require("react-dom");
|
|
403
443
|
var import_recharts = require("recharts");
|
|
404
444
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
405
445
|
function InfoIcon({ text }) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
446
|
+
const triggerRef = (0, import_react.useRef)(null);
|
|
447
|
+
const [tip, setTip] = (0, import_react.useState)(null);
|
|
448
|
+
const show = () => {
|
|
449
|
+
const el = triggerRef.current;
|
|
450
|
+
if (!el || typeof window === "undefined") return;
|
|
451
|
+
const rect = el.getBoundingClientRect();
|
|
452
|
+
const placeBelow = rect.top < 100;
|
|
453
|
+
const tipWidth = 230;
|
|
454
|
+
const pad = 12;
|
|
455
|
+
const centerX = rect.left + rect.width / 2;
|
|
456
|
+
const clampedLeft = Math.max(
|
|
457
|
+
tipWidth / 2 + pad,
|
|
458
|
+
Math.min(centerX, window.innerWidth - tipWidth / 2 - pad)
|
|
459
|
+
);
|
|
460
|
+
setTip({
|
|
461
|
+
top: placeBelow ? rect.bottom + 8 : rect.top - 8,
|
|
462
|
+
left: clampedLeft,
|
|
463
|
+
placement: placeBelow ? "bottom" : "top"
|
|
464
|
+
});
|
|
465
|
+
};
|
|
466
|
+
const hide = () => setTip(null);
|
|
467
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
468
|
+
"span",
|
|
469
|
+
{
|
|
470
|
+
className: "ga-tooltip-wrap",
|
|
471
|
+
onMouseEnter: show,
|
|
472
|
+
onMouseLeave: hide,
|
|
473
|
+
style: { position: "relative", display: "inline-flex", alignItems: "center" },
|
|
474
|
+
children: [
|
|
475
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
476
|
+
"button",
|
|
477
|
+
{
|
|
478
|
+
ref: triggerRef,
|
|
479
|
+
className: "ga-info-btn",
|
|
480
|
+
tabIndex: -1,
|
|
481
|
+
type: "button",
|
|
482
|
+
onFocus: show,
|
|
483
|
+
onBlur: hide,
|
|
484
|
+
children: "i"
|
|
485
|
+
}
|
|
486
|
+
),
|
|
487
|
+
tip && typeof document !== "undefined" && (0, import_react_dom.createPortal)(
|
|
488
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
489
|
+
"div",
|
|
490
|
+
{
|
|
491
|
+
style: {
|
|
492
|
+
position: "fixed",
|
|
493
|
+
top: tip.top,
|
|
494
|
+
left: tip.left,
|
|
495
|
+
transform: tip.placement === "top" ? "translate(-50%, -100%)" : "translate(-50%, 0)",
|
|
496
|
+
width: 230,
|
|
497
|
+
background: "#0f172a",
|
|
498
|
+
color: "#f8fafc",
|
|
499
|
+
fontSize: 12,
|
|
500
|
+
lineHeight: 1.45,
|
|
501
|
+
padding: "8px 12px",
|
|
502
|
+
borderRadius: 8,
|
|
503
|
+
boxShadow: "0 10px 25px -5px rgba(0,0,0,0.3), 0 8px 10px -6px rgba(0,0,0,0.2)",
|
|
504
|
+
border: "1px solid #334155",
|
|
505
|
+
pointerEvents: "none",
|
|
506
|
+
zIndex: 99999,
|
|
507
|
+
wordWrap: "break-word",
|
|
508
|
+
whiteSpace: "normal",
|
|
509
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
|
|
510
|
+
},
|
|
511
|
+
children: text
|
|
512
|
+
}
|
|
513
|
+
),
|
|
514
|
+
document.body
|
|
515
|
+
)
|
|
516
|
+
]
|
|
517
|
+
}
|
|
518
|
+
);
|
|
410
519
|
}
|
|
411
520
|
function Skel({
|
|
412
521
|
w,
|
|
@@ -811,11 +920,11 @@ function OverviewTab({ data, loading }) {
|
|
|
811
920
|
}
|
|
812
921
|
|
|
813
922
|
// src/studio/tabs/traffic-tab.tsx
|
|
814
|
-
var
|
|
923
|
+
var import_react2 = require("react");
|
|
815
924
|
var import_recharts2 = require("recharts");
|
|
816
925
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
817
926
|
function TrafficTab({ data, loading }) {
|
|
818
|
-
const channelBar = (0,
|
|
927
|
+
const channelBar = (0, import_react2.useMemo)(
|
|
819
928
|
() => data.channels.map((c) => ({
|
|
820
929
|
name: c.channel,
|
|
821
930
|
Sessions: c.sessions,
|
|
@@ -841,7 +950,11 @@ function TrafficTab({ data, loading }) {
|
|
|
841
950
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
842
951
|
import_recharts2.XAxis,
|
|
843
952
|
{
|
|
844
|
-
dataKey: "
|
|
953
|
+
dataKey: "date",
|
|
954
|
+
tickFormatter: formatDateTick,
|
|
955
|
+
minTickGap: 28,
|
|
956
|
+
interval: evenTickInterval(data.timeSeries?.length ?? 0),
|
|
957
|
+
padding: { left: 2, right: 15 },
|
|
845
958
|
fontSize: 11,
|
|
846
959
|
tick: { fill: "#94a3b8" },
|
|
847
960
|
axisLine: false,
|
|
@@ -861,6 +974,7 @@ function TrafficTab({ data, loading }) {
|
|
|
861
974
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
862
975
|
import_recharts2.Tooltip,
|
|
863
976
|
{
|
|
977
|
+
labelFormatter: formatTooltipDate,
|
|
864
978
|
contentStyle: {
|
|
865
979
|
background: "#fff",
|
|
866
980
|
border: "1px solid #e2e8f0",
|
|
@@ -1157,7 +1271,7 @@ function GeographyTab({ data, loading }) {
|
|
|
1157
1271
|
}
|
|
1158
1272
|
|
|
1159
1273
|
// src/studio/tabs/events-tab.tsx
|
|
1160
|
-
var
|
|
1274
|
+
var import_react3 = require("react");
|
|
1161
1275
|
|
|
1162
1276
|
// src/studio/sanity-client-shim.ts
|
|
1163
1277
|
var projectId = typeof process !== "undefined" ? process.env.SANITY_STUDIO_PROJECT_ID || process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || "" : "";
|
|
@@ -1190,16 +1304,16 @@ function EventsTab({
|
|
|
1190
1304
|
apiUrl,
|
|
1191
1305
|
dateRange
|
|
1192
1306
|
}) {
|
|
1193
|
-
const [selectedEvent, setSelectedEvent] = (0,
|
|
1194
|
-
const [paramRows, setParamRows] = (0,
|
|
1195
|
-
const [paramsLoading, setParamsLoading] = (0,
|
|
1196
|
-
const [paramsError, setParamsError] = (0,
|
|
1197
|
-
const [conditionValues, setConditionValues] = (0,
|
|
1198
|
-
const [selectedCondition, setSelectedCondition] = (0,
|
|
1199
|
-
const [filteredEvents, setFilteredEvents] = (0,
|
|
1200
|
-
const [filterLoading, setFilterLoading] = (0,
|
|
1201
|
-
const [pharmacyNameMap, setPharmacyNameMap] = (0,
|
|
1202
|
-
(0,
|
|
1307
|
+
const [selectedEvent, setSelectedEvent] = (0, import_react3.useState)(null);
|
|
1308
|
+
const [paramRows, setParamRows] = (0, import_react3.useState)([]);
|
|
1309
|
+
const [paramsLoading, setParamsLoading] = (0, import_react3.useState)(false);
|
|
1310
|
+
const [paramsError, setParamsError] = (0, import_react3.useState)(null);
|
|
1311
|
+
const [conditionValues, setConditionValues] = (0, import_react3.useState)([]);
|
|
1312
|
+
const [selectedCondition, setSelectedCondition] = (0, import_react3.useState)(null);
|
|
1313
|
+
const [filteredEvents, setFilteredEvents] = (0, import_react3.useState)(null);
|
|
1314
|
+
const [filterLoading, setFilterLoading] = (0, import_react3.useState)(false);
|
|
1315
|
+
const [pharmacyNameMap, setPharmacyNameMap] = (0, import_react3.useState)({});
|
|
1316
|
+
(0, import_react3.useEffect)(() => {
|
|
1203
1317
|
sanityClient.fetch(`*[_type == "pharmacies"]{corporateId, name}`).then((pharmacies) => {
|
|
1204
1318
|
const map = {};
|
|
1205
1319
|
for (const p of pharmacies) {
|
|
@@ -1208,7 +1322,7 @@ function EventsTab({
|
|
|
1208
1322
|
setPharmacyNameMap(map);
|
|
1209
1323
|
}).catch((err) => console.error("[EventsTab] fetchPharmacies error:", err));
|
|
1210
1324
|
}, []);
|
|
1211
|
-
(0,
|
|
1325
|
+
(0, import_react3.useEffect)(() => {
|
|
1212
1326
|
let cancelled = false;
|
|
1213
1327
|
fetchConditionValues(apiUrl, dateRange).then((values) => {
|
|
1214
1328
|
if (!cancelled) setConditionValues(values);
|
|
@@ -1217,7 +1331,7 @@ function EventsTab({
|
|
|
1217
1331
|
cancelled = true;
|
|
1218
1332
|
};
|
|
1219
1333
|
}, [apiUrl, dateRange]);
|
|
1220
|
-
(0,
|
|
1334
|
+
(0, import_react3.useEffect)(() => {
|
|
1221
1335
|
if (!selectedCondition) {
|
|
1222
1336
|
setFilteredEvents(null);
|
|
1223
1337
|
return;
|
|
@@ -1236,7 +1350,7 @@ function EventsTab({
|
|
|
1236
1350
|
cancelled = true;
|
|
1237
1351
|
};
|
|
1238
1352
|
}, [selectedCondition, apiUrl, dateRange]);
|
|
1239
|
-
(0,
|
|
1353
|
+
(0, import_react3.useEffect)(() => {
|
|
1240
1354
|
if (!selectedEvent) {
|
|
1241
1355
|
setParamRows([]);
|
|
1242
1356
|
return;
|
|
@@ -1256,7 +1370,7 @@ function EventsTab({
|
|
|
1256
1370
|
cancelled = true;
|
|
1257
1371
|
};
|
|
1258
1372
|
}, [selectedEvent, apiUrl, dateRange]);
|
|
1259
|
-
const displayEvents = (0,
|
|
1373
|
+
const displayEvents = (0, import_react3.useMemo)(
|
|
1260
1374
|
() => filteredEvents ?? data.topEvents,
|
|
1261
1375
|
[filteredEvents, data.topEvents]
|
|
1262
1376
|
);
|
|
@@ -1442,7 +1556,7 @@ function AcquisitionTab({ data, loading }) {
|
|
|
1442
1556
|
}
|
|
1443
1557
|
|
|
1444
1558
|
// src/studio/tabs/conditions-products-tab.tsx
|
|
1445
|
-
var
|
|
1559
|
+
var import_react4 = require("react");
|
|
1446
1560
|
var import_recharts3 = require("recharts");
|
|
1447
1561
|
|
|
1448
1562
|
// src/studio/utils-mixpanel-api.ts
|
|
@@ -1551,18 +1665,30 @@ async function fetchPmProductCart(apiUrl, dateRange, conditionId, product, signa
|
|
|
1551
1665
|
}
|
|
1552
1666
|
return res.json();
|
|
1553
1667
|
}
|
|
1668
|
+
async function parseJsonResponse(res, label, url) {
|
|
1669
|
+
const text = await res.text();
|
|
1670
|
+
if (!res.ok) {
|
|
1671
|
+
let msg = `${label} API error: ${res.status} ${res.statusText}`;
|
|
1672
|
+
try {
|
|
1673
|
+
const err = JSON.parse(text);
|
|
1674
|
+
if (err?.error) msg = err.error;
|
|
1675
|
+
} catch {
|
|
1676
|
+
}
|
|
1677
|
+
throw new Error(msg);
|
|
1678
|
+
}
|
|
1679
|
+
if (/^\s*<(!doctype|html)/i.test(text)) {
|
|
1680
|
+
throw new Error(
|
|
1681
|
+
`${label} API (${url}) returned HTML instead of JSON. Check the Mixpanel API URL (mixpanelApiUrl) points at the /api/mixpanel route.`
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
return JSON.parse(text);
|
|
1685
|
+
}
|
|
1554
1686
|
async function fetchTopEntities(apiUrl, dateRange, opts, signal) {
|
|
1555
1687
|
let url = `${apiUrl}?mode=top-entities&dateRange=${dateRange}`;
|
|
1556
1688
|
if (opts?.condition) url += `&condition=${encodeURIComponent(opts.condition)}`;
|
|
1557
1689
|
if (opts?.product) url += `&product=${encodeURIComponent(opts.product)}`;
|
|
1558
1690
|
const res = await fetch(url, { cache: "no-store", signal });
|
|
1559
|
-
|
|
1560
|
-
const err = await res.json().catch(() => null);
|
|
1561
|
-
throw new Error(
|
|
1562
|
-
err?.error || `Top entities API error: ${res.status} ${res.statusText}`
|
|
1563
|
-
);
|
|
1564
|
-
}
|
|
1565
|
-
const json = await res.json();
|
|
1691
|
+
const json = await parseJsonResponse(res, "Top entities", url);
|
|
1566
1692
|
return {
|
|
1567
1693
|
conditions: json.conditions ?? [],
|
|
1568
1694
|
products: json.products ?? [],
|
|
@@ -1573,13 +1699,7 @@ async function fetchTopEntities(apiUrl, dateRange, opts, signal) {
|
|
|
1573
1699
|
async function fetchTopSearches(apiUrl, dateRange, type, signal) {
|
|
1574
1700
|
const url = `${apiUrl}?mode=top-searches&dateRange=${dateRange}&type=${type}`;
|
|
1575
1701
|
const res = await fetch(url, { cache: "no-store", signal });
|
|
1576
|
-
|
|
1577
|
-
const err = await res.json().catch(() => null);
|
|
1578
|
-
throw new Error(
|
|
1579
|
-
err?.error || `Top searches API error: ${res.status} ${res.statusText}`
|
|
1580
|
-
);
|
|
1581
|
-
}
|
|
1582
|
-
const json = await res.json();
|
|
1702
|
+
const json = await parseJsonResponse(res, "Top searches", url);
|
|
1583
1703
|
return { topSearches: json.topSearches ?? [] };
|
|
1584
1704
|
}
|
|
1585
1705
|
async function clearMixpanelCache(apiUrl) {
|
|
@@ -1735,22 +1855,23 @@ function EmptyState({ label }) {
|
|
|
1735
1855
|
}
|
|
1736
1856
|
);
|
|
1737
1857
|
}
|
|
1738
|
-
function ConditionsProductsTab({ dateRange }) {
|
|
1739
|
-
const
|
|
1740
|
-
const [
|
|
1741
|
-
const [
|
|
1742
|
-
const [
|
|
1743
|
-
const [
|
|
1744
|
-
const [
|
|
1745
|
-
const [
|
|
1746
|
-
const [
|
|
1747
|
-
const [
|
|
1748
|
-
const [
|
|
1749
|
-
const [
|
|
1750
|
-
(0,
|
|
1858
|
+
function ConditionsProductsTab({ apiUrl, dateRange }) {
|
|
1859
|
+
const targetUrl = apiUrl || MIXPANEL_API_URL;
|
|
1860
|
+
const [subTab, setSubTab] = (0, import_react4.useState)("conditions");
|
|
1861
|
+
const [data, setData] = (0, import_react4.useState)(null);
|
|
1862
|
+
const [loading, setLoading] = (0, import_react4.useState)(true);
|
|
1863
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
1864
|
+
const [fetchTick] = (0, import_react4.useState)(0);
|
|
1865
|
+
const [topConditionSearches, setTopConditionSearches] = (0, import_react4.useState)(null);
|
|
1866
|
+
const [topConditionSearchesLoading, setTopConditionSearchesLoading] = (0, import_react4.useState)(true);
|
|
1867
|
+
const [conditionSearchVisible, setConditionSearchVisible] = (0, import_react4.useState)(20);
|
|
1868
|
+
const [topProductSearches, setTopProductSearches] = (0, import_react4.useState)(null);
|
|
1869
|
+
const [topProductSearchesLoading, setTopProductSearchesLoading] = (0, import_react4.useState)(true);
|
|
1870
|
+
const [productSearchVisible, setProductSearchVisible] = (0, import_react4.useState)(20);
|
|
1871
|
+
(0, import_react4.useEffect)(() => {
|
|
1751
1872
|
const controller = new AbortController();
|
|
1752
1873
|
setTopConditionSearchesLoading(true);
|
|
1753
|
-
fetchTopSearches(
|
|
1874
|
+
fetchTopSearches(targetUrl, dateRange, "condition", controller.signal).then((res) => {
|
|
1754
1875
|
if (!controller.signal.aborted) setTopConditionSearches(res);
|
|
1755
1876
|
}).catch((err) => {
|
|
1756
1877
|
if (err instanceof Error && err.name === "AbortError") return;
|
|
@@ -1758,11 +1879,11 @@ function ConditionsProductsTab({ dateRange }) {
|
|
|
1758
1879
|
if (!controller.signal.aborted) setTopConditionSearchesLoading(false);
|
|
1759
1880
|
});
|
|
1760
1881
|
return () => controller.abort();
|
|
1761
|
-
}, [dateRange, fetchTick]);
|
|
1762
|
-
(0,
|
|
1882
|
+
}, [targetUrl, dateRange, fetchTick]);
|
|
1883
|
+
(0, import_react4.useEffect)(() => {
|
|
1763
1884
|
const controller = new AbortController();
|
|
1764
1885
|
setTopProductSearchesLoading(true);
|
|
1765
|
-
fetchTopSearches(
|
|
1886
|
+
fetchTopSearches(targetUrl, dateRange, "product", controller.signal).then((res) => {
|
|
1766
1887
|
if (!controller.signal.aborted) setTopProductSearches(res);
|
|
1767
1888
|
}).catch((err) => {
|
|
1768
1889
|
if (err instanceof Error && err.name === "AbortError") return;
|
|
@@ -1770,12 +1891,12 @@ function ConditionsProductsTab({ dateRange }) {
|
|
|
1770
1891
|
if (!controller.signal.aborted) setTopProductSearchesLoading(false);
|
|
1771
1892
|
});
|
|
1772
1893
|
return () => controller.abort();
|
|
1773
|
-
}, [dateRange, fetchTick]);
|
|
1774
|
-
(0,
|
|
1894
|
+
}, [targetUrl, dateRange, fetchTick]);
|
|
1895
|
+
(0, import_react4.useEffect)(() => {
|
|
1775
1896
|
const controller = new AbortController();
|
|
1776
1897
|
setLoading(true);
|
|
1777
1898
|
setError(null);
|
|
1778
|
-
fetchTopEntities(
|
|
1899
|
+
fetchTopEntities(targetUrl, dateRange, void 0, controller.signal).then((res) => {
|
|
1779
1900
|
if (!controller.signal.aborted) setData(res);
|
|
1780
1901
|
}).catch((err) => {
|
|
1781
1902
|
if (err instanceof Error && err.name === "AbortError") return;
|
|
@@ -1784,12 +1905,12 @@ function ConditionsProductsTab({ dateRange }) {
|
|
|
1784
1905
|
if (!controller.signal.aborted) setLoading(false);
|
|
1785
1906
|
});
|
|
1786
1907
|
return () => controller.abort();
|
|
1787
|
-
}, [dateRange, fetchTick]);
|
|
1788
|
-
const conditionBarData = (0,
|
|
1908
|
+
}, [targetUrl, dateRange, fetchTick]);
|
|
1909
|
+
const conditionBarData = (0, import_react4.useMemo)(
|
|
1789
1910
|
() => [...data?.conditions ?? []].sort((a, b) => b.condition_viewed - a.condition_viewed).filter((c) => c.condition_viewed > 0).slice(0, 12).map((c) => ({ fullName: c.name, name: shortLabel(c.name), Views: c.condition_viewed })),
|
|
1790
1911
|
[data]
|
|
1791
1912
|
);
|
|
1792
|
-
const pieData = (0,
|
|
1913
|
+
const pieData = (0, import_react4.useMemo)(() => {
|
|
1793
1914
|
const sb = data?.serviceBreakdown ?? { nhs: 0, private_service: 0, private_medication: 0 };
|
|
1794
1915
|
return [
|
|
1795
1916
|
{ name: "NHS", value: sb.nhs, color: SERVICE_COLORS["NHS"] },
|
|
@@ -1797,7 +1918,7 @@ function ConditionsProductsTab({ dateRange }) {
|
|
|
1797
1918
|
{ name: "Private Medication", value: sb.private_medication, color: SERVICE_COLORS["Private Medication"] }
|
|
1798
1919
|
].filter((e) => e.value > 0);
|
|
1799
1920
|
}, [data]);
|
|
1800
|
-
const productBarData = (0,
|
|
1921
|
+
const productBarData = (0, import_react4.useMemo)(
|
|
1801
1922
|
() => [...data?.products ?? []].sort((a, b) => b.product_viewed - a.product_viewed).filter((p) => p.product_viewed > 0).slice(0, 12).map((p) => ({ fullName: p.name, name: shortLabel(p.name), Views: p.product_viewed })),
|
|
1802
1923
|
[data]
|
|
1803
1924
|
);
|
|
@@ -1830,6 +1951,7 @@ function ConditionsProductsTab({ dateRange }) {
|
|
|
1830
1951
|
border: "1px solid #e2e8f0",
|
|
1831
1952
|
boxShadow: "0 4px 16px rgba(0,0,0,.08)"
|
|
1832
1953
|
};
|
|
1954
|
+
const barTooltipLabelStyle = { color: "#0f172a", fontWeight: 600 };
|
|
1833
1955
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { animation: "ga-fadein .25s ease", display: "flex", flexDirection: "column", gap: 20 }, children: [
|
|
1834
1956
|
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1835
1957
|
"div",
|
|
@@ -1941,6 +2063,7 @@ function ConditionsProductsTab({ dateRange }) {
|
|
|
1941
2063
|
formatter: (v) => [fNum(Number(v ?? 0)), "Views"],
|
|
1942
2064
|
labelFormatter: (label, payload) => payload?.[0]?.payload?.fullName ?? label,
|
|
1943
2065
|
contentStyle: barTooltipStyle,
|
|
2066
|
+
labelStyle: barTooltipLabelStyle,
|
|
1944
2067
|
cursor: { fill: "rgba(99,102,241,0.06)" }
|
|
1945
2068
|
}
|
|
1946
2069
|
),
|
|
@@ -2197,6 +2320,7 @@ function ConditionsProductsTab({ dateRange }) {
|
|
|
2197
2320
|
formatter: (v) => [fNum(Number(v ?? 0)), "Views"],
|
|
2198
2321
|
labelFormatter: (label, payload) => payload?.[0]?.payload?.fullName ?? label,
|
|
2199
2322
|
contentStyle: barTooltipStyle,
|
|
2323
|
+
labelStyle: barTooltipLabelStyle,
|
|
2200
2324
|
cursor: { fill: "rgba(99,102,241,0.06)" }
|
|
2201
2325
|
}
|
|
2202
2326
|
),
|
|
@@ -2250,10 +2374,10 @@ function ConditionsProductsTab({ dateRange }) {
|
|
|
2250
2374
|
}
|
|
2251
2375
|
|
|
2252
2376
|
// src/studio/funnel/funnel-events-tab.tsx
|
|
2253
|
-
var
|
|
2377
|
+
var import_react8 = require("react");
|
|
2254
2378
|
|
|
2255
2379
|
// src/studio/funnel/custom-select.tsx
|
|
2256
|
-
var
|
|
2380
|
+
var import_react5 = require("react");
|
|
2257
2381
|
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2258
2382
|
function CustomSelect({
|
|
2259
2383
|
label,
|
|
@@ -2264,16 +2388,16 @@ function CustomSelect({
|
|
|
2264
2388
|
loading,
|
|
2265
2389
|
onChange
|
|
2266
2390
|
}) {
|
|
2267
|
-
const [open, setOpen] = (0,
|
|
2268
|
-
const [search, setSearch] = (0,
|
|
2269
|
-
const ref = (0,
|
|
2270
|
-
const searchRef = (0,
|
|
2271
|
-
const selected = (0,
|
|
2272
|
-
const filtered = (0,
|
|
2391
|
+
const [open, setOpen] = (0, import_react5.useState)(false);
|
|
2392
|
+
const [search, setSearch] = (0, import_react5.useState)("");
|
|
2393
|
+
const ref = (0, import_react5.useRef)(null);
|
|
2394
|
+
const searchRef = (0, import_react5.useRef)(null);
|
|
2395
|
+
const selected = (0, import_react5.useMemo)(() => options.find((o) => o.value === value), [options, value]);
|
|
2396
|
+
const filtered = (0, import_react5.useMemo)(
|
|
2273
2397
|
() => search.trim() ? options.filter((o) => o.label.toLowerCase().includes(search.toLowerCase())) : options,
|
|
2274
2398
|
[options, search]
|
|
2275
2399
|
);
|
|
2276
|
-
(0,
|
|
2400
|
+
(0, import_react5.useEffect)(() => {
|
|
2277
2401
|
if (!open) return;
|
|
2278
2402
|
function handler(e) {
|
|
2279
2403
|
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
@@ -2281,11 +2405,11 @@ function CustomSelect({
|
|
|
2281
2405
|
document.addEventListener("mousedown", handler);
|
|
2282
2406
|
return () => document.removeEventListener("mousedown", handler);
|
|
2283
2407
|
}, [open]);
|
|
2284
|
-
(0,
|
|
2408
|
+
(0, import_react5.useEffect)(() => {
|
|
2285
2409
|
if (open) setTimeout(() => searchRef.current?.focus(), 40);
|
|
2286
2410
|
else setSearch("");
|
|
2287
2411
|
}, [open]);
|
|
2288
|
-
const handleSelect = (0,
|
|
2412
|
+
const handleSelect = (0, import_react5.useCallback)(
|
|
2289
2413
|
(val) => {
|
|
2290
2414
|
onChange(val);
|
|
2291
2415
|
setOpen(false);
|
|
@@ -2510,9 +2634,9 @@ function CustomSelect({
|
|
|
2510
2634
|
}
|
|
2511
2635
|
|
|
2512
2636
|
// src/studio/funnel/patient-modal.tsx
|
|
2513
|
-
var
|
|
2637
|
+
var import_react6 = __toESM(require("react"));
|
|
2514
2638
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2515
|
-
var PatientModal =
|
|
2639
|
+
var PatientModal = import_react6.default.memo(function PatientModal2({
|
|
2516
2640
|
open,
|
|
2517
2641
|
onClose,
|
|
2518
2642
|
loading,
|
|
@@ -2523,7 +2647,7 @@ var PatientModal = import_react5.default.memo(function PatientModal2({
|
|
|
2523
2647
|
pharmacyNameMap,
|
|
2524
2648
|
onExport
|
|
2525
2649
|
}) {
|
|
2526
|
-
const formattedRecords = (0,
|
|
2650
|
+
const formattedRecords = (0, import_react6.useMemo)(
|
|
2527
2651
|
() => records.map((p) => ({
|
|
2528
2652
|
...p,
|
|
2529
2653
|
_date: p.timestamp ? new Date(p.timestamp * 1e3).toLocaleDateString("en-GB", {
|
|
@@ -2839,7 +2963,7 @@ var PatientModal = import_react5.default.memo(function PatientModal2({
|
|
|
2839
2963
|
});
|
|
2840
2964
|
|
|
2841
2965
|
// src/studio/funnel/funnel-card.tsx
|
|
2842
|
-
var
|
|
2966
|
+
var import_react7 = require("react");
|
|
2843
2967
|
|
|
2844
2968
|
// src/studio/funnel/card-sections.tsx
|
|
2845
2969
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
@@ -3413,18 +3537,18 @@ function FunnelCard({
|
|
|
3413
3537
|
}) {
|
|
3414
3538
|
const color = FUNNEL_EVENT_COLOR[r.event] ?? "#888";
|
|
3415
3539
|
const isQfs = r.event === "questionnaire_flow_step";
|
|
3416
|
-
const breakdown = (0,
|
|
3540
|
+
const breakdown = (0, import_react7.useMemo)(
|
|
3417
3541
|
() => !isQfs && r.event !== "condition_viewed" ? paramBreakdowns[r.event] : void 0,
|
|
3418
3542
|
[isQfs, paramBreakdowns, r.event]
|
|
3419
3543
|
);
|
|
3420
|
-
const filteredBreakdownEntries = (0,
|
|
3544
|
+
const filteredBreakdownEntries = (0, import_react7.useMemo)(
|
|
3421
3545
|
() => breakdown ? Object.entries(breakdown).filter(
|
|
3422
3546
|
([pk]) => pk !== "condition_slug" && pk !== "date_of_birth" && pk !== "is_pre_consult" && !(r.event === "start_assessment_clicked" && pk === "is_nhs")
|
|
3423
3547
|
) : [],
|
|
3424
3548
|
[breakdown, r.event]
|
|
3425
3549
|
);
|
|
3426
3550
|
const hasBreakdown = filteredBreakdownEntries.length > 0;
|
|
3427
|
-
const visibleFlows = (0,
|
|
3551
|
+
const visibleFlows = (0, import_react7.useMemo)(
|
|
3428
3552
|
() => isQfs ? qfsFlows.filter((f) => f.steps.some((s) => s.count > 0)) : [],
|
|
3429
3553
|
[isQfs, qfsFlows]
|
|
3430
3554
|
);
|
|
@@ -3638,28 +3762,29 @@ function FunnelCard({
|
|
|
3638
3762
|
|
|
3639
3763
|
// src/studio/funnel/funnel-events-tab.tsx
|
|
3640
3764
|
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
3641
|
-
function FunnelEventsTab({ dateRange }) {
|
|
3642
|
-
const
|
|
3643
|
-
const [
|
|
3644
|
-
const [
|
|
3645
|
-
const [
|
|
3646
|
-
const [
|
|
3647
|
-
const [
|
|
3648
|
-
const [
|
|
3649
|
-
const [
|
|
3650
|
-
const [
|
|
3651
|
-
const [
|
|
3652
|
-
const [
|
|
3653
|
-
const [
|
|
3654
|
-
const [
|
|
3655
|
-
const [
|
|
3656
|
-
const [
|
|
3657
|
-
const [
|
|
3658
|
-
const
|
|
3765
|
+
function FunnelEventsTab({ apiUrl, dateRange }) {
|
|
3766
|
+
const targetUrl = apiUrl || MIXPANEL_API_URL;
|
|
3767
|
+
const [loading, setLoading] = (0, import_react8.useState)(false);
|
|
3768
|
+
const [error, setError] = (0, import_react8.useState)(null);
|
|
3769
|
+
const [refreshKey, setRefreshKey] = (0, import_react8.useState)(0);
|
|
3770
|
+
const [clearing, setClearing] = (0, import_react8.useState)(false);
|
|
3771
|
+
const [conditionOptions, setConditionOptions] = (0, import_react8.useState)([]);
|
|
3772
|
+
const [selectedCondition, setSelectedCondition] = (0, import_react8.useState)("");
|
|
3773
|
+
const [conditionsLoading, setConditionsLoading] = (0, import_react8.useState)(true);
|
|
3774
|
+
const [rows, setRows] = (0, import_react8.useState)([]);
|
|
3775
|
+
const [qfsFlows, setQfsFlows] = (0, import_react8.useState)([]);
|
|
3776
|
+
const [paramBreakdowns, setParamBreakdowns] = (0, import_react8.useState)({});
|
|
3777
|
+
const [qfsStepBreakdowns, setQfsStepBreakdowns] = (0, import_react8.useState)({});
|
|
3778
|
+
const [conditionServiceMap, setConditionServiceMap] = (0, import_react8.useState)({});
|
|
3779
|
+
const [patientModalOpen, setPatientModalOpen] = (0, import_react8.useState)(false);
|
|
3780
|
+
const [patientRecords, setPatientRecords] = (0, import_react8.useState)([]);
|
|
3781
|
+
const [patientsLoading, setPatientsLoading] = (0, import_react8.useState)(false);
|
|
3782
|
+
const [patientsError, setPatientsError] = (0, import_react8.useState)(null);
|
|
3783
|
+
const doFetchPatients = (0, import_react8.useCallback)(() => {
|
|
3659
3784
|
if (!selectedCondition) return;
|
|
3660
3785
|
setPatientsLoading(true);
|
|
3661
3786
|
setPatientsError(null);
|
|
3662
|
-
fetchSignupPatients(
|
|
3787
|
+
fetchSignupPatients(targetUrl, dateRange, selectedCondition).then((records) => {
|
|
3663
3788
|
setPatientRecords(records);
|
|
3664
3789
|
setPatientsLoading(false);
|
|
3665
3790
|
}).catch((err) => {
|
|
@@ -3667,18 +3792,18 @@ function FunnelEventsTab({ dateRange }) {
|
|
|
3667
3792
|
setPatientsError(err instanceof Error ? err.message : String(err));
|
|
3668
3793
|
setPatientsLoading(false);
|
|
3669
3794
|
});
|
|
3670
|
-
}, [dateRange, selectedCondition]);
|
|
3671
|
-
const handleSignupCountClick = (0,
|
|
3795
|
+
}, [targetUrl, dateRange, selectedCondition]);
|
|
3796
|
+
const handleSignupCountClick = (0, import_react8.useCallback)(() => {
|
|
3672
3797
|
if (!selectedCondition) return;
|
|
3673
3798
|
setPatientModalOpen(true);
|
|
3674
3799
|
doFetchPatients();
|
|
3675
3800
|
}, [selectedCondition, doFetchPatients]);
|
|
3676
|
-
(0,
|
|
3801
|
+
(0, import_react8.useEffect)(() => {
|
|
3677
3802
|
if (patientModalOpen && refreshKey > 0) {
|
|
3678
3803
|
doFetchPatients();
|
|
3679
3804
|
}
|
|
3680
3805
|
}, [refreshKey]);
|
|
3681
|
-
const exportPatientsCSV = (0,
|
|
3806
|
+
const exportPatientsCSV = (0, import_react8.useCallback)(() => {
|
|
3682
3807
|
if (!patientRecords.length) return;
|
|
3683
3808
|
const headers = [
|
|
3684
3809
|
"Date",
|
|
@@ -3721,11 +3846,11 @@ function FunnelEventsTab({ dateRange }) {
|
|
|
3721
3846
|
a.click();
|
|
3722
3847
|
URL.revokeObjectURL(url);
|
|
3723
3848
|
}, [patientRecords, selectedCondition]);
|
|
3724
|
-
(0,
|
|
3849
|
+
(0, import_react8.useEffect)(() => {
|
|
3725
3850
|
const controller = new AbortController();
|
|
3726
3851
|
setConditionsLoading(true);
|
|
3727
3852
|
setSelectedCondition("");
|
|
3728
|
-
fetchFunnelConditions(
|
|
3853
|
+
fetchFunnelConditions(targetUrl, dateRange, void 0, true, controller.signal).then((values) => {
|
|
3729
3854
|
setConditionOptions(values);
|
|
3730
3855
|
setConditionsLoading(false);
|
|
3731
3856
|
}).catch((err) => {
|
|
@@ -3744,8 +3869,8 @@ function FunnelEventsTab({ dateRange }) {
|
|
|
3744
3869
|
setConditionServiceMap(map);
|
|
3745
3870
|
}).catch((err) => console.error("[FunnelEventsTab] fetchConditionServices error:", err));
|
|
3746
3871
|
return () => controller.abort();
|
|
3747
|
-
}, [dateRange, refreshKey]);
|
|
3748
|
-
(0,
|
|
3872
|
+
}, [targetUrl, dateRange, refreshKey]);
|
|
3873
|
+
(0, import_react8.useEffect)(() => {
|
|
3749
3874
|
if (!selectedCondition) {
|
|
3750
3875
|
setRows([]);
|
|
3751
3876
|
setQfsFlows([]);
|
|
@@ -3757,7 +3882,7 @@ function FunnelEventsTab({ dateRange }) {
|
|
|
3757
3882
|
const controller = new AbortController();
|
|
3758
3883
|
setLoading(true);
|
|
3759
3884
|
setError(null);
|
|
3760
|
-
fetchFunnelEvents(
|
|
3885
|
+
fetchFunnelEvents(targetUrl, dateRange, selectedCondition, void 0, controller.signal).then((data) => {
|
|
3761
3886
|
const { eventCounts, qfsStepsByFlow, paramBreakdowns: bd, qfsStepBreakdowns: qfsStepBd } = data;
|
|
3762
3887
|
setParamBreakdowns(bd);
|
|
3763
3888
|
setQfsStepBreakdowns(qfsStepBd ?? {});
|
|
@@ -3780,19 +3905,19 @@ function FunnelEventsTab({ dateRange }) {
|
|
|
3780
3905
|
setLoading(false);
|
|
3781
3906
|
});
|
|
3782
3907
|
return () => controller.abort();
|
|
3783
|
-
}, [dateRange, selectedCondition, refreshKey]);
|
|
3784
|
-
const qfsTotal = (0,
|
|
3908
|
+
}, [targetUrl, dateRange, selectedCondition, refreshKey]);
|
|
3909
|
+
const qfsTotal = (0, import_react8.useMemo)(
|
|
3785
3910
|
() => qfsFlows.reduce((sum, f) => sum + (f.steps[0]?.count ?? 0), 0),
|
|
3786
3911
|
[qfsFlows]
|
|
3787
3912
|
);
|
|
3788
|
-
const allRows = (0,
|
|
3913
|
+
const allRows = (0, import_react8.useMemo)(
|
|
3789
3914
|
() => [
|
|
3790
3915
|
...rows.filter((r) => !r.isStep),
|
|
3791
3916
|
{ event: "questionnaire_flow_step", label: "Pre Consult Journey", count: qfsTotal, isStep: false }
|
|
3792
3917
|
],
|
|
3793
3918
|
[rows, qfsTotal]
|
|
3794
3919
|
);
|
|
3795
|
-
const conditionSelectOptions = (0,
|
|
3920
|
+
const conditionSelectOptions = (0, import_react8.useMemo)(
|
|
3796
3921
|
() => conditionOptions.map((c) => {
|
|
3797
3922
|
const svc = conditionServiceMap[String(c.id)];
|
|
3798
3923
|
return {
|
|
@@ -3804,21 +3929,21 @@ function FunnelEventsTab({ dateRange }) {
|
|
|
3804
3929
|
}),
|
|
3805
3930
|
[conditionOptions, conditionServiceMap]
|
|
3806
3931
|
);
|
|
3807
|
-
const handleClearFilters = (0,
|
|
3932
|
+
const handleClearFilters = (0, import_react8.useCallback)(() => {
|
|
3808
3933
|
setSelectedCondition("");
|
|
3809
3934
|
}, []);
|
|
3810
|
-
const handleFetchLatest = (0,
|
|
3935
|
+
const handleFetchLatest = (0, import_react8.useCallback)(async () => {
|
|
3811
3936
|
setClearing(true);
|
|
3812
3937
|
try {
|
|
3813
|
-
await clearMixpanelCache(
|
|
3938
|
+
await clearMixpanelCache(targetUrl);
|
|
3814
3939
|
} catch (err) {
|
|
3815
3940
|
console.error("[FunnelEventsTab] clear-cache error:", err);
|
|
3816
3941
|
} finally {
|
|
3817
3942
|
setClearing(false);
|
|
3818
3943
|
setRefreshKey((k) => k + 1);
|
|
3819
3944
|
}
|
|
3820
|
-
}, []);
|
|
3821
|
-
const handleClosePatientModal = (0,
|
|
3945
|
+
}, [targetUrl]);
|
|
3946
|
+
const handleClosePatientModal = (0, import_react8.useCallback)(() => setPatientModalOpen(false), []);
|
|
3822
3947
|
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { padding: "28px 0 48px", background: "#f8f9fb", minHeight: 500 }, children: [
|
|
3823
3948
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
3824
3949
|
"div",
|
|
@@ -3982,7 +4107,7 @@ function FunnelEventsTab({ dateRange }) {
|
|
|
3982
4107
|
}
|
|
3983
4108
|
|
|
3984
4109
|
// src/studio/funnel/pm-funnel-tab.tsx
|
|
3985
|
-
var
|
|
4110
|
+
var import_react9 = require("react");
|
|
3986
4111
|
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
3987
4112
|
var HIDDEN_PARAMS = /* @__PURE__ */ new Set([
|
|
3988
4113
|
"product_name",
|
|
@@ -4586,51 +4711,52 @@ function FlowTab({ label, icon, desc, active, onClick }) {
|
|
|
4586
4711
|
}
|
|
4587
4712
|
);
|
|
4588
4713
|
}
|
|
4589
|
-
function PmFunnelTab({ dateRange }) {
|
|
4590
|
-
const
|
|
4591
|
-
const [
|
|
4592
|
-
const [
|
|
4593
|
-
const [
|
|
4594
|
-
const [
|
|
4595
|
-
const [
|
|
4596
|
-
const [
|
|
4597
|
-
const [
|
|
4598
|
-
const [
|
|
4599
|
-
const [
|
|
4600
|
-
const [
|
|
4601
|
-
const [
|
|
4602
|
-
const [
|
|
4603
|
-
const [
|
|
4604
|
-
const [
|
|
4605
|
-
const [
|
|
4606
|
-
const [
|
|
4607
|
-
const [
|
|
4608
|
-
const [
|
|
4609
|
-
const [
|
|
4610
|
-
const [
|
|
4611
|
-
const [
|
|
4612
|
-
const [
|
|
4613
|
-
const [
|
|
4614
|
-
const [
|
|
4615
|
-
const [
|
|
4616
|
-
const
|
|
4714
|
+
function PmFunnelTab({ apiUrl, dateRange }) {
|
|
4715
|
+
const targetUrl = apiUrl || MIXPANEL_API_URL;
|
|
4716
|
+
const [flowSource, setFlowSource] = (0, import_react9.useState)("product_detail");
|
|
4717
|
+
const [refreshKey, setRefreshKey] = (0, import_react9.useState)(0);
|
|
4718
|
+
const [clearing, setClearing] = (0, import_react9.useState)(false);
|
|
4719
|
+
const [pdFilterOptions, setPdFilterOptions] = (0, import_react9.useState)([]);
|
|
4720
|
+
const [pdSelectedFilter, setPdSelectedFilter] = (0, import_react9.useState)("");
|
|
4721
|
+
const [pdOptionsLoading, setPdOptionsLoading] = (0, import_react9.useState)(true);
|
|
4722
|
+
const [pdEventsData, setPdEventsData] = (0, import_react9.useState)(null);
|
|
4723
|
+
const [pdLoading, setPdLoading] = (0, import_react9.useState)(false);
|
|
4724
|
+
const [pdError, setPdError] = (0, import_react9.useState)(null);
|
|
4725
|
+
const [cdConditionOptions, setCdConditionOptions] = (0, import_react9.useState)([]);
|
|
4726
|
+
const [cdSelectedConditionId, setCdSelectedConditionId] = (0, import_react9.useState)("");
|
|
4727
|
+
const [cdOptionsLoading, setCdOptionsLoading] = (0, import_react9.useState)(true);
|
|
4728
|
+
const [cdConditionData, setCdConditionData] = (0, import_react9.useState)(null);
|
|
4729
|
+
const [cdConditionLoading, setCdConditionLoading] = (0, import_react9.useState)(false);
|
|
4730
|
+
const [cdConditionError, setCdConditionError] = (0, import_react9.useState)(null);
|
|
4731
|
+
const [cdSelectedProduct, setCdSelectedProduct] = (0, import_react9.useState)(null);
|
|
4732
|
+
const [cdCartData, setCdCartData] = (0, import_react9.useState)(null);
|
|
4733
|
+
const [cdCartLoading, setCdCartLoading] = (0, import_react9.useState)(false);
|
|
4734
|
+
const [cartAuthModalOpen, setCartAuthModalOpen] = (0, import_react9.useState)(false);
|
|
4735
|
+
const [cartAuthPatients, setCartAuthPatients] = (0, import_react9.useState)([]);
|
|
4736
|
+
const [cartAuthPatientsLoading, setCartAuthPatientsLoading] = (0, import_react9.useState)(false);
|
|
4737
|
+
const [cartAuthPatientsError, setCartAuthPatientsError] = (0, import_react9.useState)(null);
|
|
4738
|
+
const [authModalOpen, setAuthModalOpen] = (0, import_react9.useState)(false);
|
|
4739
|
+
const [authPatients, setAuthPatients] = (0, import_react9.useState)([]);
|
|
4740
|
+
const [authPatientsLoading, setAuthPatientsLoading] = (0, import_react9.useState)(false);
|
|
4741
|
+
const [authPatientsError, setAuthPatientsError] = (0, import_react9.useState)(null);
|
|
4742
|
+
const handleFetchLatest = (0, import_react9.useCallback)(async () => {
|
|
4617
4743
|
setClearing(true);
|
|
4618
4744
|
try {
|
|
4619
|
-
await clearMixpanelCache(
|
|
4745
|
+
await clearMixpanelCache(targetUrl);
|
|
4620
4746
|
} catch (err) {
|
|
4621
4747
|
console.error("PM funnel clear-cache error:", err);
|
|
4622
4748
|
} finally {
|
|
4623
4749
|
setClearing(false);
|
|
4624
4750
|
setRefreshKey((k) => k + 1);
|
|
4625
4751
|
}
|
|
4626
|
-
}, []);
|
|
4627
|
-
(0,
|
|
4752
|
+
}, [targetUrl]);
|
|
4753
|
+
(0, import_react9.useEffect)(() => {
|
|
4628
4754
|
if (flowSource !== "product_detail") return;
|
|
4629
4755
|
const controller = new AbortController();
|
|
4630
4756
|
setPdOptionsLoading(true);
|
|
4631
4757
|
setPdSelectedFilter("");
|
|
4632
4758
|
setPdEventsData(null);
|
|
4633
|
-
fetchPmFunnelProducts(
|
|
4759
|
+
fetchPmFunnelProducts(targetUrl, dateRange, "product_detail", controller.signal).then((values) => {
|
|
4634
4760
|
if (!controller.signal.aborted) {
|
|
4635
4761
|
setPdFilterOptions(values);
|
|
4636
4762
|
setPdOptionsLoading(false);
|
|
@@ -4641,8 +4767,8 @@ function PmFunnelTab({ dateRange }) {
|
|
|
4641
4767
|
if (!controller.signal.aborted) setPdOptionsLoading(false);
|
|
4642
4768
|
});
|
|
4643
4769
|
return () => controller.abort();
|
|
4644
|
-
}, [dateRange, flowSource, refreshKey]);
|
|
4645
|
-
(0,
|
|
4770
|
+
}, [targetUrl, dateRange, flowSource, refreshKey]);
|
|
4771
|
+
(0, import_react9.useEffect)(() => {
|
|
4646
4772
|
if (flowSource !== "product_detail" || !pdSelectedFilter) {
|
|
4647
4773
|
setPdEventsData(null);
|
|
4648
4774
|
setPdLoading(false);
|
|
@@ -4651,7 +4777,7 @@ function PmFunnelTab({ dateRange }) {
|
|
|
4651
4777
|
const controller = new AbortController();
|
|
4652
4778
|
setPdLoading(true);
|
|
4653
4779
|
setPdError(null);
|
|
4654
|
-
fetchPmFunnelEvents(
|
|
4780
|
+
fetchPmFunnelEvents(targetUrl, dateRange, pdSelectedFilter, "product_detail", controller.signal).then((data) => {
|
|
4655
4781
|
if (!controller.signal.aborted) {
|
|
4656
4782
|
setPdEventsData(data);
|
|
4657
4783
|
setPdLoading(false);
|
|
@@ -4664,8 +4790,8 @@ function PmFunnelTab({ dateRange }) {
|
|
|
4664
4790
|
}
|
|
4665
4791
|
});
|
|
4666
4792
|
return () => controller.abort();
|
|
4667
|
-
}, [pdSelectedFilter, dateRange, flowSource, refreshKey]);
|
|
4668
|
-
(0,
|
|
4793
|
+
}, [targetUrl, pdSelectedFilter, dateRange, flowSource, refreshKey]);
|
|
4794
|
+
(0, import_react9.useEffect)(() => {
|
|
4669
4795
|
if (flowSource !== "condition_detail") return;
|
|
4670
4796
|
const controller = new AbortController();
|
|
4671
4797
|
setCdOptionsLoading(true);
|
|
@@ -4674,7 +4800,7 @@ function PmFunnelTab({ dateRange }) {
|
|
|
4674
4800
|
setCdSelectedProduct(null);
|
|
4675
4801
|
setCdCartData(null);
|
|
4676
4802
|
Promise.all([
|
|
4677
|
-
fetchPmConditionOptions(
|
|
4803
|
+
fetchPmConditionOptions(targetUrl, dateRange, controller.signal),
|
|
4678
4804
|
sanityClient.fetch(
|
|
4679
4805
|
`*[_type == "singleCondition" && defined(conditionId)]{conditionId, title}`
|
|
4680
4806
|
)
|
|
@@ -4696,8 +4822,8 @@ function PmFunnelTab({ dateRange }) {
|
|
|
4696
4822
|
if (!controller.signal.aborted) setCdOptionsLoading(false);
|
|
4697
4823
|
});
|
|
4698
4824
|
return () => controller.abort();
|
|
4699
|
-
}, [dateRange, flowSource, refreshKey]);
|
|
4700
|
-
(0,
|
|
4825
|
+
}, [targetUrl, dateRange, flowSource, refreshKey]);
|
|
4826
|
+
(0, import_react9.useEffect)(() => {
|
|
4701
4827
|
if (flowSource !== "condition_detail" || !cdSelectedConditionId) {
|
|
4702
4828
|
setCdConditionData(null);
|
|
4703
4829
|
setCdSelectedProduct(null);
|
|
@@ -4710,7 +4836,7 @@ function PmFunnelTab({ dateRange }) {
|
|
|
4710
4836
|
setCdConditionError(null);
|
|
4711
4837
|
setCdSelectedProduct(null);
|
|
4712
4838
|
setCdCartData(null);
|
|
4713
|
-
fetchPmConditionEvents(
|
|
4839
|
+
fetchPmConditionEvents(targetUrl, dateRange, Number(cdSelectedConditionId), controller.signal).then((data) => {
|
|
4714
4840
|
if (!controller.signal.aborted) {
|
|
4715
4841
|
setCdConditionData(data);
|
|
4716
4842
|
setCdConditionLoading(false);
|
|
@@ -4723,76 +4849,76 @@ function PmFunnelTab({ dateRange }) {
|
|
|
4723
4849
|
}
|
|
4724
4850
|
});
|
|
4725
4851
|
return () => controller.abort();
|
|
4726
|
-
}, [cdSelectedConditionId, dateRange, flowSource, refreshKey]);
|
|
4727
|
-
const handleProductSelect = (0,
|
|
4852
|
+
}, [targetUrl, cdSelectedConditionId, dateRange, flowSource, refreshKey]);
|
|
4853
|
+
const handleProductSelect = (0, import_react9.useCallback)((productName) => {
|
|
4728
4854
|
if (productName === cdSelectedProduct) return;
|
|
4729
4855
|
setCdSelectedProduct(productName);
|
|
4730
4856
|
setCdCartData(null);
|
|
4731
4857
|
setCdCartLoading(true);
|
|
4732
|
-
fetchPmProductCart(
|
|
4858
|
+
fetchPmProductCart(targetUrl, dateRange, Number(cdSelectedConditionId), productName).then((data) => {
|
|
4733
4859
|
setCdCartData(data);
|
|
4734
4860
|
setCdCartLoading(false);
|
|
4735
4861
|
}).catch((err) => {
|
|
4736
4862
|
console.error("PM funnel cart error:", err);
|
|
4737
4863
|
setCdCartLoading(false);
|
|
4738
4864
|
});
|
|
4739
|
-
}, [cdSelectedConditionId, cdSelectedProduct, dateRange]);
|
|
4740
|
-
const handlePdCartAuthClick = (0,
|
|
4865
|
+
}, [targetUrl, cdSelectedConditionId, cdSelectedProduct, dateRange]);
|
|
4866
|
+
const handlePdCartAuthClick = (0, import_react9.useCallback)(() => {
|
|
4741
4867
|
if (!pdSelectedFilter) return;
|
|
4742
4868
|
setCartAuthModalOpen(true);
|
|
4743
4869
|
setCartAuthPatientsLoading(true);
|
|
4744
4870
|
setCartAuthPatientsError(null);
|
|
4745
4871
|
setCartAuthPatients([]);
|
|
4746
|
-
fetchCartAuthPatients(
|
|
4872
|
+
fetchCartAuthPatients(targetUrl, dateRange, { flowSource: "product_detail", product: pdSelectedFilter }).then((records) => {
|
|
4747
4873
|
setCartAuthPatients(records);
|
|
4748
4874
|
setCartAuthPatientsLoading(false);
|
|
4749
4875
|
}).catch((err) => {
|
|
4750
4876
|
setCartAuthPatientsError(err instanceof Error ? err.message : String(err));
|
|
4751
4877
|
setCartAuthPatientsLoading(false);
|
|
4752
4878
|
});
|
|
4753
|
-
}, [pdSelectedFilter, dateRange]);
|
|
4754
|
-
const handleCdCartAuthClick = (0,
|
|
4879
|
+
}, [targetUrl, pdSelectedFilter, dateRange]);
|
|
4880
|
+
const handleCdCartAuthClick = (0, import_react9.useCallback)(() => {
|
|
4755
4881
|
if (!cdSelectedProduct || !cdSelectedConditionId) return;
|
|
4756
4882
|
setCartAuthModalOpen(true);
|
|
4757
4883
|
setCartAuthPatientsLoading(true);
|
|
4758
4884
|
setCartAuthPatientsError(null);
|
|
4759
4885
|
setCartAuthPatients([]);
|
|
4760
|
-
fetchCartAuthPatients(
|
|
4886
|
+
fetchCartAuthPatients(targetUrl, dateRange, { flowSource: "condition_detail", product: cdSelectedProduct, conditionId: Number(cdSelectedConditionId) }).then((records) => {
|
|
4761
4887
|
setCartAuthPatients(records);
|
|
4762
4888
|
setCartAuthPatientsLoading(false);
|
|
4763
4889
|
}).catch((err) => {
|
|
4764
4890
|
setCartAuthPatientsError(err instanceof Error ? err.message : String(err));
|
|
4765
4891
|
setCartAuthPatientsLoading(false);
|
|
4766
4892
|
});
|
|
4767
|
-
}, [cdSelectedConditionId, cdSelectedProduct, dateRange]);
|
|
4768
|
-
const handlePdAuthClick = (0,
|
|
4893
|
+
}, [targetUrl, cdSelectedConditionId, cdSelectedProduct, dateRange]);
|
|
4894
|
+
const handlePdAuthClick = (0, import_react9.useCallback)(() => {
|
|
4769
4895
|
if (!pdSelectedFilter) return;
|
|
4770
4896
|
setAuthModalOpen(true);
|
|
4771
4897
|
setAuthPatientsLoading(true);
|
|
4772
4898
|
setAuthPatientsError(null);
|
|
4773
4899
|
setAuthPatients([]);
|
|
4774
|
-
fetchAuthPatients(
|
|
4900
|
+
fetchAuthPatients(targetUrl, dateRange, { flowSource: "product_detail", product: pdSelectedFilter }).then((records) => {
|
|
4775
4901
|
setAuthPatients(records);
|
|
4776
4902
|
setAuthPatientsLoading(false);
|
|
4777
4903
|
}).catch((err) => {
|
|
4778
4904
|
setAuthPatientsError(err instanceof Error ? err.message : String(err));
|
|
4779
4905
|
setAuthPatientsLoading(false);
|
|
4780
4906
|
});
|
|
4781
|
-
}, [pdSelectedFilter, dateRange]);
|
|
4782
|
-
const handleCdAuthClick = (0,
|
|
4907
|
+
}, [targetUrl, pdSelectedFilter, dateRange]);
|
|
4908
|
+
const handleCdAuthClick = (0, import_react9.useCallback)(() => {
|
|
4783
4909
|
if (!cdSelectedConditionId) return;
|
|
4784
4910
|
setAuthModalOpen(true);
|
|
4785
4911
|
setAuthPatientsLoading(true);
|
|
4786
4912
|
setAuthPatientsError(null);
|
|
4787
4913
|
setAuthPatients([]);
|
|
4788
|
-
fetchAuthPatients(
|
|
4914
|
+
fetchAuthPatients(targetUrl, dateRange, { flowSource: "condition_detail", conditionId: Number(cdSelectedConditionId) }).then((records) => {
|
|
4789
4915
|
setAuthPatients(records);
|
|
4790
4916
|
setAuthPatientsLoading(false);
|
|
4791
4917
|
}).catch((err) => {
|
|
4792
4918
|
setAuthPatientsError(err instanceof Error ? err.message : String(err));
|
|
4793
4919
|
setAuthPatientsLoading(false);
|
|
4794
4920
|
});
|
|
4795
|
-
}, [cdSelectedConditionId, dateRange]);
|
|
4921
|
+
}, [targetUrl, cdSelectedConditionId, dateRange]);
|
|
4796
4922
|
const pdFunnelEvents = [...PM_PRODUCT_DETAIL_EVENTS];
|
|
4797
4923
|
const pdAuthMethods = pdEventsData?.paramBreakdowns?.auth_completed?.method ?? {};
|
|
4798
4924
|
const pdGuestCount = (pdAuthMethods["guest"] ?? 0) + (pdAuthMethods["Guest"] ?? 0);
|
|
@@ -5102,13 +5228,13 @@ function PmFunnelTab({ dateRange }) {
|
|
|
5102
5228
|
}
|
|
5103
5229
|
|
|
5104
5230
|
// src/studio/search-console/search-console-dashboard.tsx
|
|
5105
|
-
var
|
|
5231
|
+
var import_react11 = require("react");
|
|
5106
5232
|
var import_recharts5 = require("recharts");
|
|
5107
5233
|
var import_lucide_react2 = require("lucide-react");
|
|
5108
5234
|
|
|
5109
5235
|
// src/studio/search-console/dashboard-ui.tsx
|
|
5110
|
-
var
|
|
5111
|
-
var
|
|
5236
|
+
var import_react10 = require("react");
|
|
5237
|
+
var import_react_dom2 = require("react-dom");
|
|
5112
5238
|
var import_lucide_react = require("lucide-react");
|
|
5113
5239
|
var import_recharts4 = require("recharts");
|
|
5114
5240
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
@@ -5994,16 +6120,20 @@ function ErrorState({
|
|
|
5994
6120
|
] });
|
|
5995
6121
|
}
|
|
5996
6122
|
function InfoIcon2({ text }) {
|
|
5997
|
-
const triggerRef = (0,
|
|
5998
|
-
const [tip, setTip] = (0,
|
|
6123
|
+
const triggerRef = (0, import_react10.useRef)(null);
|
|
6124
|
+
const [tip, setTip] = (0, import_react10.useState)(null);
|
|
5999
6125
|
const show = () => {
|
|
6000
6126
|
const el = triggerRef.current;
|
|
6001
6127
|
if (!el) return;
|
|
6002
6128
|
const rect = el.getBoundingClientRect();
|
|
6003
6129
|
const placeBelow = rect.top < 120;
|
|
6130
|
+
const tipWidth = 230;
|
|
6131
|
+
const pad = 12;
|
|
6132
|
+
const centerX = rect.left + rect.width / 2;
|
|
6133
|
+
const clampedLeft = typeof window !== "undefined" ? Math.max(tipWidth / 2 + pad, Math.min(centerX, window.innerWidth - tipWidth / 2 - pad)) : centerX;
|
|
6004
6134
|
setTip({
|
|
6005
6135
|
top: placeBelow ? rect.bottom + 8 : rect.top - 8,
|
|
6006
|
-
left:
|
|
6136
|
+
left: clampedLeft,
|
|
6007
6137
|
placement: placeBelow ? "bottom" : "top"
|
|
6008
6138
|
});
|
|
6009
6139
|
};
|
|
@@ -6021,7 +6151,7 @@ function InfoIcon2({ text }) {
|
|
|
6021
6151
|
children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_lucide_react.Info, { size: 12 })
|
|
6022
6152
|
}
|
|
6023
6153
|
),
|
|
6024
|
-
tip && typeof document !== "undefined" && (0,
|
|
6154
|
+
tip && typeof document !== "undefined" && (0, import_react_dom2.createPortal)(
|
|
6025
6155
|
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
6026
6156
|
"span",
|
|
6027
6157
|
{
|
|
@@ -6293,9 +6423,26 @@ function ScOverviewTab({ data, loading }) {
|
|
|
6293
6423
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("stop", { offset: "95%", stopColor: color, stopOpacity: 0 })
|
|
6294
6424
|
] }, id)) }),
|
|
6295
6425
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_recharts5.CartesianGrid, { strokeDasharray: "4 6", stroke: CHART_GRID }),
|
|
6296
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
6426
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
6427
|
+
import_recharts5.XAxis,
|
|
6428
|
+
{
|
|
6429
|
+
dataKey: "date",
|
|
6430
|
+
tickFormatter: formatDateTick,
|
|
6431
|
+
minTickGap: 28,
|
|
6432
|
+
interval: evenTickInterval(clicksSeries?.length ?? 0),
|
|
6433
|
+
padding: { left: 2, right: 15 },
|
|
6434
|
+
...chartAxis
|
|
6435
|
+
}
|
|
6436
|
+
),
|
|
6297
6437
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_recharts5.YAxis, { ...chartAxis, width: 38 }),
|
|
6298
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
6438
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
6439
|
+
import_recharts5.Tooltip,
|
|
6440
|
+
{
|
|
6441
|
+
labelFormatter: formatTooltipDate,
|
|
6442
|
+
contentStyle: chartTooltipStyle,
|
|
6443
|
+
cursor: { stroke: "#cbd5e1", strokeDasharray: "4 4" }
|
|
6444
|
+
}
|
|
6445
|
+
),
|
|
6299
6446
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_recharts5.Legend, { wrapperStyle: { fontSize: 12, fontWeight: 700, paddingTop: 10 } }),
|
|
6300
6447
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_recharts5.Area, { type: "monotone", dataKey: "clicks", name: "Clicks", stroke: "#0f9f6e", fill: "url(#scClicks)", strokeWidth: 2.4, dot: false }),
|
|
6301
6448
|
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_recharts5.Area, { type: "monotone", dataKey: "impressions", name: "Impressions", stroke: "#6366f1", fill: "url(#scImpressions)", strokeWidth: 2.4, dot: false })
|
|
@@ -6486,9 +6633,9 @@ function ScSitemapsTab({
|
|
|
6486
6633
|
onRefreshData,
|
|
6487
6634
|
loading
|
|
6488
6635
|
}) {
|
|
6489
|
-
const [submitting, setSubmitting] = (0,
|
|
6490
|
-
const [submitMsg, setSubmitMsg] = (0,
|
|
6491
|
-
const [newSitemapUrl, setNewSitemapUrl] = (0,
|
|
6636
|
+
const [submitting, setSubmitting] = (0, import_react11.useState)(null);
|
|
6637
|
+
const [submitMsg, setSubmitMsg] = (0, import_react11.useState)(null);
|
|
6638
|
+
const [newSitemapUrl, setNewSitemapUrl] = (0, import_react11.useState)("");
|
|
6492
6639
|
const totalSubmitted = data.sitemaps.reduce((sum, sitemap) => sum + sitemap.contents.reduce((contentSum, content) => contentSum + content.submitted, 0), 0);
|
|
6493
6640
|
const totalIndexed = data.sitemaps.reduce((sum, sitemap) => sum + sitemap.contents.reduce((contentSum, content) => contentSum + content.indexed, 0), 0);
|
|
6494
6641
|
const indexRate = totalSubmitted > 0 ? totalIndexed / totalSubmitted : 0;
|
|
@@ -6623,25 +6770,25 @@ function SearchConsoleDashboard({
|
|
|
6623
6770
|
activeTab: controlledTab,
|
|
6624
6771
|
onTabChange
|
|
6625
6772
|
}) {
|
|
6626
|
-
const [data, setData] = (0,
|
|
6627
|
-
const [loading, setLoading] = (0,
|
|
6628
|
-
const [error, setError] = (0,
|
|
6629
|
-
const [dateRange, setDateRange] = (0,
|
|
6630
|
-
const [uncontrolledTab, setUncontrolledTab] = (0,
|
|
6773
|
+
const [data, setData] = (0, import_react11.useState)(null);
|
|
6774
|
+
const [loading, setLoading] = (0, import_react11.useState)(true);
|
|
6775
|
+
const [error, setError] = (0, import_react11.useState)(null);
|
|
6776
|
+
const [dateRange, setDateRange] = (0, import_react11.useState)("28");
|
|
6777
|
+
const [uncontrolledTab, setUncontrolledTab] = (0, import_react11.useState)(initialTab);
|
|
6631
6778
|
const activeTab = controlledTab !== void 0 ? controlledTab : uncontrolledTab;
|
|
6632
|
-
const handleTabChange = (0,
|
|
6779
|
+
const handleTabChange = (0, import_react11.useCallback)(
|
|
6633
6780
|
(nextTab) => {
|
|
6634
6781
|
setUncontrolledTab(nextTab);
|
|
6635
6782
|
onTabChange?.(nextTab);
|
|
6636
6783
|
},
|
|
6637
6784
|
[onTabChange]
|
|
6638
6785
|
);
|
|
6639
|
-
(0,
|
|
6786
|
+
(0, import_react11.useEffect)(() => {
|
|
6640
6787
|
if (initialTab) {
|
|
6641
6788
|
setUncontrolledTab(initialTab);
|
|
6642
6789
|
}
|
|
6643
6790
|
}, [initialTab]);
|
|
6644
|
-
const loadData = (0,
|
|
6791
|
+
const loadData = (0, import_react11.useCallback)(async () => {
|
|
6645
6792
|
try {
|
|
6646
6793
|
setLoading(true);
|
|
6647
6794
|
setError(null);
|
|
@@ -6653,7 +6800,7 @@ function SearchConsoleDashboard({
|
|
|
6653
6800
|
setLoading(false);
|
|
6654
6801
|
}
|
|
6655
6802
|
}, [apiUrl, dateRange]);
|
|
6656
|
-
(0,
|
|
6803
|
+
(0, import_react11.useEffect)(() => {
|
|
6657
6804
|
loadData();
|
|
6658
6805
|
}, [loadData]);
|
|
6659
6806
|
if (loading && !data) {
|
|
@@ -7199,7 +7346,10 @@ function CombinedOverviewTab({
|
|
|
7199
7346
|
import_recharts6.XAxis,
|
|
7200
7347
|
{
|
|
7201
7348
|
dataKey: "date",
|
|
7202
|
-
tickFormatter:
|
|
7349
|
+
tickFormatter: formatDateTick,
|
|
7350
|
+
minTickGap: 28,
|
|
7351
|
+
interval: evenTickInterval(scTimeSeries?.length ?? 0),
|
|
7352
|
+
padding: { left: 2, right: 15 },
|
|
7203
7353
|
tick: { fontSize: 10, fill: "#94a3b8" },
|
|
7204
7354
|
axisLine: false,
|
|
7205
7355
|
tickLine: false
|
|
@@ -7209,6 +7359,7 @@ function CombinedOverviewTab({
|
|
|
7209
7359
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
7210
7360
|
import_recharts6.Tooltip,
|
|
7211
7361
|
{
|
|
7362
|
+
labelFormatter: formatTooltipDate,
|
|
7212
7363
|
contentStyle: {
|
|
7213
7364
|
background: "#1e293b",
|
|
7214
7365
|
border: "none",
|
|
@@ -7248,7 +7399,10 @@ function CombinedOverviewTab({
|
|
|
7248
7399
|
import_recharts6.XAxis,
|
|
7249
7400
|
{
|
|
7250
7401
|
dataKey: "date",
|
|
7251
|
-
tickFormatter:
|
|
7402
|
+
tickFormatter: formatDateTick,
|
|
7403
|
+
minTickGap: 28,
|
|
7404
|
+
interval: evenTickInterval(gaTimeSeries?.length ?? 0),
|
|
7405
|
+
padding: { left: 2, right: 15 },
|
|
7252
7406
|
tick: { fontSize: 10, fill: "#94a3b8" },
|
|
7253
7407
|
axisLine: false,
|
|
7254
7408
|
tickLine: false
|
|
@@ -7258,6 +7412,7 @@ function CombinedOverviewTab({
|
|
|
7258
7412
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
7259
7413
|
import_recharts6.Tooltip,
|
|
7260
7414
|
{
|
|
7415
|
+
labelFormatter: formatTooltipDate,
|
|
7261
7416
|
contentStyle: {
|
|
7262
7417
|
background: "#1e293b",
|
|
7263
7418
|
border: "none",
|
|
@@ -7399,25 +7554,27 @@ function mapToScRange(range) {
|
|
|
7399
7554
|
function UnifiedDashboard({
|
|
7400
7555
|
apiUrl = "/api/analytics",
|
|
7401
7556
|
scApiUrl,
|
|
7557
|
+
mixpanelApiUrl,
|
|
7402
7558
|
brandName = "Pharmacy Analytics"
|
|
7403
7559
|
}) {
|
|
7404
|
-
const [activeMainSection, setActiveMainSection] = (0,
|
|
7405
|
-
const [activeTab, setActiveTab] = (0,
|
|
7406
|
-
const [scActiveTab, setScActiveTab] = (0,
|
|
7407
|
-
const [dateRange, setDateRange] = (0,
|
|
7408
|
-
const [gaOpen, setGaOpen] = (0,
|
|
7409
|
-
const [customOpen, setCustomOpen] = (0,
|
|
7410
|
-
const [scOpen, setScOpen] = (0,
|
|
7411
|
-
const
|
|
7412
|
-
const [
|
|
7413
|
-
const [
|
|
7414
|
-
const [
|
|
7415
|
-
const [
|
|
7416
|
-
const [
|
|
7417
|
-
const
|
|
7418
|
-
const
|
|
7560
|
+
const [activeMainSection, setActiveMainSection] = (0, import_react12.useState)("all");
|
|
7561
|
+
const [activeTab, setActiveTab] = (0, import_react12.useState)("all-overview");
|
|
7562
|
+
const [scActiveTab, setScActiveTab] = (0, import_react12.useState)("overview");
|
|
7563
|
+
const [dateRange, setDateRange] = (0, import_react12.useState)("30");
|
|
7564
|
+
const [gaOpen, setGaOpen] = (0, import_react12.useState)(true);
|
|
7565
|
+
const [customOpen, setCustomOpen] = (0, import_react12.useState)(true);
|
|
7566
|
+
const [scOpen, setScOpen] = (0, import_react12.useState)(true);
|
|
7567
|
+
const isAll = activeMainSection === "all";
|
|
7568
|
+
const [gaData, setGaData] = (0, import_react12.useState)(null);
|
|
7569
|
+
const [scData, setScData] = (0, import_react12.useState)(null);
|
|
7570
|
+
const [loadingGa, setLoadingGa] = (0, import_react12.useState)(true);
|
|
7571
|
+
const [loadingSc, setLoadingSc] = (0, import_react12.useState)(true);
|
|
7572
|
+
const [gaError, setGaError] = (0, import_react12.useState)(null);
|
|
7573
|
+
const [scError, setScError] = (0, import_react12.useState)(null);
|
|
7574
|
+
const gaAbortRef = (0, import_react12.useRef)(null);
|
|
7575
|
+
const scAbortRef = (0, import_react12.useRef)(null);
|
|
7419
7576
|
const effectiveScUrl = scApiUrl || apiUrl;
|
|
7420
|
-
const loadGaData = (0,
|
|
7577
|
+
const loadGaData = (0, import_react12.useCallback)(async () => {
|
|
7421
7578
|
gaAbortRef.current?.abort();
|
|
7422
7579
|
const controller = new AbortController();
|
|
7423
7580
|
gaAbortRef.current = controller;
|
|
@@ -7434,7 +7591,7 @@ function UnifiedDashboard({
|
|
|
7434
7591
|
if (!controller.signal.aborted) setLoadingGa(false);
|
|
7435
7592
|
}
|
|
7436
7593
|
}, [apiUrl, dateRange]);
|
|
7437
|
-
const loadScData = (0,
|
|
7594
|
+
const loadScData = (0, import_react12.useCallback)(async () => {
|
|
7438
7595
|
scAbortRef.current?.abort();
|
|
7439
7596
|
const controller = new AbortController();
|
|
7440
7597
|
scAbortRef.current = controller;
|
|
@@ -7452,11 +7609,11 @@ function UnifiedDashboard({
|
|
|
7452
7609
|
if (!controller.signal.aborted) setLoadingSc(false);
|
|
7453
7610
|
}
|
|
7454
7611
|
}, [effectiveScUrl, dateRange]);
|
|
7455
|
-
const loadAllData = (0,
|
|
7612
|
+
const loadAllData = (0, import_react12.useCallback)(() => {
|
|
7456
7613
|
loadGaData();
|
|
7457
7614
|
loadScData();
|
|
7458
7615
|
}, [loadGaData, loadScData]);
|
|
7459
|
-
(0,
|
|
7616
|
+
(0, import_react12.useEffect)(() => {
|
|
7460
7617
|
loadAllData();
|
|
7461
7618
|
return () => {
|
|
7462
7619
|
gaAbortRef.current?.abort();
|
|
@@ -7813,6 +7970,7 @@ function UnifiedDashboard({
|
|
|
7813
7970
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
7814
7971
|
"button",
|
|
7815
7972
|
{
|
|
7973
|
+
disabled: !isAll,
|
|
7816
7974
|
onClick: () => setGaOpen((o) => !o),
|
|
7817
7975
|
style: {
|
|
7818
7976
|
display: "flex",
|
|
@@ -7823,7 +7981,7 @@ function UnifiedDashboard({
|
|
|
7823
7981
|
borderRadius: 6,
|
|
7824
7982
|
background: "transparent",
|
|
7825
7983
|
border: "none",
|
|
7826
|
-
cursor: "pointer",
|
|
7984
|
+
cursor: isAll ? "pointer" : "default",
|
|
7827
7985
|
marginBottom: 2
|
|
7828
7986
|
},
|
|
7829
7987
|
children: [
|
|
@@ -7843,11 +8001,11 @@ function UnifiedDashboard({
|
|
|
7843
8001
|
children: "Google Analytics"
|
|
7844
8002
|
}
|
|
7845
8003
|
),
|
|
7846
|
-
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { style: { fontSize: 11, color: "#94a3b8" }, children: gaOpen ? "\u25BE" : "\u25B8" })
|
|
8004
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { style: { fontSize: 11, color: "#94a3b8" }, children: isAll ? gaOpen ? "\u25BE" : "\u25B8" : null })
|
|
7847
8005
|
]
|
|
7848
8006
|
}
|
|
7849
8007
|
),
|
|
7850
|
-
gaOpen && GA_SUB_TABS.map((tab) => {
|
|
8008
|
+
(gaOpen || !isAll) && GA_SUB_TABS.map((tab) => {
|
|
7851
8009
|
const active = activeTab === tab.id;
|
|
7852
8010
|
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
7853
8011
|
"button",
|
|
@@ -7882,6 +8040,7 @@ function UnifiedDashboard({
|
|
|
7882
8040
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
7883
8041
|
"button",
|
|
7884
8042
|
{
|
|
8043
|
+
disabled: !isAll,
|
|
7885
8044
|
onClick: () => setCustomOpen((o) => !o),
|
|
7886
8045
|
style: {
|
|
7887
8046
|
display: "flex",
|
|
@@ -7892,7 +8051,7 @@ function UnifiedDashboard({
|
|
|
7892
8051
|
borderRadius: 6,
|
|
7893
8052
|
background: "transparent",
|
|
7894
8053
|
border: "none",
|
|
7895
|
-
cursor: "pointer",
|
|
8054
|
+
cursor: isAll ? "pointer" : "default",
|
|
7896
8055
|
marginBottom: 2
|
|
7897
8056
|
},
|
|
7898
8057
|
children: [
|
|
@@ -7912,11 +8071,11 @@ function UnifiedDashboard({
|
|
|
7912
8071
|
children: "Custom Analytics"
|
|
7913
8072
|
}
|
|
7914
8073
|
),
|
|
7915
|
-
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { style: { fontSize: 11, color: "#94a3b8" }, children: customOpen ? "\u25BE" : "\u25B8" })
|
|
8074
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { style: { fontSize: 11, color: "#94a3b8" }, children: isAll ? customOpen ? "\u25BE" : "\u25B8" : null })
|
|
7916
8075
|
]
|
|
7917
8076
|
}
|
|
7918
8077
|
),
|
|
7919
|
-
customOpen && CUSTOM_SUB_TABS.map((tab) => {
|
|
8078
|
+
(customOpen || !isAll) && CUSTOM_SUB_TABS.map((tab) => {
|
|
7920
8079
|
const active = activeTab === tab.id;
|
|
7921
8080
|
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
7922
8081
|
"button",
|
|
@@ -7951,6 +8110,7 @@ function UnifiedDashboard({
|
|
|
7951
8110
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
7952
8111
|
"button",
|
|
7953
8112
|
{
|
|
8113
|
+
disabled: !isAll,
|
|
7954
8114
|
onClick: () => setScOpen((o) => !o),
|
|
7955
8115
|
style: {
|
|
7956
8116
|
display: "flex",
|
|
@@ -7961,7 +8121,7 @@ function UnifiedDashboard({
|
|
|
7961
8121
|
borderRadius: 6,
|
|
7962
8122
|
background: "transparent",
|
|
7963
8123
|
border: "none",
|
|
7964
|
-
cursor: "pointer",
|
|
8124
|
+
cursor: isAll ? "pointer" : "default",
|
|
7965
8125
|
marginBottom: 2
|
|
7966
8126
|
},
|
|
7967
8127
|
children: [
|
|
@@ -7981,11 +8141,11 @@ function UnifiedDashboard({
|
|
|
7981
8141
|
children: "Search Console"
|
|
7982
8142
|
}
|
|
7983
8143
|
),
|
|
7984
|
-
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { style: { fontSize: 11, color: "#94a3b8" }, children: scOpen ? "\u25BE" : "\u25B8" })
|
|
8144
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { style: { fontSize: 11, color: "#94a3b8" }, children: isAll ? scOpen ? "\u25BE" : "\u25B8" : null })
|
|
7985
8145
|
]
|
|
7986
8146
|
}
|
|
7987
8147
|
),
|
|
7988
|
-
scOpen && SC_SUB_TABS.map((tab) => {
|
|
8148
|
+
(scOpen || !isAll) && SC_SUB_TABS.map((tab) => {
|
|
7989
8149
|
const isScActive = activeTab === tab.id;
|
|
7990
8150
|
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
7991
8151
|
"button",
|
|
@@ -8063,6 +8223,7 @@ function UnifiedDashboard({
|
|
|
8063
8223
|
flex: 1,
|
|
8064
8224
|
minHeight: 0,
|
|
8065
8225
|
overflowY: "auto",
|
|
8226
|
+
overflowX: "hidden",
|
|
8066
8227
|
padding: "24px 28px"
|
|
8067
8228
|
},
|
|
8068
8229
|
children: [
|
|
@@ -8117,9 +8278,9 @@ function UnifiedDashboard({
|
|
|
8117
8278
|
activeTab === "geography" && gaData && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(GeographyTab, { data: gaData, loading: loadingGa }),
|
|
8118
8279
|
activeTab === "events" && gaData && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(EventsTab, { data: gaData, loading: loadingGa, apiUrl, dateRange }),
|
|
8119
8280
|
activeTab === "acquisition" && gaData && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(AcquisitionTab, { data: gaData, loading: loadingGa }),
|
|
8120
|
-
activeTab === "conditions-products" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ConditionsProductsTab, { apiUrl, dateRange }),
|
|
8121
|
-
activeTab === "funnel" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(FunnelEventsTab, { apiUrl, dateRange }),
|
|
8122
|
-
activeTab === "pm-funnel" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PmFunnelTab, { apiUrl, dateRange }),
|
|
8281
|
+
activeTab === "conditions-products" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ConditionsProductsTab, { apiUrl: mixpanelApiUrl, dateRange }),
|
|
8282
|
+
activeTab === "funnel" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(FunnelEventsTab, { apiUrl: mixpanelApiUrl, dateRange }),
|
|
8283
|
+
activeTab === "pm-funnel" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PmFunnelTab, { apiUrl: mixpanelApiUrl, dateRange }),
|
|
8123
8284
|
activeTab.startsWith("sc-") && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
8124
8285
|
SearchConsoleDashboard,
|
|
8125
8286
|
{
|
|
@@ -8159,6 +8320,7 @@ function pharmacyAnalyticsPlugin(config = {}) {
|
|
|
8159
8320
|
{
|
|
8160
8321
|
apiUrl: config.apiUrl || "/api/analytics",
|
|
8161
8322
|
scApiUrl: config.scApiUrl,
|
|
8323
|
+
mixpanelApiUrl: config.mixpanelApiUrl,
|
|
8162
8324
|
brandName: toolTitle
|
|
8163
8325
|
}
|
|
8164
8326
|
);
|