extract frontend analytics helpers
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m12s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m12s
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user