diff --git a/src/pages/Rfm.tsx b/src/pages/Rfm.tsx index a868082..dff706c 100644 --- a/src/pages/Rfm.tsx +++ b/src/pages/Rfm.tsx @@ -72,9 +72,30 @@ const formatCurrency = (value: number) => { return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value); }; +const DAY_MS = 24 * 60 * 60 * 1000; + +const parseLocalDate = (value: string) => { + if (!value) return null; + const match = value.match(/^(\d{4})-(\d{2})-(\d{2})/); + if (match) { + const [, year, month, day] = match; + return new Date(Number(year), Number(month) - 1, Number(day)); + } + + const parsedDate = new Date(value); + return Number.isNaN(parsedDate.getTime()) ? null : parsedDate; +}; + +const startOfDay = (date: Date) => { + const nextDate = new Date(date); + nextDate.setHours(0, 0, 0, 0); + return nextDate; +}; + const formatDate = (value: string) => { if (!value) return 'N/A'; - return new Date(value).toLocaleDateString('pt-BR'); + const date = parseLocalDate(value); + return date ? date.toLocaleDateString('pt-BR') : 'N/A'; }; const scoreLabel = (score: number) => { @@ -152,6 +173,15 @@ const Rfm = () => { }); }, [visibleAnalytics]); + const getPeriodRecencyDays = useCallback((lastPurchaseDate: string) => { + const purchaseDate = parseLocalDate(lastPurchaseDate); + if (!purchaseDate) return 0; + + const purchaseDay = startOfDay(purchaseDate).getTime(); + const rangeEndDay = startOfDay(dateRange.end).getTime(); + return Math.max(0, Math.floor((rangeEndDay - purchaseDay) / DAY_MS)); + }, [dateRange.end]); + const filteredClients = useMemo(() => { const normalizedSearch = searchTerm.trim().toLowerCase(); @@ -188,7 +218,7 @@ const Rfm = () => { }>>((insights, segment) => { const segmentClients = clients.filter(client => client.segmentKey === segment.key); const totalOrders = segmentClients.reduce((sum, client) => sum + client.frequency, 0); - const totalRecency = segmentClients.reduce((sum, client) => sum + client.recencyDays, 0); + const totalRecency = segmentClients.reduce((sum, client) => sum + getPeriodRecencyDays(client.lastPurchaseDate), 0); insights[segment.key] = { averageRecencyDays: segmentClients.length ? totalRecency / segmentClients.length : 0, @@ -200,7 +230,7 @@ const Rfm = () => { return insights; }, {}); - }, [clients, segments, totals.totalRevenue]); + }, [clients, segments, getPeriodRecencyDays, totals.totalRevenue]); const selectedSegment = selectedSegmentKey ? getSegment(segments, selectedSegmentKey) : undefined; const selectedSegmentStyle = segmentStyles[selectedSegment?.key || 'lost'] || segmentStyles.lost; @@ -215,7 +245,7 @@ const Rfm = () => { Recencia: client.recencyScore, Frequencia: client.frequencyScore, Monetario: client.monetaryScore, - 'Dias desde ultima compra': client.recencyDays, + 'Dias desde compra no periodo': getPeriodRecencyDays(client.lastPurchaseDate), 'Pedidos no periodo': client.frequency, 'Ticket medio no periodo (R$)': (client.frequency ? client.monetary / client.frequency : 0).toFixed(2).replace('.', ','), 'Receita no periodo (R$)': client.monetary.toFixed(2).replace('.', ','), @@ -253,16 +283,16 @@ const Rfm = () => {
Base RFM
+Clientes no Período
{totals.periodClientCount} com compra no período
+Agrupados pela tag RFM anterior
Receita no Período
Segmentos na Base
+Segmentos com Compra
Maior receita no período: {totals.topSegment?.label || 'N/A'} @@ -275,7 +305,7 @@ const Rfm = () => {
Base classificada até o fim do período; valores mostram apenas o período selecionado.
+Compradores do período agrupados pela tag RFM anterior ao período.
{formatCurrency(selectedSegmentInsights.averageTicket)}
Recência
+Compra período
{selectedSegmentInsights.averageRecencyDays.toFixed(0)} dias