diff --git a/src/analytics/clients.ts b/src/analytics/clients.ts index 2427d6e..ab83242 100644 --- a/src/analytics/clients.ts +++ b/src/analytics/clients.ts @@ -33,10 +33,18 @@ export interface GroupedClientOrder { } export interface ClientDetailsMetrics { + chartData: Array<{ + date: string; + value: number; + }>; groupedOrders: GroupedClientOrder[]; - totalSpent: number; - totalItems: number; + allTimeOrderCount: number; clientPhone: string; + hasClient: boolean; + periodAverageTicket: number; + periodOrderCount: number; + periodSpent: number; + periodItems: number; } const scoreTertile = (value: number, values: number[], higherIsBetter = true) => { @@ -162,18 +170,23 @@ export const buildClientsSummary = ( }); }; -export const buildClientDetailsMetrics = (ordersData: OrderData[], clientName: string): ClientDetailsMetrics => { +export const buildClientDetailsMetrics = (ordersData: OrderData[], clientName: string, dateRange: DateRange): ClientDetailsMetrics => { const clientOrders = ordersData.filter(order => getClientDisplayName(order) === clientName); + const periodOrders = filterOrdersByDateRange(clientOrders, dateRange); const groupedOrdersMap: Record = {}; + const spentByDate: Record = {}; let clientPhone = ''; - let totalSpent = 0; - let totalItems = 0; + let periodSpent = 0; + let periodItems = 0; clientOrders.forEach(order => { if (order.Fone_Cliente && !clientPhone) clientPhone = order.Fone_Cliente; + }); - totalSpent += getOrderItemRevenue(order); - totalItems += order.Quantidade; + periodOrders.forEach(order => { + periodSpent += getOrderItemRevenue(order); + periodItems += order.Quantidade; + spentByDate[order.Data_Pedido] = (spentByDate[order.Data_Pedido] || 0) + getOrderItemRevenue(order); const key = order.ID_Pedido || `${order.Data_Pedido}_${order.Valor_Pedido}`; if (!groupedOrdersMap[key]) { @@ -191,5 +204,22 @@ export const buildClientDetailsMetrics = (ordersData: OrderData[], clientName: s return parseOrderDate(b.date).getTime() - parseOrderDate(a.date).getTime(); }); - return { groupedOrders, totalSpent, totalItems, clientPhone }; + const allTimeOrderIds = new Set(clientOrders.map(order => order.ID_Pedido || `${order.Data_Pedido}_${order.Valor_Pedido}`)); + const periodOrderCount = groupedOrders.length; + const chartData = Object.keys(spentByDate).map(date => ({ + date, + value: spentByDate[date] + })).sort((a, b) => parseOrderDate(a.date).getTime() - parseOrderDate(b.date).getTime()); + + return { + chartData, + groupedOrders, + allTimeOrderCount: allTimeOrderIds.size, + clientPhone, + hasClient: clientOrders.length > 0, + periodAverageTicket: periodOrderCount ? periodSpent / periodOrderCount : 0, + periodOrderCount, + periodSpent, + periodItems + }; }; diff --git a/src/pages/ClientDetails.tsx b/src/pages/ClientDetails.tsx index f028cd2..26bf8ad 100644 --- a/src/pages/ClientDetails.tsx +++ b/src/pages/ClientDetails.tsx @@ -1,30 +1,76 @@ import { useMemo, useState } from 'react'; import { useParams, Link, useOutletContext } from 'react-router-dom'; -import { ArrowLeft, User, Tag, Package, DollarSign, Clock, Phone, ChevronLeft, ChevronRight } from 'lucide-react'; -import type { OrderData } from '../types'; +import { ArrowLeft, User, Tag, Package, DollarSign, Clock, Phone, ChevronLeft, ChevronRight, ShoppingBag, ReceiptText } from 'lucide-react'; +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; +import DateRangePicker from '../components/DateRangePicker'; +import type { DateRange, OrderData } from '../types'; import { buildClientDetailsMetrics } from '../analytics/clients'; +type CustomTooltipProps = { + active?: boolean; + payload?: Array<{ value: number }>; + label?: string; +}; + +const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => { + if (active && payload && payload.length) { + return ( +
+

{label}

+

+ Gasto: {new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(payload[0].value)} +

+
+ ); + } + return null; +}; + const ClientDetails = () => { const { name } = useParams<{ name: string }>(); const decodedName = name ? decodeURIComponent(name) : ''; - const { ordersData, isDataLoading } = useOutletContext<{ ordersData: OrderData[], isDataLoading: boolean }>(); + const { dateRange, setDateRange, ordersData, isDataLoading } = useOutletContext<{ + dateRange: DateRange, + setDateRange: (range: DateRange) => void, + ordersData: OrderData[], + isDataLoading: boolean + }>(); const [currentPage, setCurrentPage] = useState(1); const [ordersPerPage, setOrdersPerPage] = useState(5); - const { groupedOrders, totalSpent, totalItems, clientPhone } = useMemo(() => { - return buildClientDetailsMetrics(ordersData, decodedName); - }, [decodedName, ordersData]); + const { + chartData, + groupedOrders, + allTimeOrderCount, + clientPhone, + hasClient, + periodAverageTicket, + periodItems, + periodOrderCount, + periodSpent + } = useMemo(() => { + return buildClientDetailsMetrics(ordersData, decodedName, dateRange); + }, [dateRange, decodedName, ordersData]); const formatCurrency = (value: number) => { return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value); }; + const formatNumber = (value: number) => { + return new Intl.NumberFormat('pt-BR').format(value); + }; + + const handleDateRangeChange = (range: DateRange) => { + setCurrentPage(1); + setDateRange(range); + }; + const totalPages = Math.ceil(groupedOrders.length / ordersPerPage); const safeCurrentPage = Math.min(currentPage, totalPages || 1); const startIndex = (safeCurrentPage - 1) * ordersPerPage; const paginatedOrders = groupedOrders.slice(startIndex, startIndex + ordersPerPage); - if (!groupedOrders.length && isDataLoading) { + if (!hasClient && isDataLoading) { return (

Carregando cliente...

@@ -32,7 +78,7 @@ const ClientDetails = () => { ); } - if (!groupedOrders.length) { + if (!hasClient) { return (

Cliente não encontrado.

@@ -58,7 +104,9 @@ const ClientDetails = () => {

{decodedName}

-

Histórico completo de compras

+

+ {formatNumber(allTimeOrderCount)} pedidos no histórico completo +

{clientPhone && ( <> @@ -72,67 +120,90 @@ const ClientDetails = () => {
-
-
-

Total Gasto

-

{formatCurrency(totalSpent)}

-
-
-

Itens Comprados

-

{totalItems}

-
+ +
+
+ +
+
+
+

Gasto no Período

+

{formatCurrency(periodSpent)}

+
+
+
+
+
+

Ticket no Período

+

{formatCurrency(periodAverageTicket)}

+
+
+ +
+
+
+
+

Total Pedidos

+

{formatNumber(periodOrderCount)}

+
+
+ +
+
+
+
+

Itens no Período

+

{formatNumber(periodItems)}

+
+
+ +
+
+
+ +
+

Gasto por Data

+ {chartData.length === 0 ? ( +
+ Nenhum gasto no período selecionado. +
+ ) : ( +
+ + + + + formatCurrency(Number(value))} /> + } cursor={{ fill: '#222222' }} /> + + + +
+ )}
{/* Orders List */}
-
-
-

Últimos pedidos

-

- Mostrando {groupedOrders.length > 0 ? startIndex + 1 : 0} a {Math.min(startIndex + ordersPerPage, groupedOrders.length)} de {groupedOrders.length} pedidos -

-
- -
-
- Mostrar - - pedidos por página -
- -
- - -
-
-
- - {paginatedOrders.map((group, groupIndex) => ( + {paginatedOrders.length === 0 ? ( +
+

Nenhum pedido no período selecionado.

+

Altere o filtro de data para ver outros pedidos deste cliente.

+
+ ) : paginatedOrders.map((group, groupIndex) => (