diff --git a/dashboard-server/klaviyo-server/services/events.service.js b/dashboard-server/klaviyo-server/services/events.service.js index c6e4dba..18e13a2 100644 --- a/dashboard-server/klaviyo-server/services/events.service.js +++ b/dashboard-server/klaviyo-server/services/events.service.js @@ -497,6 +497,24 @@ export class EventsService { dayStats.orderValueRange.smallest = totalAmount; dayStats.orderValueRange.smallestOrderId = orderId; } + + // Track order value distribution + if (totalAmount < 25) { + dayStats.orderValueRange.distribution.under25.count++; + dayStats.orderValueRange.distribution.under25.total += totalAmount; + } else if (totalAmount < 50) { + dayStats.orderValueRange.distribution.under50.count++; + dayStats.orderValueRange.distribution.under50.total += totalAmount; + } else if (totalAmount < 100) { + dayStats.orderValueRange.distribution.under100.count++; + dayStats.orderValueRange.distribution.under100.total += totalAmount; + } else if (totalAmount < 200) { + dayStats.orderValueRange.distribution.under200.count++; + dayStats.orderValueRange.distribution.under200.total += totalAmount; + } else { + dayStats.orderValueRange.distribution.over200.count++; + dayStats.orderValueRange.distribution.over200.total += totalAmount; + } } // Track order types @@ -1255,25 +1273,38 @@ export class EventsService { ); const results = await Promise.all(eventPromises); - - // Combine and sort all events - const allEvents = results - .flatMap(result => result.data || []) - .sort((a, b) => new Date(b.attributes?.datetime) - new Date(a.attributes?.datetime)); - - // Transform the events - const transformedEvents = this._transformEvents(allEvents); - - const result = { - data: transformedEvents, - meta: { - total_count: transformedEvents.length + + // Transform and flatten the events into a single array + const allEvents = []; + metrics.forEach((metric, index) => { + const response = results[index]; + const events = response?.data || []; + if (Array.isArray(events)) { + const transformedEvents = events.map(event => ({ + ...event, + metric_id: metric, + datetime: event.attributes?.datetime || event.datetime, + event_properties: { + ...event.event_properties, + datetime: event.attributes?.datetime || event.datetime, + }, + })); + allEvents.push(...transformedEvents); } - }; + }); - // Cache the results + // Sort all events by datetime in descending order + allEvents.sort((a, b) => { + const dateA = new Date(a.datetime); + const dateB = new Date(b.datetime); + return dateB - dateA; + }); + + const result = { data: allEvents }; + + // Cache the result try { - const ttl = this.redisService._getTTL(timeRange); + const ttl = this.redisService._getTTL(params.timeRange); await this.redisService.set(`${cacheKey}:feed`, result, ttl); } catch (cacheError) { console.warn('[EventsService] Cache set error:', cacheError); @@ -1281,7 +1312,7 @@ export class EventsService { return result; } catch (error) { - console.error('[EventsService] Error fetching multi-metric events:', error); + console.error('[EventsService] Error in batch metrics:', error); throw error; } } @@ -1481,26 +1512,21 @@ export class EventsService { const { metric, daily = false } = params; console.log('[EventsService] Request params:', params); - // Get period dates + // Get period dates using the same logic as calculatePeriodStats let periodStart, periodEnd, prevPeriodStart, prevPeriodEnd; if (params.startDate && params.endDate) { periodStart = this.timeManager.toDateTime(params.startDate); periodEnd = this.timeManager.toDateTime(params.endDate); const duration = periodEnd.diff(periodStart); + prevPeriodStart = periodStart.minus(duration); prevPeriodEnd = periodStart.minus({ milliseconds: 1 }); - prevPeriodStart = prevPeriodEnd.minus(duration); } else if (params.timeRange) { const range = this.timeManager.getDateRange(params.timeRange); - const prevRange = this.timeManager.getPreviousPeriod(params.timeRange); - - if (!range || !prevRange) { - throw new Error(`Invalid time range specified: ${params.timeRange}`); - } - periodStart = range.start; periodEnd = range.end; - prevPeriodStart = prevRange.start; - prevPeriodEnd = prevRange.end; + const duration = periodEnd.diff(periodStart); + prevPeriodStart = periodStart.minus(duration); + prevPeriodEnd = periodStart.minus({ milliseconds: 1 }); } // Load both current and previous period data @@ -1572,7 +1598,20 @@ export class EventsService { prevRevenue: 0, prevOrders: 0, prevItemCount: 0, - prevAvgOrderValue: 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 } + } + } }); currentDate = currentDate.plus({ days: 1 }); } @@ -1592,6 +1631,7 @@ export class EventsService { const props = event.event_properties || {}; const totalAmount = Number(props.TotalAmount || 0); const items = props.Items || []; + const orderId = props.OrderId; dayStats.revenue += totalAmount; dayStats.orders++; @@ -1602,6 +1642,36 @@ export class EventsService { dayStats.percentage = (dayStats.count / totalOrders) * 100; dayStats.averageOrderValue = dayStats.revenue / dayStats.orders; dayStats.averageItemsPerOrder = dayStats.itemCount / dayStats.orders; + + // 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 + if (totalAmount < 25) { + dayStats.orderValueRange.distribution.under25.count++; + dayStats.orderValueRange.distribution.under25.total += totalAmount; + } else if (totalAmount < 50) { + dayStats.orderValueRange.distribution.under50.count++; + dayStats.orderValueRange.distribution.under50.total += totalAmount; + } else if (totalAmount < 100) { + dayStats.orderValueRange.distribution.under100.count++; + dayStats.orderValueRange.distribution.under100.total += totalAmount; + } else if (totalAmount < 200) { + dayStats.orderValueRange.distribution.under200.count++; + dayStats.orderValueRange.distribution.under200.total += totalAmount; + } else { + dayStats.orderValueRange.distribution.over200.count++; + dayStats.orderValueRange.distribution.over200.total += totalAmount; + } + } } // Process previous period events @@ -1689,7 +1759,35 @@ export class EventsService { prevValue: Number(day.prevValue || 0), prevCount: Number(day.prevCount || 0), prevPercentage: Number(day.prevPercentage || 0), - prevAvgOrderValue: Number(day.prevAvgOrderValue || 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, + distribution: { + under25: { + count: Number(day.orderValueRange?.distribution?.under25?.count || 0), + total: Number(day.orderValueRange?.distribution?.under25?.total || 0) + }, + under50: { + count: Number(day.orderValueRange?.distribution?.under50?.count || 0), + total: Number(day.orderValueRange?.distribution?.under50?.total || 0) + }, + under100: { + count: Number(day.orderValueRange?.distribution?.under100?.count || 0), + total: Number(day.orderValueRange?.distribution?.under100?.total || 0) + }, + under200: { + count: Number(day.orderValueRange?.distribution?.under200?.count || 0), + total: Number(day.orderValueRange?.distribution?.under200?.total || 0) + }, + over200: { + count: Number(day.orderValueRange?.distribution?.over200?.count || 0), + total: Number(day.orderValueRange?.distribution?.over200?.total || 0) + } + } + } })); return stats; diff --git a/dashboard/src/components/dashboard/StatCards.jsx b/dashboard/src/components/dashboard/StatCards.jsx index 7cd8367..b0a6d6a 100644 --- a/dashboard/src/components/dashboard/StatCards.jsx +++ b/dashboard/src/components/dashboard/StatCards.jsx @@ -105,6 +105,11 @@ const TimeSeriesChart = ({ const ChartComponent = type === "line" ? LineChart : BarChart; const DataComponent = type === "line" ? Line : Bar; + // Handle multiple series + const keys = Array.isArray(dataKey) ? dataKey : [dataKey]; + const names = Array.isArray(name) ? name : [name]; + const colors = Array.isArray(color) ? color : [color]; + return (