extract frontend analytics helpers
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m12s

This commit is contained in:
Cauê Faleiros
2026-05-27 15:58:12 -03:00
parent 8c2590c56a
commit 72ded82ec7
9 changed files with 362 additions and 258 deletions

View File

@@ -2,10 +2,9 @@ import { useMemo, useState } from 'react';
import { Link, useOutletContext } from 'react-router-dom';
import { Search, ChevronRight, Filter, ChevronLeft, Download } from 'lucide-react';
import type { OrderData, DateRange } from '../types';
import { parseOrderDate, exportToCSV } from '../dataService';
import { exportToCSV } from '../dataService';
import DateRangePicker from '../components/DateRangePicker';
type SortOption = 'recent' | 'spent_desc' | 'spent_asc' | 'items_desc' | 'items_asc';
import { buildClientsSummary, type ClientSortOption } from '../analytics/clients';
const Clients = () => {
const { dateRange, setDateRange, ordersData } = useOutletContext<{
@@ -14,66 +13,14 @@ const Clients = () => {
ordersData: OrderData[]
}>();
const [searchTerm, setSearchTerm] = useState('');
const [sortBy, setSortBy] = useState<SortOption>('recent');
const [sortBy, setSortBy] = useState<ClientSortOption>('recent');
// Pagination state
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(10);
const clientsData = useMemo(() => {
const orders = ordersData.filter(order => {
const orderDate = parseOrderDate(order.Data_Pedido);
return orderDate >= dateRange.start && orderDate <= dateRange.end;
});
const clientMap: Record<string, { totalSpent: number, totalItems: number, uniqueOrders: Set<string>, lastPurchase: number, phone: string }> = {};
orders.forEach(order => {
const clientName = order.Nome_Cliente || `Cliente Desconhecido (Pedido ${order.Valor_Pedido})`;
if (!clientMap[clientName]) {
clientMap[clientName] = { totalSpent: 0, totalItems: 0, uniqueOrders: new Set(), lastPurchase: 0, phone: '' };
}
if (order.Fone_Cliente) {
clientMap[clientName].phone = order.Fone_Cliente;
}
// Calculate total spent based on quantity * unit price
clientMap[clientName].totalSpent += (order.Quantidade * order.Valor_Unitario);
clientMap[clientName].totalItems += order.Quantidade;
clientMap[clientName].uniqueOrders.add(`${order.Data_Pedido}_${order.Valor_Pedido}`);
const orderTime = parseOrderDate(order.Data_Pedido).getTime();
if (orderTime > clientMap[clientName].lastPurchase) {
clientMap[clientName].lastPurchase = orderTime;
}
});
let result = Object.keys(clientMap).map(name => ({
name,
phone: clientMap[name].phone,
totalSpent: clientMap[name].totalSpent,
totalItems: clientMap[name].totalItems,
orderCount: clientMap[name].uniqueOrders.size, // Grouped by unique date+value combinations
lastPurchase: clientMap[name].lastPurchase
}));
if (searchTerm) {
result = result.filter(c => c.name.toLowerCase().includes(searchTerm.toLowerCase()));
}
return result.sort((a, b) => {
switch (sortBy) {
case 'recent': return b.lastPurchase - a.lastPurchase;
case 'spent_desc': return b.totalSpent - a.totalSpent;
case 'spent_asc': return a.totalSpent - b.totalSpent;
case 'items_desc': return b.totalItems - a.totalItems;
case 'items_asc': return a.totalItems - b.totalItems;
default: return 0;
}
});
return buildClientsSummary(ordersData, dateRange, searchTerm, sortBy);
}, [searchTerm, sortBy, ordersData, dateRange]);
// Pagination logic
@@ -107,7 +54,7 @@ const Clients = () => {
<select
value={sortBy}
onChange={(e) => {
setSortBy(e.target.value as SortOption);
setSortBy(e.target.value as ClientSortOption);
setCurrentPage(1);
}}
className="appearance-none bg-dark-card border border-dark-border text-dark-text text-sm rounded-xl pl-9 pr-8 py-2.5 focus:outline-none focus:border-brand-primary transition-colors shadow-sm cursor-pointer"