diff --git a/dashboard/src/components/dashboard/KlaviyoStats.jsx b/dashboard/src/components/dashboard/KlaviyoStats.jsx
deleted file mode 100644
index 0b9dc78..0000000
--- a/dashboard/src/components/dashboard/KlaviyoStats.jsx
+++ /dev/null
@@ -1,2064 +0,0 @@
-import React, { useState, useEffect, useMemo, useCallback, useRef } from "react";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Skeleton } from "@/components/ui/skeleton";
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import {
- DollarSign,
- ShoppingCart,
- Package,
- Clock,
- Map,
- Tags,
- Star,
- XCircle,
- TrendingUp,
- AlertCircle,
- Box,
- RefreshCcw,
- CircleDollarSign,
- ArrowDown,
- ArrowUp,
- MapPin
-} from "lucide-react";
-import { DateTime } from "luxon";
-import { TimeRangeSelect } from "@/components/dashboard/TimeRangeSelect";
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@/components/ui/table";
-import {
- BarChart,
- Bar,
- XAxis,
- YAxis,
- CartesianGrid,
- Tooltip,
- ResponsiveContainer,
- LineChart,
- Line,
- Area,
- AreaChart,
- ComposedChart,
-} from "recharts";
-
-const CHART_COLORS = [
- "hsl(var(--primary))",
- "hsl(var(--secondary))",
- "#8b5cf6",
- "#10b981",
- "#f59e0b",
- "#ef4444",
-];
-// Formatting utilities
-const formatCurrency = (value, minimumFractionDigits = 0) => {
- if (!value || isNaN(value)) return "$0";
- return new Intl.NumberFormat("en-US", {
- style: "currency",
- currency: "USD",
- minimumFractionDigits,
- maximumFractionDigits: minimumFractionDigits,
- }).format(value);
-};
-
-const formatHour = (hour) => {
- const hourNum = parseInt(hour);
- if (hourNum === 0) return "12am";
- if (hourNum === 12) return "12pm";
- if (hourNum > 12) return `${hourNum - 12}pm`;
- return `${hourNum}am`;
-};
-
-const normalizePaymentMethod = (method) => {
- if (method.toLowerCase().includes("credit card")) return "Credit Card";
- if (method.toLowerCase().includes("gift")) return "Gift Card";
- return method;
-};
-
-const formatPercent = (value, total) => {
- if (!total || !value) return "0%";
- return `${((value / total) * 100).toFixed(1)}%`;
-};
-
-const getPreviousPeriod = (timeRange) => {
- switch (timeRange) {
- case 'today': return 'yesterday';
- case 'yesterday': return 'twoDaysAgo';
- case 'last7days': return 'last7days';
- case 'last30days': return 'last30days';
- case 'last90days': return 'last90days';
- default: return timeRange;
- }
-};
-
-const formatShipMethod = (method) => {
- if (!method) return "Standard Shipping";
- return method
- .replace("usps_", "USPS ")
- .replace("ups_", "UPS ")
- .replace("fedex_", "FedEx ")
- .replace(/_/g, " ")
- .split(" ")
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
- .join(" ");
-};
-
-// Component building blocks
-const DetailCard = ({ title, icon: Icon, children }) => (
-
-
- {Icon && }
-
{title}
-
- {children}
-
-);
-
-const SkeletonMetricCard = () => (
-
-
-
-
-
-
-
-
-
-
-);
-
-const SkeletonChart = ({ type = "line" }) => (
-
-
-
- {type === "bar" ? (
-
- {[...Array(24)].map((_, i) => (
-
- ))}
-
- ) : type === "range" ? (
-
-
- {[...Array(5)].map((_, i) => (
-
- ))}
-
-
-
- ) : (
-
- {[...Array(5)].map((_, i) => (
-
- ))}
-
-
- )}
-
-
-
-);
-
-
-const SkeletonTable = ({ rows = 5 }) => (
-
- {/* Header */}
-
- {[...Array(3)].map((_, i) => (
-
- ))}
-
- {/* Rows */}
- {[...Array(rows)].map((_, i) => (
-
- {[...Array(3)].map((_, j) => (
-
- ))}
-
- ))}
-
-);
-
-const SkeletonMetricGrid = () => (
-
- {Array(12)
- .fill(0)
- .map((_, i) => (
-
- ))}
-
-);
-
-const StatRow = ({ label, value, change, emphasize }) => (
-
-
{label}
-
-
- {value}
-
- {change && (
- 0 ? "text-green-500" : "text-red-500"
- }`}
- >
- {change > 0 ? "↑" : "↓"} {Math.abs(change)}%
-
- )}
-
-
-);
-
-const DetailSection = ({ title, children }) => (
-
-
{title}
- {children}
-
-);
-
-const formatChartDate = (value) => {
- if (!value) return '';
- try {
- return DateTime.fromISO(value).setZone('America/New_York').toFormat('LLL d');
- } catch (error) {
- console.error("[KLAVIYO STATS] Date formatting error:", error);
- return value;
- }
-};
-
-export const RevenueDetails = ({ metrics, isLoading }) => {
- if (isLoading) {
- return ;
- }
-
- // Ensure we have valid daily data
- if (!metrics?.revenue?.daily || !Array.isArray(metrics.revenue.daily)) {
- console.error("[KLAVIYO STATS] Invalid daily revenue data:", metrics?.revenue);
- return null;
- }
-
- // Sort daily data by date to ensure correct order
- const revenueData = metrics.revenue.daily
- .sort((a, b) => DateTime.fromISO(a.date).toMillis() - DateTime.fromISO(b.date).toMillis())
- .map(day => ({
- date: day.date,
- revenue: parseFloat(day.value) || 0,
- orders: parseInt(day.orders) || 0,
- items: parseInt(day.items) || 0
- }));
-
- console.log("[KLAVIYO STATS] Processed revenue data:", {
- totalDays: revenueData.length,
- dates: revenueData.map(d => d.date),
- totals: {
- revenue: revenueData.reduce((sum, day) => sum + day.revenue, 0),
- orders: revenueData.reduce((sum, day) => sum + day.orders, 0),
- items: revenueData.reduce((sum, day) => sum + day.items, 0)
- }
- });
-
- return (
-
-
-
-
-
- formatCurrency(value, 0)}
- width={80}
- tick={{ fontSize: 12 }}
- tickLine={false}
- axisLine={false}
- />
- [formatCurrency(value, 0), "Revenue"]}
- />
-
-
-
-
- );
-};
-
-export const OrdersDetails = ({ metrics, isLoading }) => {
- if (isLoading) {
- return (
-
- );
- }
-
- const orderData =
- metrics.revenue?.daily?.map((day) => ({
- date: day.date,
- orders: day.orders || 0,
- })) || [];
-
- const hourlyData =
- metrics.hourly_distribution
- ?.map((count, hour) => ({
- hour,
- orders: count,
- }))
- .filter((data) => data.orders > 0) || [];
-
- return (
-
-
-
-
-
-
-
- [value.toLocaleString(), "Orders"]}
- />
-
-
-
-
-
- {hourlyData.length > 0 && (
-
-
Hourly Distribution
-
-
-
-
-
-
- [value.toLocaleString(), "Orders"]}
- labelFormatter={formatHour}
- />
-
-
-
-
-
- )}
-
- );
-};
-
-export const AverageOrderDetails = ({ metrics, isLoading }) => {
- if (isLoading) {
- return ;
- }
-
- const avgOrderData =
- metrics.revenue?.daily?.map((day) => ({
- date: day.date,
- average: day.orders > 0 ? day.value / day.orders : 0,
- })) || [];
-
- return (
- formatCurrency(value, 0)}
- minDays={30}
- />
- );
-};
-
-const DataTable = ({ title, data, isLoading }) => {
- if (isLoading) {
- return (
-
-
-
-
-
-
- {Array(10).fill(0).map((_, i) => (
-
- ))}
-
-
-
- );
- }
-
- return (
-
-
- {title}
-
-
-
-
-
- Name
- Count
-
-
-
- {data.map(([name, count]) => (
-
- {name}
- {count}
-
- ))}
-
-
-
-
- );
-};
-
-export const BrandsAndCategoriesDetails = ({ metrics, isLoading }) => {
- const brands = Object.entries(metrics?.orders?.brands || {})
- .sort(([, a], [, b]) => b - a)
- .slice(0, 20);
-
- const categories = Object.entries(metrics?.orders?.categories || {})
- .sort(([, a], [, b]) => b - a)
- .slice(0, 20);
-
- return (
-
-
-
-
- );
-};
-export const ShippedOrdersDetails = ({ metrics, isLoading }) => {
- if (isLoading) {
- return (
-
-
-
-
-
- {Array(8)
- .fill(0)
- .map((_, i) => (
-
- ))}
-
-
-
- );
- }
-
- const shippedData = metrics.revenue?.daily?.map((day) => ({
- date: day.date,
- shipped_orders: day.shipped_orders || 0,
- })) || [];
-
- const locations = Object.entries(metrics.orders?.shipping_states || {})
- .sort(([, a], [, b]) => b - a)
- .filter(([location]) => location);
-
- return (
-
-
-
-
-
-
-
- [value.toLocaleString(), "Orders"]}
- />
-
-
-
-
-
-
-
Top Shipping Locations
-
- {locations.map(([location, count]) => (
-
- {location}
- {count.toLocaleString()}
-
- ))}
-
-
-
- );
-};
-export const PreOrdersDetails = ({ metrics, isLoading }) => {
- if (isLoading) {
- return ;
- }
-
- const preOrderData =
- metrics.revenue?.daily?.map((day) => ({
- date: day.date,
- percentage:
- day.orders > 0 ? ((day.pre_orders || 0) / day.orders) * 100 : 0,
- })) || [];
-
- return (
-
-
-
-
-
- `${Math.round(value)}%`}
- width={60}
- tick={{ fontSize: 12 }}
- tickLine={false}
- axisLine={false}
- />
- } />
-
-
-
-
- );
-};
-export const LocalPickupDetails = ({ metrics, isLoading }) => {
- if (isLoading) {
- return ;
- }
-
- const pickupData =
- metrics.revenue?.daily?.map((day) => ({
- date: day.date,
- percentage:
- day.orders > 0 ? ((day.local_pickup || 0) / day.orders) * 100 : 0,
- })) || [];
-
- return (
-
-
-
-
-
- `${Math.round(value)}%`}
- width={60}
- tick={{ fontSize: 12 }}
- tickLine={false}
- axisLine={false}
- />
- } />
-
-
-
-
- );
-};
-export const OnHoldDetails = ({ metrics, isLoading }) => {
- if (isLoading) {
- return ;
- }
-
- const onHoldData =
- metrics.revenue?.daily?.map((day) => ({
- date: day.date,
- percentage:
- day.orders > 0 ? ((day.status?.on_hold || 0) / day.orders) * 100 : 0,
- })) || [];
-
- return (
-
-
-
-
-
- `${Math.round(value)}%`}
- width={60}
- tick={{ fontSize: 12 }}
- tickLine={false}
- axisLine={false}
- />
- } />
-
-
-
-
- );
-};
-
-export const ShippingDetails = ({ metrics }) => {
- const locations = Object.entries(
- metrics.orders?.shipping_locations || {}
- ).sort(([, a], [, b]) => b - a);
- const shippedOrders = metrics.orders?.shipped_orders || 0;
- const totalOrders = metrics.orders?.total || 0;
-
- return (
-
-
-
-
-
-
-
- {Object.entries(metrics.orders?.shipping_methods || {})
- .sort(([, a], [, b]) => b - a)
- .map(([method, count]) => (
-
-
{formatShipMethod(method)}
-
- {count.toLocaleString()}
-
- ({((count / shippedOrders) * 100).toFixed(1)}%)
-
-
-
- ))}
-
-
-
-
-
- {locations.map(([location, count]) => (
-
-
{location}
-
- {count.toLocaleString()}
-
- ({((count / totalOrders) * 100).toFixed(1)}%)
-
-
-
- ))}
-
-
-
- );
-};
-
-export const ProductsDetails = ({ metrics }) => {
- const brands = Object.entries(metrics.orders?.brands || {}).sort(
- ([, a], [, b]) => b - a
- );
- const categories = Object.entries(metrics.orders?.categories || {}).sort(
- ([, a], [, b]) => b - a
- );
-
- return (
-
-
-
- {brands.map(([brand, count], index) => (
-
-
- {index + 1}.
- {brand}
-
-
- {count.toLocaleString()}
-
- ({((count / metrics.orders.items_total) * 100).toFixed(1)}%)
-
-
-
- ))}
-
-
-
-
-
- {categories.map(([category, count], index) => (
-
-
- {index + 1}.
- {category}
-
-
- {count.toLocaleString()}
-
- ({((count / metrics.orders.items_total) * 100).toFixed(1)}%)
-
-
-
- ))}
-
-
-
- );
-};
-
-export const RefundsDetails = ({ metrics, isLoading }) => {
- if (isLoading) {
- return ;
- }
-
- const refundData =
- metrics.revenue?.daily?.map((day) => ({
- date: day.date,
- amount: day.refunds?.total || 0,
- })) || [];
-
- return (
- formatCurrency(value, 0)}
- minDays={30}
- />
- );
-};
-
-export const PeakHourDetails = ({ metrics, isLoading }) => {
- if (isLoading) {
- return ;
- }
-
- const hourlyData =
- metrics.hourly_distribution
- ?.map((count, hour) => ({
- hour,
- orders: count,
- }))
- .filter((data) => data.orders > 0) || [];
-
- return (
-
-
-
-
-
-
- [value, "Orders"]}
- labelFormatter={formatHour}
- content={}
- />
-
-
-
-
- );
-};
-export const CancellationsDetails = ({ metrics, isLoading }) => {
- if (isLoading) {
- return ;
- }
- const cancelData =
- metrics.revenue?.daily?.map((day) => ({
- date: day.date,
- amount: day.cancellations?.total || 0,
- })) || [];
-
- return (
- formatCurrency(value, 0)}
- minDays={30}
- />
- );
-};
-
-export const OrderRangeDetails = ({ metrics, isLoading }) => {
- if (isLoading) {
- return ;
- }
-
- const rangeData = metrics.revenue?.daily
- ?.map((day) => {
- if (!day.orders_list?.length) return null;
-
- const validOrders = day.orders_list
- .map((order) => parseFloat(order.TotalAmount))
- .filter((amount) => amount > 0);
-
- if (!validOrders.length) return null;
-
- return {
- date: day.date,
- min: Math.min(...validOrders),
- max: Math.max(...validOrders),
- avg: validOrders.reduce((a, b) => a + b, 0) / validOrders.length,
- };
- })
- .filter(Boolean);
-
- return (
-
-
-
-
-
- formatCurrency(value, 0)}
- width={80}
- tick={{ fontSize: 12 }}
- tickLine={false}
- axisLine={false}
- />
- } />
-
-
-
-
-
-
- );
-};
-const MetricCard = React.memo(
- ({ title, value, subtitle, icon: Icon, iconColor, onClick, secondaryValue }) => (
-
-
-
- {title}
-
-
-
-
-
- {value}
-
-
- {subtitle}
-
- {secondaryValue && (
-
- {secondaryValue}
-
- )}
-
-
- )
-);
-
-MetricCard.displayName = "MetricCard";
-
-const ChartTooltip = ({ active, payload, label }) => {
- if (!active || !payload?.length) return null;
-
- return (
-
-
{formatChartDate(label)}
- {payload.map((entry, index) => (
-
- {entry.name}:
- {typeof entry.value === "number"
- ? entry.name.toLowerCase().includes("revenue")
- ? formatCurrency(entry.value)
- : entry.value.toLocaleString()
- : entry.value}
-
- ))}
-
- );
-};
-
-const TimeSeriesChart = ({
- data,
- valueKey,
- label,
- type = "line",
- valueFormatter = (v) => v,
-}) => {
- const ChartComponent =
- type === "line" ? LineChart : type === "bar" ? BarChart : AreaChart;
-
- return (
-
-
-
-
-
-
- } />
- {type === "line" && (
-
- )}
- {type === "bar" && (
-
- )}
- {type === "area" && (
-
-
-
-
-
-
-
-
- )}
-
-
-
- );
-};
-
-// Status components
-const OrderStatusTags = React.memo(({ details }) => {
- if (!details) return null;
-
- const tags = [
- {
- condition: details.HasPreorder,
- label: "Pre-order",
- color: "bg-blue-100 dark:bg-blue-900/20 text-blue-800 dark:text-blue-300",
- },
- {
- condition: details.LocalPickup,
- label: "Local Pickup",
- color:
- "bg-purple-100 dark:bg-purple-900/20 text-purple-800 dark:text-purple-300",
- },
- {
- condition: details.IsOnHold,
- label: "On Hold",
- color:
- "bg-yellow-100 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-300",
- },
- {
- condition: details.HasDigitalGC,
- label: "Gift Card",
- color:
- "bg-indigo-100 dark:bg-indigo-900/20 text-indigo-800 dark:text-indigo-300",
- },
- ];
-
- return (
-
- {tags.map(
- ({ condition, label, color }, index) =>
- condition && (
-
- {label}
-
- )
- )}
-
- );
-});
-
-OrderStatusTags.displayName = "OrderStatusTags";
-
-const PromotionalInfo = React.memo(({ details }) => {
- if (!details?.PromosUsedReg?.length && !details?.PointsDiscount) return null;
-
- return (
-
-
- Savings Applied
-
-
- {details.PromosUsedReg?.map(([code, amount], index) => (
-
-
- {code}
-
-
- -{formatCurrency(amount)}
-
-
- ))}
- {details.PointsDiscount > 0 && (
-
-
- Points Discount
-
-
- -{formatCurrency(details.PointsDiscount)}
-
-
- )}
-
-
- );
-});
-
-PromotionalInfo.displayName = "PromotionalInfo";
-
-
-const ShippingInfo = React.memo(({ details }) => (
-
-
-
Shipping Address
-
-
{details.ShippingName}
-
{details.ShippingStreet1}
- {details.ShippingStreet2 &&
{details.ShippingStreet2}
}
-
- {details.ShippingCity}, {details.ShippingState} {details.ShippingZip}
-
- {details.ShippingCountry !== "US" && (
-
{details.ShippingCountry}
- )}
-
-
- {details.TrackingNumber && (
-
-
- Tracking Information
-
-
-
- {formatShipMethod(details.ShipMethod)}
-
-
- {details.TrackingNumber}
-
-
-
- )}
-
-));
-
-ShippingInfo.displayName = "ShippingInfo";
-
-const OrderSummary = React.memo(({ details }) => (
-
-
-
-
Subtotal
-
-
- Items ({details.Items?.length || 0})
- {formatCurrency(details.Subtotal)}
-
- {details.PointsDiscount > 0 && (
-
- Points Discount
- -{formatCurrency(details.PointsDiscount)}
-
- )}
- {details.TotalDiscounts > 0 && (
-
- Discounts
- -{formatCurrency(details.TotalDiscounts)}
-
- )}
-
-
-
-
Shipping
-
-
- Shipping Cost
- {formatCurrency(details.ShippingTotal)}
-
- {details.RushFee > 0 && (
-
- Rush Fee
- {formatCurrency(details.RushFee)}
-
- )}
- {details.SalesTax > 0 && (
-
- Sales Tax
- {formatCurrency(details.SalesTax)}
-
- )}
-
-
-
-
-
-
-
-
Total
- {details.TotalSavings > 0 && (
-
- You saved {formatCurrency(details.TotalSavings)}
-
- )}
-
-
- {formatCurrency(details.TotalAmount)}
-
-
-
- {details.Payments?.length > 0 && (
-
-
- Payment Details
-
-
- {details.Payments.map(([method, amount], index) => (
-
- {method}
- {formatCurrency(amount)}
-
- ))}
-
-
- )}
-
-
-
-
-));
-
-OrderSummary.displayName = "OrderSummary";
-
-// Loading and empty states
-const LoadingState = () => (
-
-);
-
-const ErrorState = ({ message }) => (
-
-
-
Error Loading Metrics
-
{message}
-
-);
-
-// Main component
-const KlaviyoStats = ({ className }) => {
- const [timeRange, setTimeRange] = useState("today");
- const [timeRangeChanging, setTimeRangeChanging] = useState(false);
- const [metrics, setMetrics] = useState(null);
- const [previousMetrics, setPreviousMetrics] = useState(null);
- const [error, setError] = useState(null);
- const [selectedMetric, setSelectedMetric] = useState(null);
- const [lastUpdate, setLastUpdate] = useState(null);
-
- // Extended data pre-loaded in background
- const [extendedData, setExtendedData] = useState(null);
- const [isLoadingExtraData, setIsLoadingExtraData] = useState(false);
- const [isLoadingPrevious, setIsLoadingPrevious] = useState(false);
-
- const handleTimeRangeChange = useCallback(async (newRange) => {
- const validRanges = [
- 'today',
- 'yesterday',
- 'last2days',
- 'last7days',
- 'last30days',
- 'last90days',
- 'previous7days',
- 'previous30days',
- 'previous90days'
- ];
-
- if (!validRanges.includes(newRange)) {
- console.error(`Invalid time range: ${newRange}`);
- return;
- }
- setTimeRangeChanging(true);
- setTimeRange(newRange);
- }, []);
-
- const isSingleDay = timeRange === "today" || timeRange === "yesterday";
-
- const processedMetrics = useMemo(() => {
- if (!metrics) return null;
-
- console.log("[KLAVIYO STATS] Processing metrics:", {
- hasMetrics: !!metrics,
- hasPreviousMetrics: !!previousMetrics,
- revenue: metrics.revenue,
- orders: metrics.orders,
- refunds: metrics.refunds,
- cancellations: metrics.cancellations,
- timeRange
- });
-
- const getComparison = (current, previous) => {
- if (!previous) return null;
- const diff = current - previous;
- return {
- diff,
- percent: previous ? (diff / previous) * 100 : 0,
- increased: diff > 0,
- };
- };
-
- // Ensure we have valid numbers for all metrics
- const currentRevenue = parseFloat(metrics.revenue?.total) || 0;
- const previousRevenue = parseFloat(previousMetrics?.revenue?.total) || 0;
- const revenueComparison = getComparison(currentRevenue, previousRevenue);
-
- const totalOrders = parseInt(metrics.orders?.total) || 0;
- const totalItems = parseInt(metrics.orders?.items_total) || 0;
- const shippedOrders = parseInt(metrics.orders?.shipped_orders) || 0;
- const onHoldCount = parseInt(metrics.orders?.status?.on_hold) || 0;
- const preOrderCount = parseInt(metrics.orders?.pre_orders) || 0;
- const localPickupCount = parseInt(metrics.orders?.local_pickup) || 0;
- const refundCount = parseInt(metrics.refunds?.count) || 0;
- const refundTotal = parseFloat(metrics.refunds?.total) || 0;
- const cancelCount = parseInt(metrics.cancellations?.count) || 0;
- const cancelTotal = parseFloat(metrics.cancellations?.total) || 0;
-
- const avgOrderValue = totalOrders > 0 ? currentRevenue / totalOrders : 0;
- const avgItems = totalOrders > 0 ? totalItems / totalOrders : 0;
-
- const processed = {
- revenue: {
- current: currentRevenue,
- previous: previousRevenue,
- comparison: revenueComparison,
- },
- orders: {
- current: totalOrders,
- items: totalItems,
- avgItems: avgItems,
- avgValue: avgOrderValue,
- shipped_orders: shippedOrders,
- locations: Object.keys(metrics.orders?.shipping_locations || {}).length,
- preOrders: {
- count: preOrderCount,
- percent: totalOrders > 0 ? (preOrderCount / totalOrders) * 100 : 0,
- },
- localPickup: {
- count: localPickupCount,
- percent: totalOrders > 0 ? (localPickupCount / totalOrders) * 100 : 0,
- },
- onHold: {
- count: onHoldCount,
- percent: totalOrders > 0 ? (onHoldCount / totalOrders) * 100 : 0,
- },
- largest: metrics.orders?.largest || { value: 0, items: 0 },
- smallest: metrics.orders?.smallest || { value: 0, items: 0 },
- },
- products: {
- brands: Object.keys(metrics.orders?.brands || {}).length,
- categories: Object.keys(metrics.orders?.categories || {}).length,
- },
- peak: metrics.peak_hour || null,
- bestDay: metrics.revenue?.best_day || null,
- refunds: {
- count: refundCount,
- total: refundTotal,
- },
- cancellations: {
- count: cancelCount,
- total: cancelTotal,
- },
- };
-
- console.log("[KLAVIYO STATS] Processed metrics:", processed);
- return processed;
-}, [metrics, previousMetrics, timeRange]);
-
- const RevenueCard = useMemo(() => {
- const comparison = processedMetrics?.revenue.comparison;
-
- return (
-
- ) : formatCurrency(processedMetrics?.revenue.current, 0)
- }
- subtitle={
-
- {timeRangeChanging || isLoadingPrevious ? (
-
- ) : previousMetrics ? (
- <>
-
{`prev: ${formatCurrency(previousMetrics.revenue?.total || 0, 0)}`}
- {comparison && (
-
- {comparison.increased ?
:
}
- {Math.abs(comparison.percent).toFixed(1)}%
-
- )}
- >
- ) : null}
-
- }
- icon={DollarSign}
- iconColor="text-green-500"
- onClick={() => setSelectedMetric("revenue")}
- />
- );
- }, [timeRangeChanging, isLoadingPrevious, processedMetrics, previousMetrics]);
-
- const hasFetchedExtendedData = useRef(false);
-
- const fetchExtendedData = useCallback(async () => {
- if (hasFetchedExtendedData.current) return; // Skip fetch if already fetched
- setIsLoadingExtraData(true);
-
- try {
- const extendedResponse = await fetch(`/api/klaviyo/metrics/last30days`);
- const extendedData = await extendedResponse.json();
- setExtendedData(extendedData);
- hasFetchedExtendedData.current = true; // Mark as fetched
- } catch (error) {
- console.error("Error fetching extended data:", error);
- } finally {
- setIsLoadingExtraData(false);
- }
-}, []);
-
- const fetchData = useCallback(async () => {
- console.log("[KLAVIYO STATS] Starting fetchData:", { timeRange });
- setTimeRangeChanging(true);
- setError(null);
-
- try {
- const currentResponse = await fetch(`/api/klaviyo/metrics/${timeRange}`);
- if (!currentResponse.ok) {
- throw new Error(`Failed to fetch current metrics: ${currentResponse.status}`);
- }
-
- const currentData = await currentResponse.json();
-
- // Log the raw response data
- console.log("[KLAVIYO STATS] Raw API Response:", {
- timeRange,
- revenue: currentData.revenue,
- daily: currentData.revenue?.daily?.map(day => ({
- date: day.date,
- value: day.value,
- orders: day.orders
- }))
- });
-
- // Ensure we have all required days in the data
- const today = DateTime.now().setZone('America/New_York');
- const expectedDays = timeRange === 'last7days' ? 7 :
- timeRange === 'last30days' ? 30 :
- timeRange === 'last90days' ? 90 : 1;
-
- const startDate = today.minus({ days: expectedDays - 1 }).startOf('day');
- const endDate = today.endOf('day');
-
- // Create an object to store expected dates
- const expectedDates = {};
- let currentDate = startDate;
- while (currentDate <= endDate) {
- const dateKey = currentDate.toFormat('yyyy-MM-dd');
- expectedDates[dateKey] = {
- date: dateKey,
- value: 0,
- orders: 0,
- items: 0,
- pre_orders: 0,
- local_pickup: 0,
- refunds: { total: 0, count: 0 },
- cancellations: { total: 0, count: 0 },
- status: { on_hold: 0, processing: 0, completed: 0 }
- };
- currentDate = currentDate.plus({ days: 1 });
- }
-
- // Log expected dates
- console.log("[KLAVIYO STATS] Expected dates:", {
- timeRange,
- dates: Object.keys(expectedDates)
- });
-
- // Merge existing data with expected dates
- if (currentData?.revenue?.daily) {
- currentData.revenue.daily.forEach(day => {
- if (expectedDates[day.date]) {
- expectedDates[day.date] = { ...expectedDates[day.date], ...day };
- }
- });
- }
-
- // Convert back to array and sort by date
- const sortedDaily = Object.values(expectedDates).sort((a, b) =>
- DateTime.fromISO(a.date).toMillis() - DateTime.fromISO(b.date).toMillis()
- );
-
- // Log the processed daily data
- console.log("[KLAVIYO STATS] Processed daily data:", {
- timeRange,
- daily: sortedDaily.map(day => ({
- date: day.date,
- value: day.value,
- orders: day.orders
- }))
- });
-
- // Update the daily data with the complete set
- currentData.revenue = {
- ...currentData.revenue,
- daily: sortedDaily
- };
-
- // Recalculate totals based on daily data
- const totals = sortedDaily.reduce((acc, day) => ({
- revenue: acc.revenue + (parseFloat(day.value) || 0),
- orders: acc.orders + (parseInt(day.orders) || 0),
- items: acc.items + (parseInt(day.items) || 0),
- pre_orders: acc.pre_orders + (parseInt(day.pre_orders) || 0),
- local_pickup: acc.local_pickup + (parseInt(day.local_pickup) || 0),
- on_hold: acc.on_hold + (parseInt(day.status?.on_hold) || 0),
- refunds: {
- total: acc.refunds.total + (parseFloat(day.refunds?.total) || 0),
- count: acc.refunds.count + (parseInt(day.refunds?.count) || 0)
- },
- cancellations: {
- total: acc.cancellations.total + (parseFloat(day.cancellations?.total) || 0),
- count: acc.cancellations.count + (parseInt(day.cancellations?.count) || 0)
- }
- }), {
- revenue: 0,
- orders: 0,
- items: 0,
- pre_orders: 0,
- local_pickup: 0,
- on_hold: 0,
- refunds: { total: 0, count: 0 },
- cancellations: { total: 0, count: 0 }
- });
-
- // Update the metrics with recalculated totals
- currentData.revenue.total = totals.revenue;
- currentData.orders = {
- ...currentData.orders,
- total: totals.orders,
- items_total: totals.items,
- pre_orders: totals.pre_orders,
- local_pickup: totals.local_pickup,
- status: { ...currentData.orders?.status, on_hold: totals.on_hold }
- };
- currentData.refunds = totals.refunds;
- currentData.cancellations = totals.cancellations;
-
- setMetrics(currentData);
- setLastUpdate(DateTime.now());
- setTimeRangeChanging(false);
-
- // Fetch previous period
- const prevPeriod = getPreviousPeriod(timeRange);
- setIsLoadingPrevious(true);
-
- const previousResponse = await fetch(`/api/klaviyo/metrics/${prevPeriod}`);
- const previousData = await previousResponse.json();
-
- if (previousResponse.ok) {
- setPreviousMetrics(previousData);
- }
- setIsLoadingPrevious(false);
- } catch (error) {
- console.error("[KLAVIYO STATS] Error fetching metrics:", error);
- setError(error.message);
- setTimeRangeChanging(false);
- }
- }, [timeRange]);
-
- useEffect(() => {
- let isSubscribed = true;
-
- const loadMainData = async () => {
- if (!isSubscribed) return;
- console.log("[KLAVIYO STATS] Loading main data for timeRange:", timeRange);
- await fetchData();
- };
-
- const loadExtendedData = async () => {
- if (!isSubscribed) return;
- console.log("[KLAVIYO STATS] Loading extended data");
- await fetchExtendedData();
- };
-
- loadMainData().then(() => {
- loadExtendedData();
- });
-
- let interval;
- if (timeRange === "today") {
- interval = setInterval(() => {
- if (isSubscribed) {
- console.log("[KLAVIYO STATS] Auto-refreshing today's data");
- loadMainData();
- }
- }, 5 * 60 * 1000);
- }
-
- return () => {
- isSubscribed = false;
- if (interval) clearInterval(interval);
- };
-}, [timeRange, fetchData, fetchExtendedData]);
-
- const dataForDetails = extendedData || metrics;
-
-const renderMetricDetails = useCallback(() => {
- if (!selectedMetric || !metrics) return null;
- const isDetailLoading = !extendedData || timeRangeChanging || isLoadingExtraData;
-
- switch (selectedMetric) {
- case "revenue":
- return ;
- case "orders":
- return ;
- case "average_order":
- return ;
- case "brands_categories":
- return ;
- case "shipped":
- return ;
- case "pre_orders":
- return ;
- case "local_pickup":
- return ;
- case "on_hold":
- return ;
- case "refunds":
- return ;
- case "cancellations":
- return ;
- case "order_range":
- return ;
- case "peak_hour":
- return ;
- default:
- return null;
- }
- }, [selectedMetric, metrics, timeRangeChanging, isLoadingExtraData, extendedData, dataForDetails]);
-
- if (error) {
- return (
-
-
-
- Error loading metrics: {error}
-
-
-
- );
- }return (
-
-
-
-
-
Sales Dashboard
-
- {timeRange === "today" ? (
- `Today (${DateTime.now().setZone('America/New_York').toFormat('M/d h:mm a')} ET)`
- ) : timeRange === "yesterday" ? (
- `Yesterday (${DateTime.now().setZone('America/New_York').minus({ days: 1 }).toFormat('M/d')} 1:00 AM - 12:59 AM ET)`
- ) : (
- `${DateTime.now().setZone('America/New_York').minus({
- days: timeRange === "last7days" ? 6 :
- timeRange === "last30days" ? 29 : 89
- }).toFormat('M/d')} - ${DateTime.now().setZone('America/New_York').toFormat('M/d h:mm a')} ET`
- )}
-
-
-
-
- {lastUpdate && !timeRangeChanging && (
-
- {lastUpdate.toFormat("hh:mm a")}
-
- )}
-
-
-
-
-
- {error ? (
-
- ) : timeRangeChanging ? (
-
- ) : processedMetrics ? (
- <>
-
- {RevenueCard}
-
- setSelectedMetric("orders")}
- />
-
- setSelectedMetric("average_order")}
- />
-
- setSelectedMetric("brands_categories")}
- />
-
- setSelectedMetric("shipped")}
- />
-
- setSelectedMetric("pre_orders")}
- />
-
- setSelectedMetric("local_pickup")}
- />
-
- setSelectedMetric("on_hold")}
- />
-
- {isSingleDay ? (
- setSelectedMetric("peak_hour")}
- />
- ) : (
- setSelectedMetric("revenue")}
- />
-
- )}
-
- setSelectedMetric("refunds")}
- />
-
- setSelectedMetric("cancellations")}
- />
-
- setSelectedMetric("order_range")}
- />
-
- {selectedMetric && (
-
- )}
- >
- ) : null}
-
-
- );
-};
-
-export default KlaviyoStats;
\ No newline at end of file