diff --git a/dashboard-server/klaviyo-server/services/events.service.js b/dashboard-server/klaviyo-server/services/events.service.js index 9e7d166..374a22a 100644 --- a/dashboard-server/klaviyo-server/services/events.service.js +++ b/dashboard-server/klaviyo-server/services/events.service.js @@ -1512,21 +1512,21 @@ export class EventsService { const { metric, daily = false } = params; console.log('[EventsService] Request params:', params); - // Get period dates using the same logic as calculatePeriodStats + // Get period dates using TimeManager to respect 1 AM day start let periodStart, periodEnd, prevPeriodStart, prevPeriodEnd; if (params.startDate && params.endDate) { - periodStart = this.timeManager.toDateTime(params.startDate); - periodEnd = this.timeManager.toDateTime(params.endDate); + periodStart = this.timeManager.getDayStart(this.timeManager.toDateTime(params.startDate)); + periodEnd = this.timeManager.getDayEnd(this.timeManager.toDateTime(params.endDate)); const duration = periodEnd.diff(periodStart); - prevPeriodStart = periodStart.minus(duration); - prevPeriodEnd = periodStart.minus({ milliseconds: 1 }); + prevPeriodStart = this.timeManager.getDayStart(periodStart.minus(duration)); + prevPeriodEnd = this.timeManager.getDayEnd(periodStart.minus({ milliseconds: 1 })); } else if (params.timeRange) { const range = this.timeManager.getDateRange(params.timeRange); periodStart = range.start; periodEnd = range.end; - const duration = periodEnd.diff(periodStart); - prevPeriodStart = periodStart.minus(duration); - prevPeriodEnd = periodStart.minus({ milliseconds: 1 }); + const prevRange = this.timeManager.getPreviousPeriod(params.timeRange); + prevPeriodStart = prevRange.start; + prevPeriodEnd = prevRange.end; } // Load both current and previous period data @@ -1549,36 +1549,7 @@ export class EventsService { const currentEvents = this._transformEvents(currentResponse.data || []); const prevEvents = this._transformEvents(prevResponse.data || []); - // Filter events based on metric type - const filterEvents = (events) => { - switch (metric) { - case 'pre_orders': - return events.filter(event => - Boolean(event.event_properties?.HasPreorder || - event.event_properties?.has_preorder || - event.event_properties?.preorder) - ); - case 'local_pickup': - return events.filter(event => - Boolean(event.event_properties?.LocalPickup || - event.event_properties?.local_pickup || - event.event_properties?.pickup) - ); - case 'on_hold': - return events.filter(event => - Boolean(event.event_properties?.IsOnHold || - event.event_properties?.is_on_hold || - event.event_properties?.on_hold) - ); - default: - return events; - } - }; - - const filteredCurrentEvents = filterEvents(currentEvents); - const filteredPrevEvents = filterEvents(prevEvents); - - // Initialize daily stats map with all dates in range + // Initialize daily stats map with all dates in range using TimeManager's day start const dailyStats = new Map(); let currentDate = periodStart; while (currentDate <= periodEnd) { @@ -1589,8 +1560,6 @@ export class EventsService { revenue: 0, orders: 0, itemCount: 0, - averageOrderValue: 0, - averageItemsPerOrder: 0, count: 0, value: 0, percentage: 0, @@ -1598,98 +1567,73 @@ export class EventsService { prevRevenue: 0, prevOrders: 0, prevItemCount: 0, - prevAvgOrderValue: 0, - orderValueRange: { - largest: 0, - smallest: 0, - largestOrderId: null, - smallestOrderId: null, - distribution: { - under25: { count: 0, total: 0 }, - under50: { count: 0, total: 0 }, - under100: { count: 0, total: 0 }, - under200: { count: 0, total: 0 }, - over200: { count: 0, total: 0 } - } - } + prevValue: 0, + prevCount: 0, + prevPercentage: 0, + averageOrderValue: 0, + averageItemsPerOrder: 0, + prevAvgOrderValue: 0 }); - currentDate = currentDate.plus({ days: 1 }); + currentDate = this.timeManager.getDayStart(currentDate.plus({ days: 1 })); } - // Get total orders for the period (needed for percentages) - const totalOrders = currentEvents.length; + // First pass: Count total orders per day using TimeManager's day boundaries + for (const event of currentEvents) { + const datetime = this.timeManager.toDateTime(event.attributes?.datetime); + if (!datetime) continue; - // Process current period events + // Get the day start for this event's time to ensure proper day assignment + const dayStart = this.timeManager.getDayStart(datetime); + const dateKey = dayStart.toFormat('yyyy-MM-dd'); + if (!dailyStats.has(dateKey)) continue; + + const dayStats = dailyStats.get(dateKey); + dayStats.orders++; + dailyStats.set(dateKey, dayStats); + } + + // Second pass: Process filtered orders + const filterEvents = (events) => { + switch (metric) { + case 'pre_orders': + return events.filter(event => Boolean(event.event_properties?.HasPreorder)); + case 'local_pickup': + return events.filter(event => Boolean(event.event_properties?.LocalPickup)); + case 'on_hold': + return events.filter(event => Boolean(event.event_properties?.IsOnHold)); + default: + return events; + } + }; + + const filteredCurrentEvents = filterEvents(currentEvents); + + // Process current period filtered events using TimeManager's day boundaries for (const event of filteredCurrentEvents) { const datetime = this.timeManager.toDateTime(event.attributes?.datetime); if (!datetime) continue; - const dateKey = datetime.toFormat('yyyy-MM-dd'); + // Get the day start for this event's time + const dayStart = this.timeManager.getDayStart(datetime); + const dateKey = dayStart.toFormat('yyyy-MM-dd'); if (!dailyStats.has(dateKey)) continue; const dayStats = dailyStats.get(dateKey); const props = event.event_properties || {}; const totalAmount = Number(props.TotalAmount || 0); const items = props.Items || []; - const orderId = props.OrderId; - dayStats.revenue += totalAmount; - dayStats.orders++; - dayStats.itemCount += items.length; - dayStats.value += totalAmount; dayStats.count++; - dayStats.totalOrders = totalOrders; - dayStats.percentage = (dayStats.count / totalOrders) * 100; - dayStats.averageOrderValue = dayStats.revenue / dayStats.orders; - dayStats.averageItemsPerOrder = dayStats.itemCount / dayStats.orders; + dayStats.value += totalAmount; + dayStats.itemCount += items.length; + dayStats.percentage = (dayStats.count / dayStats.orders) * 100; + dayStats.averageOrderValue = dayStats.value / dayStats.count; + dayStats.averageItemsPerOrder = dayStats.itemCount / dayStats.count; - // Track daily order value range - if (totalAmount > dayStats.orderValueRange.largest) { - dayStats.orderValueRange.largest = totalAmount; - dayStats.orderValueRange.largestOrderId = orderId; - } - if (totalAmount > 0) { - if (dayStats.orderValueRange.smallest === 0 || totalAmount < dayStats.orderValueRange.smallest) { - dayStats.orderValueRange.smallest = totalAmount; - dayStats.orderValueRange.smallestOrderId = orderId; - } - - // Track order value distribution with more granular ranges - const ranges = [ - { min: 0, max: 25 }, - { min: 25, max: 50 }, - { min: 50, max: 75 }, - { min: 75, max: 100 }, - { min: 100, max: 150 }, - { min: 150, max: 200 }, - { min: 200, max: 300 }, - { min: 300, max: 500 }, - { min: 500, max: Infinity } - ]; - - // Initialize distribution if not exists - if (!dayStats.orderValueDistribution) { - dayStats.orderValueDistribution = ranges.map(range => ({ - min: range.min, - max: range.max === Infinity ? 'Infinity' : range.max, - count: 0, - total: 0 - })); - } - - // Find the appropriate range and update counts - const rangeIndex = ranges.findIndex(range => - totalAmount >= range.min && totalAmount < range.max - ); - - if (rangeIndex !== -1) { - dayStats.orderValueDistribution[rangeIndex].count++; - dayStats.orderValueDistribution[rangeIndex].total += totalAmount; - } - } + dailyStats.set(dateKey, dayStats); } - // Process previous period events + // Initialize and process previous period stats using TimeManager's day boundaries const prevDailyStats = new Map(); let prevDate = prevPeriodStart; while (prevDate <= prevPeriodEnd) { @@ -1697,37 +1641,47 @@ export class EventsService { prevDailyStats.set(dateKey, { date: prevDate.toISO(), timestamp: dateKey, - revenue: 0, orders: 0, - itemCount: 0, + count: 0, value: 0, - count: 0 + percentage: 0 }); - prevDate = prevDate.plus({ days: 1 }); + prevDate = this.timeManager.getDayStart(prevDate.plus({ days: 1 })); } - // Get total orders for previous period - const prevTotalOrders = prevEvents.length; + // First pass for previous period: Count total orders + for (const event of prevEvents) { + const datetime = this.timeManager.toDateTime(event.attributes?.datetime); + if (!datetime) continue; - // Process previous period events + const dayStart = this.timeManager.getDayStart(datetime); + const dateKey = dayStart.toFormat('yyyy-MM-dd'); + if (!prevDailyStats.has(dateKey)) continue; + + const dayStats = prevDailyStats.get(dateKey); + dayStats.orders++; + prevDailyStats.set(dateKey, dayStats); + } + + // Second pass for previous period: Process filtered orders + const filteredPrevEvents = filterEvents(prevEvents); for (const event of filteredPrevEvents) { const datetime = this.timeManager.toDateTime(event.attributes?.datetime); if (!datetime) continue; - const dateKey = datetime.toFormat('yyyy-MM-dd'); + const dayStart = this.timeManager.getDayStart(datetime); + const dateKey = dayStart.toFormat('yyyy-MM-dd'); if (!prevDailyStats.has(dateKey)) continue; const dayStats = prevDailyStats.get(dateKey); const props = event.event_properties || {}; const totalAmount = Number(props.TotalAmount || 0); - const items = props.Items || []; - dayStats.revenue += totalAmount; - dayStats.orders++; - dayStats.itemCount += items.length; - dayStats.value += totalAmount; dayStats.count++; - dayStats.percentage = (dayStats.count / prevTotalOrders) * 100; + dayStats.value += totalAmount; + dayStats.percentage = (dayStats.count / dayStats.orders) * 100; + + prevDailyStats.set(dateKey, dayStats); } // Map previous period data to current period days @@ -1742,13 +1696,11 @@ export class EventsService { if (prevDayStats && currentDayStats) { const dayStats = dailyStats.get(currentDayStats.timestamp); if (dayStats) { - dayStats.prevRevenue = prevDayStats.revenue; - dayStats.prevOrders = prevDayStats.orders; - dayStats.prevItemCount = prevDayStats.itemCount; dayStats.prevValue = prevDayStats.value; dayStats.prevCount = prevDayStats.count; + dayStats.prevOrders = prevDayStats.orders; dayStats.prevPercentage = prevDayStats.percentage; - dayStats.prevAvgOrderValue = prevDayStats.orders > 0 ? prevDayStats.revenue / prevDayStats.orders : 0; + dayStats.prevAvgOrderValue = prevDayStats.count > 0 ? prevDayStats.value / prevDayStats.count : 0; dailyStats.set(currentDayStats.timestamp, dayStats); } } @@ -1759,29 +1711,19 @@ export class EventsService { .sort((a, b) => a.date.localeCompare(b.date)) .map(day => ({ ...day, - revenue: Number(day.revenue || 0), + revenue: Number(day.value || 0), orders: Number(day.orders || 0), itemCount: Number(day.itemCount || 0), - averageOrderValue: Number(day.averageOrderValue || 0), - averageItemsPerOrder: Number(day.averageItemsPerOrder || 0), count: Number(day.count || 0), value: Number(day.value || 0), percentage: Number(day.percentage || 0), - totalOrders: Number(day.totalOrders || 0), - prevRevenue: Number(day.prevRevenue || 0), - prevOrders: Number(day.prevOrders || 0), - prevItemCount: Number(day.prevItemCount || 0), + averageOrderValue: Number(day.averageOrderValue || 0), + averageItemsPerOrder: Number(day.averageItemsPerOrder || 0), prevValue: Number(day.prevValue || 0), prevCount: Number(day.prevCount || 0), + prevOrders: Number(day.prevOrders || 0), prevPercentage: Number(day.prevPercentage || 0), - prevAvgOrderValue: Number(day.prevAvgOrderValue || 0), - orderValueRange: { - largest: Number(day.orderValueRange?.largest || 0), - smallest: Number(day.orderValueRange?.smallest || 0), - largestOrderId: day.orderValueRange?.largestOrderId || null, - smallestOrderId: day.orderValueRange?.smallestOrderId || null - }, - orderValueDistribution: day.orderValueDistribution || [] + prevAvgOrderValue: Number(day.prevAvgOrderValue || 0) })); return stats; diff --git a/dashboard/src/components/dashboard/StatCards.jsx b/dashboard/src/components/dashboard/StatCards.jsx index 6eb65ed..ba57ba7 100644 --- a/dashboard/src/components/dashboard/StatCards.jsx +++ b/dashboard/src/components/dashboard/StatCards.jsx @@ -532,7 +532,6 @@ const ShippingDetails = ({ data }) => { const OrderTypeDetails = ({ data, type }) => { if (!data?.length) return