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 'last2days'; case 'last7days': return 'previous7days'; case 'last30days': return 'previous30days'; case 'last90days': return 'previous90days'; 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 = () => (

Loading data...

); const ErrorState = ({ message }) => (

Error Loading Metrics

{message}

); // Add data validation and normalization utilities const validateMetricsData = (data) => { if (!data || typeof data !== 'object') { throw new Error('Invalid metrics data: expected object'); } if (!data.revenue?.daily || !Array.isArray(data.revenue.daily)) { throw new Error('Invalid metrics data: missing or invalid daily revenue data'); } return true; }; const normalizeMetricsData = (data) => { // Ensure all required properties exist with default values const normalized = { revenue: { total: parseFloat(data.revenue?.total) || 0, daily: data.revenue?.daily?.map(day => ({ date: day.date, value: parseFloat(day.value) || 0, orders: parseInt(day.orders) || 0, items: parseInt(day.items) || 0, pre_orders: parseInt(day.pre_orders) || 0, local_pickup: parseInt(day.local_pickup) || 0, refunds: { total: parseFloat(day.refunds?.total) || 0, count: parseInt(day.refunds?.count) || 0 }, cancellations: { total: parseFloat(day.cancellations?.total) || 0, count: parseInt(day.cancellations?.count) || 0 }, status: { on_hold: parseInt(day.status?.on_hold) || 0, processing: parseInt(day.status?.processing) || 0, completed: parseInt(day.status?.completed) || 0 }, hourly_orders: Array.isArray(day.hourly_orders) ? day.hourly_orders.map(count => parseInt(count) || 0) : Array(24).fill(0), payment_methods: day.payment_methods || {}, categories: day.categories || {}, brands: day.brands || {}, shipping_states: day.shipping_states || {}, unique_customers: Array.isArray(day.unique_customers) ? [...new Set(day.unique_customers)] : [] })) || [] }, orders: { total: parseInt(data.orders?.total) || 0, items_total: parseInt(data.orders?.items_total) || 0, pre_orders: parseInt(data.orders?.pre_orders) || 0, local_pickup: parseInt(data.orders?.local_pickup) || 0, unique_customers: Array.isArray(data.orders?.unique_customers) ? [...new Set(data.orders.unique_customers)] : [], payment_methods: data.orders?.payment_methods || {}, categories: data.orders?.categories || {}, brands: data.orders?.brands || {}, shipping_states: data.orders?.shipping_states || {}, status: { pre_order: parseInt(data.orders?.status?.pre_order) || 0, ready: parseInt(data.orders?.status?.ready) || 0, on_hold: parseInt(data.orders?.status?.on_hold) || 0 } }, hourly_distribution: Array.isArray(data.hourly_distribution) ? data.hourly_distribution.map(count => parseInt(count) || 0) : Array(24).fill(0), refunds: { count: parseInt(data.refunds?.count) || 0, total: parseFloat(data.refunds?.total) || 0 }, cancellations: { count: parseInt(data.cancellations?.count) || 0, total: parseFloat(data.cancellations?.total) || 0 } }; // Sort daily data by date normalized.revenue.daily.sort((a, b) => new Date(a.date) - new Date(b.date)); // Calculate any derived values if (normalized.orders.total > 0) { normalized.orders.average_order_value = normalized.revenue.total / normalized.orders.total; normalized.orders.average_items = normalized.orders.items_total / normalized.orders.total; } return normalized; }; // 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 { // Fetch current period data const currentResponse = await fetch(`/api/klaviyo/metrics/${timeRange}`); if (!currentResponse.ok) { throw new Error(`Failed to fetch current metrics: ${currentResponse.status}`); } const rawData = await currentResponse.json(); // Validate and normalize the data try { validateMetricsData(rawData); const currentData = normalizeMetricsData(rawData); setMetrics(currentData); setLastUpdate(DateTime.now().setZone('America/New_York')); } catch (validationError) { console.error("[KLAVIYO STATS] Data validation error:", validationError); throw new Error(`Invalid data structure: ${validationError.message}`); } // Fetch previous period data const prevPeriod = getPreviousPeriod(timeRange); setIsLoadingPrevious(true); try { const previousResponse = await fetch(`/api/klaviyo/metrics/${prevPeriod}`); if (!previousResponse.ok) { console.warn(`Failed to fetch previous period metrics: ${previousResponse.status}`); } else { const rawPreviousData = await previousResponse.json(); try { validateMetricsData(rawPreviousData); const previousData = normalizeMetricsData(rawPreviousData); setPreviousMetrics(previousData); } catch (validationError) { console.warn("[KLAVIYO STATS] Previous period data validation error:", validationError); } } } catch (error) { console.error("[KLAVIYO STATS] Error fetching previous period:", error); } finally { setIsLoadingPrevious(false); } console.log("[KLAVIYO STATS] Processed data:", { timeRange, totalRevenue: metrics?.revenue?.total, dailyCount: metrics?.revenue?.daily?.length, hasPreviousData: !!previousMetrics }); } catch (error) { console.error("[KLAVIYO STATS] Error fetching data:", error); setError(error.message); } finally { setTimeRangeChanging(false); } }, [timeRange, getPreviousPeriod]); // Add helper function for getting previous period const getPreviousPeriod = useCallback((currentRange) => { switch (currentRange) { case 'today': return 'yesterday'; case 'yesterday': return 'last2days'; case 'last7days': return 'previous7days'; case 'last30days': return 'previous30days'; case 'last90days': return 'previous90days'; default: return currentRange; } }, []); 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 && ( setSelectedMetric(null)} > {selectedMetric .split("_") .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" ")}{" "} Details {renderMetricDetails()} )} ) : null}
); }; export default KlaviyoStats;