diff --git a/dashboard-server/klaviyo-server/services/events.service.js b/dashboard-server/klaviyo-server/services/events.service.js index ba0ed0b..c6e4dba 100644 --- a/dashboard-server/klaviyo-server/services/events.service.js +++ b/dashboard-server/klaviyo-server/services/events.service.js @@ -1294,38 +1294,70 @@ export class EventsService { return events.map(event => { try { - // Extract metric ID from the relationships field if it exists + // Extract metric ID from all possible locations const metricId = event.relationships?.metric?.data?.id || event.attributes?.metric?.id || event.attributes?.metric_id; - // Extract properties from all possible locations in the Klaviyo event structure - const eventProps = { + // Extract properties from all possible locations + const rawProps = { ...(event.attributes?.event_properties || {}), ...(event.attributes?.properties || {}), ...(event.attributes?.profile || {}), value: event.attributes?.value, datetime: event.attributes?.datetime }; - + // Normalize shipping data const shippingData = { - name: eventProps.ShippingName || eventProps.shipping_name || eventProps.shipping?.name, - street1: eventProps.ShippingStreet1 || eventProps.shipping_street1 || eventProps.shipping?.street1, - street2: eventProps.ShippingStreet2 || eventProps.shipping_street2 || eventProps.shipping?.street2, - city: eventProps.ShippingCity || eventProps.shipping_city || eventProps.shipping?.city, - state: eventProps.ShippingState || eventProps.shipping_state || eventProps.shipping?.state, - zip: eventProps.ShippingZip || eventProps.shipping_zip || eventProps.shipping?.zip, - country: eventProps.ShippingCountry || eventProps.shipping_country || eventProps.shipping?.country, - method: eventProps.ShipMethod || eventProps.shipping_method || eventProps.shipping?.method, - tracking: eventProps.TrackingNumber || eventProps.tracking_number + name: rawProps.ShippingName || rawProps.shipping_name || rawProps.shipping?.name, + street1: rawProps.ShippingStreet1 || rawProps.shipping_street1 || rawProps.shipping?.street1, + street2: rawProps.ShippingStreet2 || rawProps.shipping_street2 || rawProps.shipping?.street2, + city: rawProps.ShippingCity || rawProps.shipping_city || rawProps.shipping?.city, + state: rawProps.ShippingState || rawProps.shipping_state || rawProps.shipping?.state, + zip: rawProps.ShippingZip || rawProps.shipping_zip || rawProps.shipping?.zip, + country: rawProps.ShippingCountry || rawProps.shipping_country || rawProps.shipping?.country, + method: rawProps.ShipMethod || rawProps.shipping_method || rawProps.shipping?.method, + tracking: rawProps.TrackingNumber || rawProps.tracking_number }; + // Normalize payment data + const paymentData = { + method: rawProps.PaymentMethod || rawProps.payment_method || rawProps.payment?.method, + name: rawProps.PaymentName || rawProps.payment_name || rawProps.payment?.name, + amount: Number(rawProps.PaymentAmount || rawProps.payment_amount || rawProps.payment?.amount || 0) + }; + + // Normalize order flags + const orderFlags = { + type: rawProps.OrderType || rawProps.order_type || 'standard', + hasPreorder: Boolean(rawProps.HasPreorder || rawProps.has_preorder || rawProps.preorder), + localPickup: Boolean(rawProps.LocalPickup || rawProps.local_pickup || rawProps.pickup), + isOnHold: Boolean(rawProps.IsOnHold || rawProps.is_on_hold || rawProps.on_hold), + hasDigiItem: Boolean(rawProps.HasDigiItem || rawProps.has_digital_item || rawProps.digital_item), + hasNotions: Boolean(rawProps.HasNotions || rawProps.has_notions || rawProps.notions), + hasDigitalGC: Boolean(rawProps.HasDigitalGC || rawProps.has_digital_gc || rawProps.gift_card), + stillOwes: Boolean(rawProps.StillOwes || rawProps.still_owes || rawProps.balance_due) + }; + + // Normalize refund/cancel data + const refundData = { + reason: rawProps.CancelReason || rawProps.cancel_reason || rawProps.reason, + message: rawProps.CancelMessage || rawProps.cancel_message || rawProps.message, + orderMessage: rawProps.OrderMessage || rawProps.order_message || rawProps.note + }; + + // Transform items + const items = this._transformItems(rawProps.Items || rawProps.items || rawProps.line_items || []); + + // Calculate totals + const totalAmount = Number(rawProps.TotalAmount || rawProps.PaymentAmount || rawProps.total_amount || rawProps.value || 0); + const itemCount = items.reduce((sum, item) => sum + Number(item.Quantity || item.QuantityOrdered || 1), 0); + const transformed = { id: event.id, type: event.type, metric_id: metricId, - // Preserve the original attributes structure for compatibility attributes: { ...event.attributes, datetime: event.attributes?.datetime, @@ -1337,42 +1369,29 @@ export class EventsService { }, relationships: event.relationships, event_properties: { - ...eventProps, - // Transform common properties - EmailAddress: eventProps.EmailAddress || eventProps.email, - FirstName: eventProps.FirstName || eventProps.first_name, - LastName: eventProps.LastName || eventProps.last_name, - OrderId: eventProps.OrderId || eventProps.FromOrder || eventProps.order_id, - TotalAmount: Number(eventProps.TotalAmount || eventProps.PaymentAmount || eventProps.total_amount || eventProps.value || 0), - Items: this._transformItems(eventProps.Items || eventProps.items || eventProps.line_items || []), - // Add normalized shipping information - ShippingName: shippingData.name, - ShippingStreet1: shippingData.street1, - ShippingStreet2: shippingData.street2, - ShippingCity: shippingData.city, - ShippingState: shippingData.state, - ShippingZip: shippingData.zip, - ShippingCountry: shippingData.country, - ShippingMethod: shippingData.method, - TrackingNumber: shippingData.tracking, - ShipMethod: shippingData.method, - // Add payment information - PaymentMethod: eventProps.PaymentMethod || eventProps.payment_method || eventProps.payment?.method, - PaymentName: eventProps.PaymentName || eventProps.payment_name || eventProps.payment?.name, - PaymentAmount: Number(eventProps.PaymentAmount || eventProps.payment_amount || eventProps.payment?.amount || 0), - // Add order flags - OrderType: eventProps.OrderType || eventProps.order_type || 'standard', - HasPreorder: Boolean(eventProps.HasPreorder || eventProps.has_preorder || eventProps.preorder), - LocalPickup: Boolean(eventProps.LocalPickup || eventProps.local_pickup || eventProps.pickup), - IsOnHold: Boolean(eventProps.IsOnHold || eventProps.is_on_hold || eventProps.on_hold), - HasDigiItem: Boolean(eventProps.HasDigiItem || eventProps.has_digital_item || eventProps.digital_item), - HasNotions: Boolean(eventProps.HasNotions || eventProps.has_notions || eventProps.notions), - HasDigitalGC: Boolean(eventProps.HasDigitalGC || eventProps.has_digital_gc || eventProps.gift_card), - StillOwes: Boolean(eventProps.StillOwes || eventProps.still_owes || eventProps.balance_due), - // Add refund/cancel information - CancelReason: eventProps.CancelReason || eventProps.cancel_reason || eventProps.reason, - CancelMessage: eventProps.CancelMessage || eventProps.cancel_message || eventProps.message, - OrderMessage: eventProps.OrderMessage || eventProps.order_message || eventProps.note + // Basic properties + EmailAddress: rawProps.EmailAddress || rawProps.email, + FirstName: rawProps.FirstName || rawProps.first_name, + LastName: rawProps.LastName || rawProps.last_name, + OrderId: rawProps.OrderId || rawProps.FromOrder || rawProps.order_id, + TotalAmount: totalAmount, + ItemCount: itemCount, + Items: items, + + // Shipping information + ...shippingData, + + // Payment information + ...paymentData, + + // Order flags + ...orderFlags, + + // Refund/cancel information + ...refundData, + + // Original properties (for backward compatibility) + ...rawProps } }; @@ -1398,23 +1417,55 @@ export class EventsService { } return items.map(item => { - const transformed = { - ...item, - ProductID: item.ProductID || item.product_id, - ProductName: item.ProductName || item.product_name, - SKU: item.SKU || item.sku, - Brand: item.Brand || item.brand, - Categories: item.Categories || item.categories || [], - ItemPrice: Number(item.ItemPrice || item.item_price || 0), - Quantity: Number(item.Quantity || item.QuantityOrdered || item.quantity || 1), - QuantityOrdered: Number(item.QuantityOrdered || item.Quantity || item.quantity_ordered || 1), - QuantitySent: Number(item.QuantitySent || item.quantity_sent || 0), - QuantityBackordered: Number(item.QuantityBackordered || item.quantity_backordered || 0), - RowTotal: Number(item.RowTotal || item.row_total || (item.ItemPrice * (item.Quantity || item.QuantityOrdered || 1))), - ItemStatus: item.ItemStatus || item.item_status || 'In Stock', - ImgThumb: item.ImgThumb || item.img_thumb - }; - return transformed; + try { + const quantity = Number(item.Quantity || item.QuantityOrdered || item.quantity || item.quantity_ordered || 1); + const price = Number(item.ItemPrice || item.item_price || item.price || 0); + const rowTotal = Number(item.RowTotal || item.row_total || (price * quantity) || 0); + + const transformed = { + // Basic item information + ProductID: item.ProductID || item.product_id || item.id, + ProductName: item.ProductName || item.product_name || item.name, + SKU: item.SKU || item.sku, + Brand: item.Brand || item.brand, + Categories: Array.isArray(item.Categories) ? item.Categories : + Array.isArray(item.categories) ? item.categories : [], + + // Pricing + ItemPrice: price, + RowTotal: rowTotal, + + // Quantities + Quantity: quantity, + QuantityOrdered: quantity, + QuantitySent: Number(item.QuantitySent || item.quantity_sent || 0), + QuantityBackordered: Number(item.QuantityBackordered || item.quantity_backordered || 0), + + // Status and images + ItemStatus: item.ItemStatus || item.item_status || item.status || 'In Stock', + ImgThumb: item.ImgThumb || item.img_thumb || item.thumbnail, + + // Additional properties + IsPreorder: Boolean(item.IsPreorder || item.is_preorder || item.preorder), + IsDigital: Boolean(item.IsDigital || item.is_digital || item.digital), + IsGiftCard: Boolean(item.IsGiftCard || item.is_gift_card || item.gift_card), + + // Original properties (for backward compatibility) + ...item + }; + + return transformed; + } catch (error) { + console.error('[EventsService] Error transforming item:', error, item); + // Return a minimal valid item structure + return { + ProductID: item.ProductID || item.product_id || 'unknown', + ProductName: item.ProductName || item.product_name || 'Unknown Product', + Quantity: 1, + ItemPrice: 0, + RowTotal: 0 + }; + } }); } @@ -1439,128 +1490,67 @@ export class EventsService { prevPeriodEnd = periodStart.minus({ milliseconds: 1 }); prevPeriodStart = prevPeriodEnd.minus(duration); } else if (params.timeRange) { - // Handle both current and previous period time ranges - const timeRange = params.timeRange; - const isPreviousPeriod = timeRange.startsWith('previous'); - const normalizedTimeRange = isPreviousPeriod ? timeRange.replace('previous', 'last') : timeRange; + const range = this.timeManager.getDateRange(params.timeRange); + const prevRange = this.timeManager.getPreviousPeriod(params.timeRange); - console.log('[EventsService] Time range details:', { - originalTimeRange: timeRange, - isPreviousPeriod, - normalizedTimeRange - }); - - // Get current period range - const range = this.timeManager.getDateRange(normalizedTimeRange); - if (!range) { - throw new Error(`Invalid time range specified: ${timeRange}`); - } - - // Get previous period range using TimeManager - const prevRange = this.timeManager.getPreviousPeriod(normalizedTimeRange); - if (!prevRange) { - throw new Error(`Could not calculate previous period for: ${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; - - console.log('[EventsService] Calculated date ranges:', { - timeRange, - current: { - start: periodStart.toISO(), - end: periodEnd.toISO(), - duration: periodEnd.diff(periodStart).as('days') - }, - previous: { - start: prevPeriodStart.toISO(), - end: prevPeriodEnd.toISO(), - duration: prevPeriodEnd.diff(prevPeriodStart).as('days') - } - }); } // Load both current and previous period data - console.log('[EventsService] Fetching events with params:', { - current: { - startDate: periodStart.toISO(), - endDate: periodEnd.toISO(), - metricId: METRIC_IDS.PLACED_ORDER, - ...params - }, - previous: { - startDate: prevPeriodStart.toISO(), - endDate: prevPeriodEnd.toISO(), - metricId: METRIC_IDS.PLACED_ORDER - } - }); - const [currentResponse, prevResponse] = await Promise.all([ this.getEvents({ ...params, startDate: periodStart.toISO(), endDate: periodEnd.toISO(), - metricId: METRIC_IDS.PLACED_ORDER, - customFilters: params.metric === 'pre_orders' ? ['equals(event_properties.HasPreorder,true)'] : - params.metric === 'local_pickup' ? ['equals(event_properties.LocalPickup,true)'] : - params.metric === 'on_hold' ? ['equals(event_properties.IsOnHold,true)'] : undefined + metricId: METRIC_IDS.PLACED_ORDER }), this.getEvents({ ..._.omit(params, ['timeRange', 'startDate', 'endDate']), startDate: prevPeriodStart.toISO(), endDate: prevPeriodEnd.toISO(), - metricId: METRIC_IDS.PLACED_ORDER, - timeRange: undefined, - isPreviousPeriod: true, - cacheKey: `prev_${prevPeriodStart.toISO()}_${prevPeriodEnd.toISO()}`, - customFilters: params.metric === 'pre_orders' ? ['equals(event_properties.HasPreorder,true)'] : - params.metric === 'local_pickup' ? ['equals(event_properties.LocalPickup,true)'] : - params.metric === 'on_hold' ? ['equals(event_properties.IsOnHold,true)'] : undefined + metricId: METRIC_IDS.PLACED_ORDER }) ]); - // Add debug logging for request params and filters - console.log('[EventsService] Request details with filters:', { - current: { - params: { - ...params, - startDate: periodStart.toISO(), - endDate: periodEnd.toISO(), - customFilters: params.metric === 'pre_orders' ? ['equals(event_properties.HasPreorder,true)'] : - params.metric === 'local_pickup' ? ['equals(event_properties.LocalPickup,true)'] : - params.metric === 'on_hold' ? ['equals(event_properties.IsOnHold,true)'] : undefined - }, - responseLength: currentResponse?.data?.length - }, - previous: { - params: { - ..._.omit(params, ['timeRange', 'startDate', 'endDate']), - startDate: prevPeriodStart.toISO(), - endDate: prevPeriodEnd.toISO(), - customFilters: params.metric === 'pre_orders' ? ['equals(event_properties.HasPreorder,true)'] : - params.metric === 'local_pickup' ? ['equals(event_properties.LocalPickup,true)'] : - params.metric === 'on_hold' ? ['equals(event_properties.IsOnHold,true)'] : undefined - }, - responseLength: prevResponse?.data?.length - } - }); - // Transform events const currentEvents = this._transformEvents(currentResponse.data || []); const prevEvents = this._transformEvents(prevResponse.data || []); - console.log('[EventsService] Transformed events:', { - current: { - count: currentEvents.length, - revenue: currentEvents.reduce((sum, event) => sum + (Number(event.event_properties?.TotalAmount) || 0), 0) - }, - previous: { - count: prevEvents.length, - revenue: prevEvents.reduce((sum, event) => sum + (Number(event.event_properties?.TotalAmount) || 0), 0) + // 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 const dailyStats = new Map(); @@ -1575,6 +1565,10 @@ export class EventsService { itemCount: 0, averageOrderValue: 0, averageItemsPerOrder: 0, + count: 0, + value: 0, + percentage: 0, + totalOrders: 0, prevRevenue: 0, prevOrders: 0, prevItemCount: 0, @@ -1583,8 +1577,11 @@ export class EventsService { currentDate = currentDate.plus({ days: 1 }); } + // Get total orders for the period (needed for percentages) + const totalOrders = currentEvents.length; + // Process current period events - for (const event of currentEvents) { + for (const event of filteredCurrentEvents) { const datetime = this.timeManager.toDateTime(event.attributes?.datetime); if (!datetime) continue; @@ -1599,6 +1596,10 @@ export class EventsService { 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; } @@ -1613,13 +1614,18 @@ export class EventsService { timestamp: dateKey, revenue: 0, orders: 0, - itemCount: 0 + itemCount: 0, + value: 0, + count: 0 }); prevDate = prevDate.plus({ days: 1 }); } - // Aggregate previous period data - for (const event of prevEvents) { + // Get total orders for previous period + const prevTotalOrders = prevEvents.length; + + // Process previous period events + for (const event of filteredPrevEvents) { const datetime = this.timeManager.toDateTime(event.attributes?.datetime); if (!datetime) continue; @@ -1634,21 +1640,15 @@ export class EventsService { dayStats.revenue += totalAmount; dayStats.orders++; dayStats.itemCount += items.length; - prevDailyStats.set(dateKey, dayStats); + dayStats.value += totalAmount; + dayStats.count++; + dayStats.percentage = (dayStats.count / prevTotalOrders) * 100; } // Map previous period data to current period days const prevPeriodDays = Array.from(prevDailyStats.values()).sort((a, b) => a.date.localeCompare(b.date)); const currentPeriodDays = Array.from(dailyStats.values()).sort((a, b) => a.date.localeCompare(b.date)); - // Add debug logging for data before mapping - console.log('[EventsService] Data before mapping:', { - currentPeriod: currentPeriodDays.slice(0, 3), - previousPeriod: prevPeriodDays.slice(0, 3), - currentLength: currentPeriodDays.length, - prevLength: prevPeriodDays.length - }); - // Map the data using array indices for (let i = 0; i < currentPeriodDays.length && i < prevPeriodDays.length; i++) { const currentDayStats = currentPeriodDays[i]; @@ -1660,19 +1660,15 @@ export class EventsService { dayStats.prevRevenue = prevDayStats.revenue; dayStats.prevOrders = prevDayStats.orders; dayStats.prevItemCount = prevDayStats.itemCount; + dayStats.prevValue = prevDayStats.value; + dayStats.prevCount = prevDayStats.count; + dayStats.prevPercentage = prevDayStats.percentage; dayStats.prevAvgOrderValue = prevDayStats.orders > 0 ? prevDayStats.revenue / prevDayStats.orders : 0; dailyStats.set(currentDayStats.timestamp, dayStats); } } } - // Add debug logging for mapped data - console.log('[EventsService] Sample of mapped data:', { - firstDay: dailyStats.get(currentPeriodDays[0]?.timestamp), - lastDay: dailyStats.get(currentPeriodDays[currentPeriodDays.length - 1]?.timestamp), - totalDays: dailyStats.size - }); - // Convert to array and sort by date const stats = Array.from(dailyStats.values()) .sort((a, b) => a.date.localeCompare(b.date)) @@ -1683,9 +1679,16 @@ export class EventsService { 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), + prevValue: Number(day.prevValue || 0), + prevCount: Number(day.prevCount || 0), + prevPercentage: Number(day.prevPercentage || 0), prevAvgOrderValue: Number(day.prevAvgOrderValue || 0) })); diff --git a/dashboard-server/klaviyo-server/services/redis.service.js b/dashboard-server/klaviyo-server/services/redis.service.js index 9a5409b..884cca6 100644 --- a/dashboard-server/klaviyo-server/services/redis.service.js +++ b/dashboard-server/klaviyo-server/services/redis.service.js @@ -141,14 +141,27 @@ _getCacheKey(type, params = {}) { metric, daily, cacheKey, - isPreviousPeriod + isPreviousPeriod, + customFilters } = params; let key = `klaviyo:${type}`; // Handle "stats:details" for daily or metric-based keys if (type === 'stats:details') { - key += `:${metric}${daily ? ':daily' : ''}`; + // Add metric to key + key += `:${metric || 'all'}`; + + // Add daily flag if present + if (daily) { + key += ':daily'; + } + + // Add custom filters hash if present + if (customFilters?.length) { + const filterHash = customFilters.join('').replace(/[^a-zA-Z0-9]/g, ''); + key += `:${filterHash}`; + } } // If a specific cache key is provided, use it (highest priority) @@ -157,15 +170,27 @@ _getCacheKey(type, params = {}) { } // Otherwise, build a default cache key else if (timeRange) { - key += `:${timeRange}${metricId ? `:${metricId}` : ''}`; + key += `:${timeRange}`; + if (metricId) { + key += `:${metricId}`; + } if (isPreviousPeriod) { - key += ':prev'; + key += ':prev'; } } else if (startDate && endDate) { - key += `:custom:${startDate}:${endDate}${metricId ? `:${metricId}` : ''}`; - if (isPreviousPeriod) { - key += ':prev'; + // For custom date ranges, include both dates in the key + key += `:custom:${startDate}:${endDate}`; + if (metricId) { + key += `:${metricId}`; } + if (isPreviousPeriod) { + key += ':prev'; + } + } + + // Add order type to key if present + if (['pre_orders', 'local_pickup', 'on_hold'].includes(metric)) { + key += `:${metric}`; } return key; diff --git a/dashboard-server/klaviyo-server/utils/time.utils.js b/dashboard-server/klaviyo-server/utils/time.utils.js index 6a6a467..ab86c02 100644 --- a/dashboard-server/klaviyo-server/utils/time.utils.js +++ b/dashboard-server/klaviyo-server/utils/time.utils.js @@ -163,8 +163,9 @@ export class TimeManager { return null; } case 'today': { + const dayStart = this.getDayStart(now); return { - start: this.getDayStart(now), + start: dayStart, end: this.getDayEnd(now) }; } @@ -177,27 +178,29 @@ export class TimeManager { } case 'last7days': { // For last 7 days, we want to include today and the previous 6 days - // So if today is 12/17, we want 12/11 1am to 12/17 12:59am const dayStart = this.getDayStart(now); + const weekStart = dayStart.minus({ days: 6 }); return { - start: dayStart.minus({ days: 6 }), // 6 days ago from start of today - end: this.getDayEnd(now) // end of today + start: weekStart, + end: this.getDayEnd(now) }; } case 'last30days': { // Include today and previous 29 days const dayStart = this.getDayStart(now); + const monthStart = dayStart.minus({ days: 29 }); return { - start: dayStart.minus({ days: 29 }), // 29 days ago from start of today - end: this.getDayEnd(now) // end of today + start: monthStart, + end: this.getDayEnd(now) }; } case 'last90days': { // Include today and previous 89 days const dayStart = this.getDayStart(now); + const start = dayStart.minus({ days: 89 }); return { - start: dayStart.minus({ days: 89 }), // 89 days ago from start of today - end: this.getDayEnd(now) // end of today + start, + end: this.getDayEnd(now) }; } case 'thisWeek': { @@ -212,7 +215,6 @@ export class TimeManager { const lastWeek = now.minus({ weeks: 1 }); const weekStart = this.getWeekStart(lastWeek); const weekEnd = weekStart.plus({ days: 6 }); // 6 days after start = Saturday - return { start: weekStart, end: this.getDayEnd(weekEnd) @@ -221,7 +223,6 @@ export class TimeManager { case 'thisMonth': { const dayStart = this.getDayStart(now); const monthStart = dayStart.startOf('month').set({ hour: this.dayStartHour }); - return { start: monthStart, end: this.getDayEnd(now) @@ -231,7 +232,6 @@ export class TimeManager { const lastMonth = now.minus({ months: 1 }); const monthStart = lastMonth.startOf('month').set({ hour: this.dayStartHour }); const monthEnd = monthStart.plus({ months: 1 }).minus({ days: 1 }); - return { start: monthStart, end: this.getDayEnd(monthEnd) @@ -298,7 +298,9 @@ export class TimeManager { * @returns {Object} Object with start and end DateTime objects */ getPreviousPeriod(period, now = this.getNow()) { - switch (period) { + const normalizedPeriod = period.startsWith('previous') ? period.replace('previous', 'last') : period; + + switch (normalizedPeriod) { case 'today': { const yesterday = now.minus({ days: 1 }); return { @@ -313,8 +315,7 @@ export class TimeManager { end: this.getDayEnd(twoDaysAgo) }; } - case 'last7days': - case 'previous7days': { + case 'last7days': { const dayStart = this.getDayStart(now); const currentStart = dayStart.minus({ days: 6 }); const prevEnd = currentStart.minus({ milliseconds: 1 }); @@ -324,8 +325,7 @@ export class TimeManager { end: prevEnd }; } - case 'last30days': - case 'previous30days': { + case 'last30days': { const dayStart = this.getDayStart(now); const currentStart = dayStart.minus({ days: 29 }); const prevEnd = currentStart.minus({ milliseconds: 1 }); @@ -335,8 +335,7 @@ export class TimeManager { end: prevEnd }; } - case 'last90days': - case 'previous90days': { + case 'last90days': { const dayStart = this.getDayStart(now); const currentStart = dayStart.minus({ days: 89 }); const prevEnd = currentStart.minus({ milliseconds: 1 }); @@ -382,13 +381,6 @@ export class TimeManager { end: prevEnd }; } - case 'twoDaysAgo': { - const twoDaysAgo = now.minus({ days: 2 }); - return { - start: this.getDayStart(twoDaysAgo), - end: this.getDayEnd(twoDaysAgo) - }; - } default: console.warn(`[TimeManager] No previous period defined for: ${period}`); return null;