Initial commit: Dockerized, Postgres, CI/CD pipeline
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 3m36s
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 3m36s
This commit is contained in:
145
src/pages/ClientDetails.tsx
Normal file
145
src/pages/ClientDetails.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useParams, Link, useOutletContext } from 'react-router-dom';
|
||||
import { ArrowLeft, User, Calendar, Tag, Package, DollarSign } from 'lucide-react';
|
||||
import type { OrderData } from '../types';
|
||||
|
||||
const ClientDetails = () => {
|
||||
const { name } = useParams<{ name: string }>();
|
||||
const decodedName = name ? decodeURIComponent(name) : '';
|
||||
const { ordersData } = useOutletContext<{ ordersData: OrderData[] }>();
|
||||
|
||||
const { groupedOrders, totalSpent, totalItems } = useMemo(() => {
|
||||
const orders = ordersData;
|
||||
const clientOrders = orders.filter(order => {
|
||||
const clientName = order.Nome_Cliente || `Cliente Desconhecido (Pedido ${order.Valor_Pedido})`;
|
||||
return clientName === decodedName;
|
||||
});
|
||||
|
||||
const groupedOrdersMap: Record<string, { date: string, orderTotal: number, items: OrderData[] }> = {};
|
||||
let totalSpent = 0;
|
||||
let totalItems = 0;
|
||||
|
||||
clientOrders.forEach(order => {
|
||||
totalSpent += (order.Quantidade * order.Valor_Unitario);
|
||||
totalItems += order.Quantidade;
|
||||
|
||||
// Use date and total order value as a unique cart/order identifier
|
||||
const key = `${order.Data_Pedido}_${order.Valor_Pedido}`;
|
||||
if (!groupedOrdersMap[key]) {
|
||||
groupedOrdersMap[key] = {
|
||||
date: order.Data_Pedido,
|
||||
orderTotal: order.Valor_Pedido,
|
||||
items: []
|
||||
};
|
||||
}
|
||||
groupedOrdersMap[key].items.push(order);
|
||||
});
|
||||
|
||||
// Sort grouped orders by date descending
|
||||
const groupedOrders = Object.values(groupedOrdersMap).sort((a, b) => {
|
||||
const [dayA, monthA, yearA] = a.date.split('-').map(Number);
|
||||
const [dayB, monthB, yearB] = b.date.split('-').map(Number);
|
||||
return new Date(yearB, monthB - 1, dayB).getTime() - new Date(yearA, monthA - 1, dayA).getTime();
|
||||
});
|
||||
|
||||
return { groupedOrders, totalSpent, totalItems };
|
||||
}, [decodedName, ordersData]);
|
||||
|
||||
const formatCurrency = (value: number) => {
|
||||
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value);
|
||||
};
|
||||
|
||||
if (!groupedOrders.length) {
|
||||
return (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-zinc-500 dark:text-dark-muted font-medium">Cliente não encontrado.</p>
|
||||
<Link to="/clients" className="text-brand-primary hover:underline mt-4 inline-block font-bold">Voltar para clientes</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header Area */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<Link to="/clients" className="inline-flex items-center text-sm font-bold text-zinc-400 dark:text-dark-muted hover:text-zinc-900 dark:hover:text-dark-text transition-colors w-fit">
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
Voltar
|
||||
</Link>
|
||||
|
||||
<div className="flex flex-col md:flex-row md:items-end justify-between gap-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-16 h-16 rounded-2xl bg-white dark:bg-dark-card border border-zinc-200 dark:border-dark-border flex items-center justify-center shadow-sm">
|
||||
<User className="w-8 h-8 text-brand-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-zinc-900 dark:text-dark-text">{decodedName}</h1>
|
||||
<p className="text-zinc-500 dark:text-dark-muted font-medium">Histórico completo de compras</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-8">
|
||||
<div className="flex flex-col items-center">
|
||||
<p className="text-xs font-bold text-zinc-400 dark:text-dark-muted uppercase tracking-widest mb-1">Total Gasto</p>
|
||||
<p className="text-2xl font-bold text-brand-primary">{formatCurrency(totalSpent)}</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<p className="text-xs font-bold text-zinc-400 dark:text-dark-muted uppercase tracking-widest mb-1">Itens Comprados</p>
|
||||
<p className="text-2xl font-bold text-zinc-900 dark:text-dark-text">{totalItems}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Orders List */}
|
||||
<div className="flex flex-col gap-6">
|
||||
{groupedOrders.map((group, groupIndex) => (
|
||||
<div key={groupIndex} className="bg-white dark:bg-dark-card border border-zinc-200 dark:border-dark-border rounded-2xl overflow-hidden shadow-sm">
|
||||
<div className="p-4 border-b border-zinc-100 dark:border-dark-border bg-zinc-50/50 dark:bg-dark-header flex justify-between items-center">
|
||||
<h2 className="text-sm font-bold uppercase tracking-wider text-zinc-500 dark:text-dark-muted">
|
||||
Data do Pedido: {group.date}
|
||||
</h2>
|
||||
<span className="text-sm font-bold text-brand-primary">
|
||||
Total do Pedido: {formatCurrency(group.orderTotal)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-zinc-100 dark:divide-dark-border">
|
||||
{group.items.map((order, index) => (
|
||||
<div key={`${order.ID_Produto}-${index}`} className="px-4 py-2 flex flex-col md:flex-row md:items-center justify-between gap-3 hover:bg-zinc-50/50 dark:hover:bg-dark-input/30 transition-colors">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-0.5">
|
||||
<div className="flex items-center gap-1">
|
||||
<Tag className="w-3 h-3 text-zinc-400 dark:text-dark-muted" />
|
||||
<span className="text-[10px] font-bold text-zinc-400 dark:text-dark-muted">ID: {order.ID_Produto}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-sm font-bold text-zinc-900 dark:text-dark-text truncate mb-1">{order.Descricao_Produto}</h3>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<div className="flex items-center gap-1.5 text-[11px]">
|
||||
<Package className="w-3.5 h-3.5 text-zinc-400 dark:text-dark-muted" />
|
||||
<span className="text-zinc-500 dark:text-dark-muted font-medium">Qtd: <span className="text-zinc-900 dark:text-dark-text font-bold">{order.Quantidade}</span></span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5 text-[11px]">
|
||||
<DollarSign className="w-3.5 h-3.5 text-zinc-400 dark:text-dark-muted" />
|
||||
<span className="text-zinc-500 dark:text-dark-muted font-medium">Preço: <span className="text-zinc-900 dark:text-dark-text font-bold">{formatCurrency(order.Valor_Unitario)}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-right shrink-0">
|
||||
<p className="text-[10px] font-bold text-zinc-400 dark:text-dark-muted uppercase tracking-widest mb-0.5">Subtotal</p>
|
||||
<p className="text-base font-bold text-zinc-900 dark:text-dark-text">{formatCurrency(order.Quantidade * order.Valor_Unitario)}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClientDetails;
|
||||
Reference in New Issue
Block a user