medusa-analytics 0.0.13 → 0.0.14
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/.medusa/server/src/admin/index.js +594 -9
- package/.medusa/server/src/admin/index.mjs +597 -12
- package/.medusa/server/src/api/admin/analytics/products/shared.js +588 -0
- package/.medusa/server/src/api/admin/analytics/products-filters/route.js +21 -0
- package/.medusa/server/src/api/admin/analytics/products-filters/types.js +3 -0
- package/.medusa/server/src/api/admin/analytics/products-over-time/route.js +24 -0
- package/.medusa/server/src/api/admin/analytics/products-over-time/types.js +3 -0
- package/.medusa/server/src/api/admin/analytics/products-performance/route.js +51 -0
- package/.medusa/server/src/api/admin/analytics/products-performance/types.js +3 -0
- package/.medusa/server/src/api/admin/analytics/products-summary/route.js +36 -0
- package/.medusa/server/src/api/admin/analytics/products-summary/types.js +3 -0
- package/.medusa/server/src/api/admin/analytics/products-top-sellers/route.js +48 -0
- package/.medusa/server/src/api/admin/analytics/products-top-sellers/types.js +3 -0
- package/README.md +45 -0
- package/package.json +1 -1
|
@@ -48,7 +48,7 @@ const ORDER_CHART_STATUSES = [
|
|
|
48
48
|
{ key: "cancelled", label: "Cancelled", color: STATUS_COLORS.cancelled },
|
|
49
49
|
{ key: "delivered", label: "Delivered", color: STATUS_COLORS.delivered }
|
|
50
50
|
];
|
|
51
|
-
function formatCurrency(value) {
|
|
51
|
+
function formatCurrency$1(value) {
|
|
52
52
|
return new Intl.NumberFormat("en-IN", {
|
|
53
53
|
style: "currency",
|
|
54
54
|
currency: "INR",
|
|
@@ -56,7 +56,7 @@ function formatCurrency(value) {
|
|
|
56
56
|
maximumFractionDigits: 0
|
|
57
57
|
}).format(value);
|
|
58
58
|
}
|
|
59
|
-
const SUMMARY_PERIODS = [
|
|
59
|
+
const SUMMARY_PERIODS$1 = [
|
|
60
60
|
{ value: "all", label: "All time" },
|
|
61
61
|
{ value: "0", label: "Today's orders" },
|
|
62
62
|
{ value: "7", label: "Last 7 days" },
|
|
@@ -150,7 +150,7 @@ function OrdersDashboard() {
|
|
|
150
150
|
value: filter,
|
|
151
151
|
onChange: (e) => setFilter(e.target.value),
|
|
152
152
|
className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
153
|
-
children: SUMMARY_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
153
|
+
children: SUMMARY_PERIODS$1.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
154
154
|
}
|
|
155
155
|
)
|
|
156
156
|
] }),
|
|
@@ -173,11 +173,11 @@ function OrdersDashboard() {
|
|
|
173
173
|
] }),
|
|
174
174
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
175
175
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Total revenue" }),
|
|
176
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formatCurrency(data.totalRevenue) })
|
|
176
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formatCurrency$1(data.totalRevenue) })
|
|
177
177
|
] }),
|
|
178
178
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
179
179
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Average order value" }),
|
|
180
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formatCurrency(data.aov) })
|
|
180
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formatCurrency$1(data.aov) })
|
|
181
181
|
] }),
|
|
182
182
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
183
183
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Orders today" }),
|
|
@@ -386,7 +386,7 @@ const COUNT_DAY_PRESETS = [
|
|
|
386
386
|
{ value: "30", label: "Last 30 days" },
|
|
387
387
|
{ value: "90", label: "Last 90 days" }
|
|
388
388
|
];
|
|
389
|
-
const GRAPH_PERIODS = [
|
|
389
|
+
const GRAPH_PERIODS$1 = [
|
|
390
390
|
{ value: "one_week", label: "One week" },
|
|
391
391
|
{ value: "one_month", label: "One month" },
|
|
392
392
|
{ value: "one_year", label: "One year" }
|
|
@@ -527,7 +527,7 @@ function CustomersDashboard() {
|
|
|
527
527
|
value: graphPeriod,
|
|
528
528
|
onChange: (e) => setGraphPeriod(e.target.value),
|
|
529
529
|
className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
530
|
-
children: GRAPH_PERIODS.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
530
|
+
children: GRAPH_PERIODS$1.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p.value, children: p.label }, p.value))
|
|
531
531
|
}
|
|
532
532
|
)
|
|
533
533
|
] })
|
|
@@ -604,9 +604,593 @@ function CustomersDashboard() {
|
|
|
604
604
|
] })
|
|
605
605
|
] });
|
|
606
606
|
}
|
|
607
|
+
const SUMMARY_PERIODS = [
|
|
608
|
+
{ value: "all", label: "All time" },
|
|
609
|
+
{ value: "0", label: "Today" },
|
|
610
|
+
{ value: "7", label: "Last 7 days" },
|
|
611
|
+
{ value: "30", label: "Last 30 days" },
|
|
612
|
+
{ value: "90", label: "Last 90 days" }
|
|
613
|
+
];
|
|
614
|
+
const GRAPH_PERIODS = [
|
|
615
|
+
{ value: "one_week", label: "One week" },
|
|
616
|
+
{ value: "one_month", label: "One month" },
|
|
617
|
+
{ value: "one_year", label: "One year" }
|
|
618
|
+
];
|
|
619
|
+
const TOP_SELLER_PERIODS = [
|
|
620
|
+
{ value: "week", label: "Week" },
|
|
621
|
+
{ value: "month", label: "Month" },
|
|
622
|
+
{ value: "year", label: "Year" },
|
|
623
|
+
{ value: "all", label: "All time" }
|
|
624
|
+
];
|
|
625
|
+
const SALES_COLOR = "var(--medusa-color-ui-fg-interactive)";
|
|
626
|
+
const VIEWS_COLOR = "#8B5CF6";
|
|
627
|
+
const REVENUE_COLOR = "#F59E0B";
|
|
628
|
+
function formatCurrency(value) {
|
|
629
|
+
return new Intl.NumberFormat("en-IN", {
|
|
630
|
+
style: "currency",
|
|
631
|
+
currency: "INR",
|
|
632
|
+
minimumFractionDigits: 0,
|
|
633
|
+
maximumFractionDigits: 0
|
|
634
|
+
}).format(value);
|
|
635
|
+
}
|
|
636
|
+
function formatRatio(value) {
|
|
637
|
+
if (value === null || Number.isNaN(value)) return "N/A";
|
|
638
|
+
return `${value.toFixed(2)}x`;
|
|
639
|
+
}
|
|
640
|
+
function truncateLabel(value, maxLength = 22) {
|
|
641
|
+
return value.length > maxLength ? `${value.slice(0, maxLength - 1)}…` : value;
|
|
642
|
+
}
|
|
643
|
+
function buildAnalyticsUrl(path, params) {
|
|
644
|
+
const search = new URLSearchParams();
|
|
645
|
+
for (const [key, value] of Object.entries(params)) {
|
|
646
|
+
if (value) {
|
|
647
|
+
search.set(key, value);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
const query = search.toString();
|
|
651
|
+
return query ? `${path}?${query}` : path;
|
|
652
|
+
}
|
|
653
|
+
function TrendTooltip({
|
|
654
|
+
active,
|
|
655
|
+
payload,
|
|
656
|
+
label
|
|
657
|
+
}) {
|
|
658
|
+
var _a, _b, _c, _d;
|
|
659
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
660
|
+
const row = (_a = payload[0]) == null ? void 0 : _a.payload;
|
|
661
|
+
const displayLabel = (row == null ? void 0 : row.label) ?? (label ? new Date(label).toLocaleDateString() : "");
|
|
662
|
+
const unitsSold = ((_b = payload.find((entry) => entry.name === "Units sold")) == null ? void 0 : _b.value) ?? 0;
|
|
663
|
+
const views = ((_c = payload.find((entry) => entry.name === "Views")) == null ? void 0 : _c.value) ?? 0;
|
|
664
|
+
const revenue = ((_d = payload.find((entry) => entry.name === "Revenue")) == null ? void 0 : _d.value) ?? 0;
|
|
665
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
666
|
+
"div",
|
|
667
|
+
{
|
|
668
|
+
style: {
|
|
669
|
+
backgroundColor: "rgb(255, 255, 255)",
|
|
670
|
+
color: "#111827",
|
|
671
|
+
border: "1px solid rgb(229, 231, 235)",
|
|
672
|
+
borderRadius: "8px",
|
|
673
|
+
padding: "12px 16px",
|
|
674
|
+
boxShadow: "0 4px 14px rgba(0, 0, 0, 0.08)",
|
|
675
|
+
fontSize: "14px",
|
|
676
|
+
lineHeight: 1.5
|
|
677
|
+
},
|
|
678
|
+
children: [
|
|
679
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, marginBottom: "6px" }, children: displayLabel }),
|
|
680
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
681
|
+
"Units sold: ",
|
|
682
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(unitsSold).toLocaleString() })
|
|
683
|
+
] }),
|
|
684
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
685
|
+
"Views: ",
|
|
686
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(views).toLocaleString() })
|
|
687
|
+
] }),
|
|
688
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
689
|
+
"Revenue: ",
|
|
690
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(Number(revenue)) })
|
|
691
|
+
] })
|
|
692
|
+
]
|
|
693
|
+
}
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
function ProductBarTooltip({
|
|
697
|
+
active,
|
|
698
|
+
payload
|
|
699
|
+
}) {
|
|
700
|
+
var _a;
|
|
701
|
+
if (!active || !(payload == null ? void 0 : payload.length)) return null;
|
|
702
|
+
const row = (_a = payload[0]) == null ? void 0 : _a.payload;
|
|
703
|
+
if (!row) return null;
|
|
704
|
+
const unitsSold = "units_sold" in row ? row.units_sold : 0;
|
|
705
|
+
const totalViews = "total_views" in row ? row.total_views : 0;
|
|
706
|
+
const revenue = "revenue" in row ? row.revenue : 0;
|
|
707
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
708
|
+
"div",
|
|
709
|
+
{
|
|
710
|
+
style: {
|
|
711
|
+
backgroundColor: "rgb(255, 255, 255)",
|
|
712
|
+
color: "#111827",
|
|
713
|
+
border: "1px solid rgb(229, 231, 235)",
|
|
714
|
+
borderRadius: "8px",
|
|
715
|
+
padding: "12px 16px",
|
|
716
|
+
boxShadow: "0 4px 14px rgba(0, 0, 0, 0.08)",
|
|
717
|
+
fontSize: "14px",
|
|
718
|
+
lineHeight: 1.5
|
|
719
|
+
},
|
|
720
|
+
children: [
|
|
721
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 600, marginBottom: "6px" }, children: row.product_title }),
|
|
722
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
723
|
+
"Units sold: ",
|
|
724
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(unitsSold).toLocaleString() })
|
|
725
|
+
] }),
|
|
726
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
727
|
+
"Views: ",
|
|
728
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: Math.floor(totalViews).toLocaleString() })
|
|
729
|
+
] }),
|
|
730
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
731
|
+
"Revenue: ",
|
|
732
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: formatCurrency(Number(revenue)) })
|
|
733
|
+
] })
|
|
734
|
+
]
|
|
735
|
+
}
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
function ProductsDashboard() {
|
|
739
|
+
var _a, _b, _c;
|
|
740
|
+
const [summaryDays, setSummaryDays] = react.useState("all");
|
|
741
|
+
const [graphPeriod, setGraphPeriod] = react.useState("one_week");
|
|
742
|
+
const [topSellerPeriod, setTopSellerPeriod] = react.useState("week");
|
|
743
|
+
const [salesChannelId, setSalesChannelId] = react.useState("all");
|
|
744
|
+
const [salesChannels, setSalesChannels] = react.useState([]);
|
|
745
|
+
const [filtersLoading, setFiltersLoading] = react.useState(true);
|
|
746
|
+
const [filtersError, setFiltersError] = react.useState(null);
|
|
747
|
+
const [summary, setSummary] = react.useState(null);
|
|
748
|
+
const [summaryLoading, setSummaryLoading] = react.useState(true);
|
|
749
|
+
const [summaryError, setSummaryError] = react.useState(null);
|
|
750
|
+
const [overTime, setOverTime] = react.useState(null);
|
|
751
|
+
const [overTimeLoading, setOverTimeLoading] = react.useState(true);
|
|
752
|
+
const [overTimeError, setOverTimeError] = react.useState(null);
|
|
753
|
+
const [topSellers, setTopSellers] = react.useState(null);
|
|
754
|
+
const [topSellersLoading, setTopSellersLoading] = react.useState(true);
|
|
755
|
+
const [topSellersError, setTopSellersError] = react.useState(null);
|
|
756
|
+
const [performance, setPerformance] = react.useState(null);
|
|
757
|
+
const [performanceLoading, setPerformanceLoading] = react.useState(true);
|
|
758
|
+
const [performanceError, setPerformanceError] = react.useState(null);
|
|
759
|
+
react.useEffect(() => {
|
|
760
|
+
let cancelled = false;
|
|
761
|
+
setFiltersLoading(true);
|
|
762
|
+
setFiltersError(null);
|
|
763
|
+
fetch("/admin/analytics/products-filters").then((res) => {
|
|
764
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
765
|
+
return res.json();
|
|
766
|
+
}).then((body) => {
|
|
767
|
+
if (!cancelled) {
|
|
768
|
+
setSalesChannels(body.salesChannels ?? []);
|
|
769
|
+
}
|
|
770
|
+
}).catch((error) => {
|
|
771
|
+
if (!cancelled) {
|
|
772
|
+
setFiltersError(error instanceof Error ? error.message : String(error));
|
|
773
|
+
}
|
|
774
|
+
}).finally(() => {
|
|
775
|
+
if (!cancelled) setFiltersLoading(false);
|
|
776
|
+
});
|
|
777
|
+
return () => {
|
|
778
|
+
cancelled = true;
|
|
779
|
+
};
|
|
780
|
+
}, []);
|
|
781
|
+
react.useEffect(() => {
|
|
782
|
+
let cancelled = false;
|
|
783
|
+
setSummaryLoading(true);
|
|
784
|
+
setSummaryError(null);
|
|
785
|
+
fetch(
|
|
786
|
+
buildAnalyticsUrl("/admin/analytics/products-summary", {
|
|
787
|
+
days: summaryDays,
|
|
788
|
+
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null
|
|
789
|
+
})
|
|
790
|
+
).then((res) => {
|
|
791
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
792
|
+
return res.json();
|
|
793
|
+
}).then((body) => {
|
|
794
|
+
if (!cancelled) setSummary(body);
|
|
795
|
+
}).catch((error) => {
|
|
796
|
+
if (!cancelled) {
|
|
797
|
+
setSummaryError(error instanceof Error ? error.message : String(error));
|
|
798
|
+
}
|
|
799
|
+
}).finally(() => {
|
|
800
|
+
if (!cancelled) setSummaryLoading(false);
|
|
801
|
+
});
|
|
802
|
+
return () => {
|
|
803
|
+
cancelled = true;
|
|
804
|
+
};
|
|
805
|
+
}, [salesChannelId, summaryDays]);
|
|
806
|
+
react.useEffect(() => {
|
|
807
|
+
let cancelled = false;
|
|
808
|
+
setOverTimeLoading(true);
|
|
809
|
+
setOverTimeError(null);
|
|
810
|
+
fetch(
|
|
811
|
+
buildAnalyticsUrl("/admin/analytics/products-over-time", {
|
|
812
|
+
period: graphPeriod,
|
|
813
|
+
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null
|
|
814
|
+
})
|
|
815
|
+
).then((res) => {
|
|
816
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
817
|
+
return res.json();
|
|
818
|
+
}).then((body) => {
|
|
819
|
+
if (!cancelled) setOverTime(body);
|
|
820
|
+
}).catch((error) => {
|
|
821
|
+
if (!cancelled) {
|
|
822
|
+
setOverTimeError(error instanceof Error ? error.message : String(error));
|
|
823
|
+
}
|
|
824
|
+
}).finally(() => {
|
|
825
|
+
if (!cancelled) setOverTimeLoading(false);
|
|
826
|
+
});
|
|
827
|
+
return () => {
|
|
828
|
+
cancelled = true;
|
|
829
|
+
};
|
|
830
|
+
}, [graphPeriod, salesChannelId]);
|
|
831
|
+
react.useEffect(() => {
|
|
832
|
+
let cancelled = false;
|
|
833
|
+
setTopSellersLoading(true);
|
|
834
|
+
setTopSellersError(null);
|
|
835
|
+
fetch(
|
|
836
|
+
buildAnalyticsUrl("/admin/analytics/products-top-sellers", {
|
|
837
|
+
period: topSellerPeriod,
|
|
838
|
+
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null
|
|
839
|
+
})
|
|
840
|
+
).then((res) => {
|
|
841
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
842
|
+
return res.json();
|
|
843
|
+
}).then((body) => {
|
|
844
|
+
if (!cancelled) setTopSellers(body);
|
|
845
|
+
}).catch((error) => {
|
|
846
|
+
if (!cancelled) {
|
|
847
|
+
setTopSellersError(error instanceof Error ? error.message : String(error));
|
|
848
|
+
}
|
|
849
|
+
}).finally(() => {
|
|
850
|
+
if (!cancelled) setTopSellersLoading(false);
|
|
851
|
+
});
|
|
852
|
+
return () => {
|
|
853
|
+
cancelled = true;
|
|
854
|
+
};
|
|
855
|
+
}, [salesChannelId, topSellerPeriod]);
|
|
856
|
+
react.useEffect(() => {
|
|
857
|
+
let cancelled = false;
|
|
858
|
+
setPerformanceLoading(true);
|
|
859
|
+
setPerformanceError(null);
|
|
860
|
+
fetch(
|
|
861
|
+
buildAnalyticsUrl("/admin/analytics/products-performance", {
|
|
862
|
+
days: summaryDays,
|
|
863
|
+
sales_channel_id: salesChannelId !== "all" ? salesChannelId : null
|
|
864
|
+
})
|
|
865
|
+
).then((res) => {
|
|
866
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
867
|
+
return res.json();
|
|
868
|
+
}).then((body) => {
|
|
869
|
+
if (!cancelled) setPerformance(body);
|
|
870
|
+
}).catch((error) => {
|
|
871
|
+
if (!cancelled) {
|
|
872
|
+
setPerformanceError(error instanceof Error ? error.message : String(error));
|
|
873
|
+
}
|
|
874
|
+
}).finally(() => {
|
|
875
|
+
if (!cancelled) setPerformanceLoading(false);
|
|
876
|
+
});
|
|
877
|
+
return () => {
|
|
878
|
+
cancelled = true;
|
|
879
|
+
};
|
|
880
|
+
}, [salesChannelId, summaryDays]);
|
|
881
|
+
const topSellerChartData = react.useMemo(
|
|
882
|
+
() => ((topSellers == null ? void 0 : topSellers.products) ?? []).slice(0, 7).map((row) => ({
|
|
883
|
+
...row,
|
|
884
|
+
product_title: truncateLabel(row.product_title, 24)
|
|
885
|
+
})).reverse(),
|
|
886
|
+
[topSellers]
|
|
887
|
+
);
|
|
888
|
+
const topViewedChartData = react.useMemo(
|
|
889
|
+
() => ((performance == null ? void 0 : performance.topViewedProducts) ?? []).slice(0, 7).map((row) => ({
|
|
890
|
+
...row,
|
|
891
|
+
product_title: truncateLabel(row.product_title, 24)
|
|
892
|
+
})).reverse(),
|
|
893
|
+
[performance]
|
|
894
|
+
);
|
|
895
|
+
if (summaryLoading) {
|
|
896
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
897
|
+
"div",
|
|
898
|
+
{
|
|
899
|
+
className: "flex items-center justify-center min-h-[320px]",
|
|
900
|
+
role: "status",
|
|
901
|
+
"aria-label": "Loading product analytics",
|
|
902
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading…" })
|
|
903
|
+
}
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
if (summaryError || !summary) {
|
|
907
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: summaryError ?? "Failed to load product analytics" }) });
|
|
908
|
+
}
|
|
909
|
+
const viewsConnected = summary.productViewsConnected || (overTime == null ? void 0 : overTime.productViewsConnected) === true || (topSellers == null ? void 0 : topSellers.productViewsConnected) === true || (performance == null ? void 0 : performance.productViewsConnected) === true;
|
|
910
|
+
const selectedChannelLabel = salesChannelId === "all" ? "All channels" : ((_a = salesChannels.find((channel) => channel.id === salesChannelId)) == null ? void 0 : _a.name) ?? "Selected channel";
|
|
911
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-8", children: [
|
|
912
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4", children: [
|
|
913
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
914
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Products" }),
|
|
915
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm mt-1", children: "Track units sold, revenue, best sellers, and product visit behavior." })
|
|
916
|
+
] }),
|
|
917
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
918
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
919
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "products-channel-filter", className: "text-ui-fg-muted text-sm", children: "Channel" }),
|
|
920
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
921
|
+
"select",
|
|
922
|
+
{
|
|
923
|
+
id: "products-channel-filter",
|
|
924
|
+
value: salesChannelId,
|
|
925
|
+
onChange: (event) => setSalesChannelId(event.target.value),
|
|
926
|
+
disabled: filtersLoading,
|
|
927
|
+
className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
928
|
+
children: [
|
|
929
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All channels" }),
|
|
930
|
+
salesChannels.map((channel) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: channel.id, children: channel.name }, channel.id))
|
|
931
|
+
]
|
|
932
|
+
}
|
|
933
|
+
)
|
|
934
|
+
] }),
|
|
935
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
936
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "products-summary-period", className: "text-ui-fg-muted text-sm", children: "Period" }),
|
|
937
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
938
|
+
"select",
|
|
939
|
+
{
|
|
940
|
+
id: "products-summary-period",
|
|
941
|
+
value: summaryDays,
|
|
942
|
+
onChange: (event) => setSummaryDays(event.target.value),
|
|
943
|
+
className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
944
|
+
children: SUMMARY_PERIODS.map((period) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: period.value, children: period.label }, period.value))
|
|
945
|
+
}
|
|
946
|
+
)
|
|
947
|
+
] }),
|
|
948
|
+
summaryDays !== "all" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
949
|
+
"button",
|
|
950
|
+
{
|
|
951
|
+
type: "button",
|
|
952
|
+
onClick: () => setSummaryDays("all"),
|
|
953
|
+
className: "text-xs text-ui-fg-muted hover:text-ui-fg-base transition-colors",
|
|
954
|
+
"aria-label": "Show all products analytics",
|
|
955
|
+
children: "Clear filter"
|
|
956
|
+
}
|
|
957
|
+
),
|
|
958
|
+
salesChannelId !== "all" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
959
|
+
"button",
|
|
960
|
+
{
|
|
961
|
+
type: "button",
|
|
962
|
+
onClick: () => setSalesChannelId("all"),
|
|
963
|
+
className: "text-xs text-ui-fg-muted hover:text-ui-fg-base transition-colors",
|
|
964
|
+
"aria-label": "Show all sales channels",
|
|
965
|
+
children: "Clear channel"
|
|
966
|
+
}
|
|
967
|
+
)
|
|
968
|
+
] })
|
|
969
|
+
] }),
|
|
970
|
+
filtersError && /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger text-sm", children: filtersError }) }),
|
|
971
|
+
!viewsConnected && /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Product views are unavailable until the `medusa-product-helper` tracking module is registered and storefront page views are being recorded." }) }),
|
|
972
|
+
salesChannelId !== "all" && viewsConnected && /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-sm", children: [
|
|
973
|
+
"Showing product analytics for ",
|
|
974
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: selectedChannelLabel }),
|
|
975
|
+
". Product views are scoped to products assigned to this sales channel."
|
|
976
|
+
] }) }),
|
|
977
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4", children: [
|
|
978
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
979
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Units sold" }),
|
|
980
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: summary.unitsSold.toLocaleString() })
|
|
981
|
+
] }),
|
|
982
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
983
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Product revenue" }),
|
|
984
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formatCurrency(summary.productRevenue) })
|
|
985
|
+
] }),
|
|
986
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
987
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Orders with product sales" }),
|
|
988
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: summary.ordersWithProducts.toLocaleString() })
|
|
989
|
+
] }),
|
|
990
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
991
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Active products sold" }),
|
|
992
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: summary.activeProductsSold.toLocaleString() })
|
|
993
|
+
] }),
|
|
994
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
995
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "Product views" }),
|
|
996
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: summary.totalProductViews.toLocaleString() })
|
|
997
|
+
] }),
|
|
998
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-4", children: [
|
|
999
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm", children: "View / order ratio" }),
|
|
1000
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: formatRatio(summary.viewToOrderRatio) })
|
|
1001
|
+
] })
|
|
1002
|
+
] }),
|
|
1003
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
1004
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 mb-4", children: [
|
|
1005
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1006
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Sales and views over time" }),
|
|
1007
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm mt-1", children: "Units sold and revenue are always shown. Product views appear when tracking data is available." })
|
|
1008
|
+
] }),
|
|
1009
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1010
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "products-graph-period", className: "text-ui-fg-muted text-sm", children: "Range" }),
|
|
1011
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1012
|
+
"select",
|
|
1013
|
+
{
|
|
1014
|
+
id: "products-graph-period",
|
|
1015
|
+
value: graphPeriod,
|
|
1016
|
+
onChange: (event) => setGraphPeriod(event.target.value),
|
|
1017
|
+
className: "h-9 rounded-md border border-ui-border-base bg-transparent px-3 text-sm text-ui-fg-base outline-none transition focus:ring-2 focus:ring-ui-fg-interactive",
|
|
1018
|
+
children: GRAPH_PERIODS.map((period) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: period.value, children: period.label }, period.value))
|
|
1019
|
+
}
|
|
1020
|
+
)
|
|
1021
|
+
] })
|
|
1022
|
+
] }),
|
|
1023
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-80", children: overTimeLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading chart…" }) }) : overTimeError || !overTime ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: overTimeError ?? "Failed to load product trend" }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.LineChart, { data: overTime.series, children: [
|
|
1024
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", vertical: false }),
|
|
1025
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { dataKey: "label" }),
|
|
1026
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1027
|
+
recharts.YAxis,
|
|
1028
|
+
{
|
|
1029
|
+
yAxisId: "counts",
|
|
1030
|
+
allowDecimals: false,
|
|
1031
|
+
tickFormatter: (value) => Math.floor(Number(value)).toLocaleString()
|
|
1032
|
+
}
|
|
1033
|
+
),
|
|
1034
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1035
|
+
recharts.YAxis,
|
|
1036
|
+
{
|
|
1037
|
+
yAxisId: "revenue",
|
|
1038
|
+
orientation: "right",
|
|
1039
|
+
tickFormatter: (value) => formatCurrency(Number(value))
|
|
1040
|
+
}
|
|
1041
|
+
),
|
|
1042
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(TrendTooltip, {}) }),
|
|
1043
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, {}),
|
|
1044
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1045
|
+
recharts.Line,
|
|
1046
|
+
{
|
|
1047
|
+
yAxisId: "counts",
|
|
1048
|
+
type: "monotone",
|
|
1049
|
+
dataKey: "units_sold",
|
|
1050
|
+
name: "Units sold",
|
|
1051
|
+
stroke: SALES_COLOR,
|
|
1052
|
+
strokeWidth: 2,
|
|
1053
|
+
dot: false
|
|
1054
|
+
}
|
|
1055
|
+
),
|
|
1056
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1057
|
+
recharts.Line,
|
|
1058
|
+
{
|
|
1059
|
+
yAxisId: "counts",
|
|
1060
|
+
type: "monotone",
|
|
1061
|
+
dataKey: "views",
|
|
1062
|
+
name: "Views",
|
|
1063
|
+
stroke: VIEWS_COLOR,
|
|
1064
|
+
strokeWidth: 2,
|
|
1065
|
+
dot: false
|
|
1066
|
+
}
|
|
1067
|
+
),
|
|
1068
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1069
|
+
recharts.Line,
|
|
1070
|
+
{
|
|
1071
|
+
yAxisId: "revenue",
|
|
1072
|
+
type: "monotone",
|
|
1073
|
+
dataKey: "revenue",
|
|
1074
|
+
name: "Revenue",
|
|
1075
|
+
stroke: REVENUE_COLOR,
|
|
1076
|
+
strokeWidth: 2,
|
|
1077
|
+
dot: false
|
|
1078
|
+
}
|
|
1079
|
+
)
|
|
1080
|
+
] }) }) })
|
|
1081
|
+
] }),
|
|
1082
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 xl:grid-cols-2 gap-4", children: [
|
|
1083
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
1084
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-4 mb-4", children: [
|
|
1085
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1086
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Best sellers" }),
|
|
1087
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm mt-1", children: "Ranked by units sold." })
|
|
1088
|
+
] }),
|
|
1089
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-2", children: TOP_SELLER_PERIODS.map((period) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1090
|
+
ui.Button,
|
|
1091
|
+
{
|
|
1092
|
+
variant: topSellerPeriod === period.value ? "secondary" : "transparent",
|
|
1093
|
+
size: "small",
|
|
1094
|
+
onClick: () => setTopSellerPeriod(period.value),
|
|
1095
|
+
children: period.label
|
|
1096
|
+
},
|
|
1097
|
+
period.value
|
|
1098
|
+
)) })
|
|
1099
|
+
] }),
|
|
1100
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-80", children: topSellersLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading best sellers…" }) }) : topSellersError || !topSellers ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: topSellersError ?? "Failed to load best sellers" }) }) : topSellerChartData.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "No product sales found for this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.BarChart, { data: topSellerChartData, layout: "vertical", margin: { left: 8, right: 8 }, children: [
|
|
1101
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", horizontal: false }),
|
|
1102
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { type: "number", allowDecimals: false }),
|
|
1103
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1104
|
+
recharts.YAxis,
|
|
1105
|
+
{
|
|
1106
|
+
type: "category",
|
|
1107
|
+
dataKey: "product_title",
|
|
1108
|
+
width: 160,
|
|
1109
|
+
tickLine: false
|
|
1110
|
+
}
|
|
1111
|
+
),
|
|
1112
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(ProductBarTooltip, {}) }),
|
|
1113
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: "units_sold", fill: SALES_COLOR, radius: [0, 6, 6, 0] })
|
|
1114
|
+
] }) }) })
|
|
1115
|
+
] }),
|
|
1116
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
1117
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "mb-4", children: "Top seller breakdown" }),
|
|
1118
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
1119
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1120
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Product" }),
|
|
1121
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Units" }),
|
|
1122
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Orders" }),
|
|
1123
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Revenue" })
|
|
1124
|
+
] }) }),
|
|
1125
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Body, { children: [
|
|
1126
|
+
((topSellers == null ? void 0 : topSellers.products) ?? []).slice(0, 8).map((product) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1127
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.product_title }),
|
|
1128
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.units_sold.toLocaleString() }),
|
|
1129
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.order_count.toLocaleString() }),
|
|
1130
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: formatCurrency(product.revenue) })
|
|
1131
|
+
] }, product.product_id)),
|
|
1132
|
+
!topSellersLoading && (((_b = topSellers == null ? void 0 : topSellers.products) == null ? void 0 : _b.length) ?? 0) === 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1133
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: "No best seller data yet." }),
|
|
1134
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1135
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1136
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {})
|
|
1137
|
+
] })
|
|
1138
|
+
] })
|
|
1139
|
+
] })
|
|
1140
|
+
] })
|
|
1141
|
+
] }),
|
|
1142
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 xl:grid-cols-2 gap-4", children: [
|
|
1143
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
1144
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "mb-1", children: "Most viewed products" }),
|
|
1145
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted text-sm mb-4", children: "View activity is sourced from the existing product-helper tracking module." }),
|
|
1146
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-80", children: performanceLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Loading product views…" }) }) : performanceError || !performance ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-danger", children: performanceError ?? "Failed to load product performance" }) }) : !performance.productViewsConnected ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "Product view tracking is not available in this environment." }) }) : topViewedChartData.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "No product views found for this range." }) }) : /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children: /* @__PURE__ */ jsxRuntime.jsxs(recharts.BarChart, { data: topViewedChartData, layout: "vertical", margin: { left: 8, right: 8 }, children: [
|
|
1147
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.CartesianGrid, { strokeDasharray: "3 3", horizontal: false }),
|
|
1148
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.XAxis, { type: "number", allowDecimals: false }),
|
|
1149
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1150
|
+
recharts.YAxis,
|
|
1151
|
+
{
|
|
1152
|
+
type: "category",
|
|
1153
|
+
dataKey: "product_title",
|
|
1154
|
+
width: 160,
|
|
1155
|
+
tickLine: false
|
|
1156
|
+
}
|
|
1157
|
+
),
|
|
1158
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { content: /* @__PURE__ */ jsxRuntime.jsx(ProductBarTooltip, {}) }),
|
|
1159
|
+
/* @__PURE__ */ jsxRuntime.jsx(recharts.Bar, { dataKey: "total_views", fill: VIEWS_COLOR, radius: [0, 6, 6, 0] })
|
|
1160
|
+
] }) }) })
|
|
1161
|
+
] }),
|
|
1162
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
|
|
1163
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", className: "mb-4", children: "View-to-sales opportunities" }),
|
|
1164
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
1165
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1166
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Product" }),
|
|
1167
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Views" }),
|
|
1168
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Units" }),
|
|
1169
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Views / unit" })
|
|
1170
|
+
] }) }),
|
|
1171
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Body, { children: [
|
|
1172
|
+
((performance == null ? void 0 : performance.viewOpportunities) ?? []).slice(0, 8).map((product) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1173
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.product_title }),
|
|
1174
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.total_views.toLocaleString() }),
|
|
1175
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: product.units_sold.toLocaleString() }),
|
|
1176
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: formatRatio(product.views_per_unit) })
|
|
1177
|
+
] }, product.product_id)),
|
|
1178
|
+
!performanceLoading && !performanceError && (((_c = performance == null ? void 0 : performance.viewOpportunities) == null ? void 0 : _c.length) ?? 0) === 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
1179
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: "No product view opportunities yet." }),
|
|
1180
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1181
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {}),
|
|
1182
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, {})
|
|
1183
|
+
] })
|
|
1184
|
+
] })
|
|
1185
|
+
] })
|
|
1186
|
+
] })
|
|
1187
|
+
] })
|
|
1188
|
+
] });
|
|
1189
|
+
}
|
|
607
1190
|
const ANALYTICS_MODULES = [
|
|
608
1191
|
{ id: "orders", label: "Orders" },
|
|
609
|
-
{ id: "customers", label: "Customers" }
|
|
1192
|
+
{ id: "customers", label: "Customers" },
|
|
1193
|
+
{ id: "products", label: "Products" }
|
|
610
1194
|
];
|
|
611
1195
|
const AnalyticsPage = () => {
|
|
612
1196
|
const [activeModule, setActiveModule] = react.useState("orders");
|
|
@@ -623,7 +1207,8 @@ const AnalyticsPage = () => {
|
|
|
623
1207
|
m.id
|
|
624
1208
|
)) }),
|
|
625
1209
|
activeModule === "orders" && /* @__PURE__ */ jsxRuntime.jsx(OrdersDashboard, {}),
|
|
626
|
-
activeModule === "customers" && /* @__PURE__ */ jsxRuntime.jsx(CustomersDashboard, {})
|
|
1210
|
+
activeModule === "customers" && /* @__PURE__ */ jsxRuntime.jsx(CustomersDashboard, {}),
|
|
1211
|
+
activeModule === "products" && /* @__PURE__ */ jsxRuntime.jsx(ProductsDashboard, {})
|
|
627
1212
|
] }) }) }) });
|
|
628
1213
|
};
|
|
629
1214
|
const config = adminSdk.defineRouteConfig({
|