From 68a79838a2cce47b6d82e78f5ba7e7923db29795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Fri, 12 Jun 2026 14:36:22 -0300 Subject: [PATCH] Use RFM summaries for clients list --- src/components/Layout.tsx | 5 ++-- src/pages/Clients.tsx | 56 +++++++++++++++++---------------------- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 2a9657d..1650cda 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -6,7 +6,8 @@ import { fetchData, fetchStock, logout } from '../dataService'; const Layout = () => { const location = useLocation(); - const needsRawData = location.pathname.startsWith('/products') || location.pathname.startsWith('/clients'); + const needsRawData = location.pathname.startsWith('/products') || + (location.pathname.startsWith('/clients/') && location.pathname !== '/clients'); const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(() => { return localStorage.getItem('graph_sidebar_collapsed') === 'true'; }); @@ -47,7 +48,7 @@ const Layout = () => { useEffect(() => { if (!needsRawData) return; - // Product and client pages still depend on raw orders until their API migration is complete. + // Product pages and client details still depend on raw orders until their API migration is complete. // eslint-disable-next-line react-hooks/set-state-in-effect void loadData(true); }, [loadData, needsRawData]); diff --git a/src/pages/Clients.tsx b/src/pages/Clients.tsx index 355f299..4fc5cc0 100644 --- a/src/pages/Clients.tsx +++ b/src/pages/Clients.tsx @@ -1,10 +1,10 @@ import { useEffect, useMemo, useState } from 'react'; import { Link, useOutletContext } from 'react-router-dom'; import { Search, ChevronRight, Filter, ChevronLeft, Download } from 'lucide-react'; -import type { OrderData, DateRange, RfmAnalytics, RfmClient } from '../types'; +import type { DateRange, RfmAnalytics, RfmClient } from '../types'; import { exportToCSV, fetchRfmAnalytics } from '../dataService'; import DateRangePicker from '../components/DateRangePicker'; -import { buildClientsSummary, type ClientSortOption, type ClientSummary } from '../analytics/clients'; +import type { ClientSortOption, ClientSummary } from '../analytics/clients'; const clientTypeStyles: Record = { 'Campeão': 'border-emerald-500/30 bg-emerald-500/15 text-emerald-300', @@ -63,10 +63,9 @@ const sortClients = (clients: ClientSummary[], sortBy: ClientSortOption) => { }; const Clients = () => { - const { dateRange, setDateRange, ordersData } = useOutletContext<{ + const { dateRange, setDateRange } = useOutletContext<{ dateRange: DateRange, - setDateRange: (range: DateRange) => void, - ordersData: OrderData[] + setDateRange: (range: DateRange) => void }>(); const [searchTerm, setSearchTerm] = useState(''); const [sortBy, setSortBy] = useState('recent'); @@ -92,35 +91,30 @@ const Clients = () => { }; }, [dateRange]); - const rfmClientsByIdentity = useMemo(() => { - const map = new Map(); - - (rfmAnalytics?.clients || []).forEach(client => { - if (client.phone) map.set(`phone:${client.phone}`, client); - map.set(`name:${client.name.toLowerCase()}`, client); - }); - - return map; - }, [rfmAnalytics]); - const allClientsData = useMemo(() => { - const localClients = buildClientsSummary(ordersData, dateRange, searchTerm, sortBy); - const enrichedClients = localClients.map(client => { - const rfmClient = (client.phone && rfmClientsByIdentity.get(`phone:${client.phone}`)) || - rfmClientsByIdentity.get(`name:${client.name.toLowerCase()}`); + const normalizedSearch = searchTerm.trim().toLowerCase(); + const clients = (rfmAnalytics?.clients || []).map((client): ClientSummary => ({ + name: client.name, + phone: client.phone, + totalSpent: client.monetary, + averageTicket: client.frequency ? client.monetary / client.frequency : 0, + totalItems: client.quantityPurchased, + orderCount: client.frequency, + lastPurchase: client.lastPurchaseDate ? new Date(client.lastPurchaseDate).getTime() : 0, + clientType: backendSegmentToClientType[client.segmentKey] || client.segmentLabel, + rfmScore: client.rfmScore, + rfmPriority: getRfmPriority(client) + })); - if (!rfmClient) return client; + const filteredClients = normalizedSearch + ? clients.filter(client => + client.name.toLowerCase().includes(normalizedSearch) || + client.phone.toLowerCase().includes(normalizedSearch) + ) + : clients; - return { - ...client, - clientType: backendSegmentToClientType[rfmClient.segmentKey] || client.clientType, - rfmScore: rfmClient.rfmScore, - rfmPriority: getRfmPriority(rfmClient) - }; - }); - - return sortClients(enrichedClients, sortBy); - }, [searchTerm, sortBy, ordersData, dateRange, rfmClientsByIdentity]); + return sortClients(filteredClients, sortBy); + }, [searchTerm, sortBy, rfmAnalytics]); const clientsData = useMemo(() => { if (clientTypeFilter === 'all') return allClientsData;