Initial commit: Dockerized, Postgres, CI/CD pipeline
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 3m36s

This commit is contained in:
Cauê Faleiros
2026-05-04 14:40:14 -03:00
commit 0e5354f1fe
40 changed files with 6640 additions and 0 deletions

145
src/pages/ClientDetails.tsx Normal file
View 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;