Align seller table with client design
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 45s

This commit is contained in:
Cauê Faleiros
2026-08-04 10:13:04 -03:00
parent ff0f73e87c
commit 7831d13c7e

View File

@@ -1,7 +1,8 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { Link, useOutletContext } from 'react-router-dom'; import { Link, useOutletContext } from 'react-router-dom';
import { ArrowRight, BriefcaseBusiness, Search } from 'lucide-react'; import { BriefcaseBusiness, ChevronRight, Search } from 'lucide-react';
import DateRangePicker from '../components/DateRangePicker'; import DateRangePicker from '../components/DateRangePicker';
import PaginationControls from '../components/PaginationControls';
import RefreshStatus from '../components/RefreshStatus'; import RefreshStatus from '../components/RefreshStatus';
import { fetchSellerAnalytics } from '../dataService'; import { fetchSellerAnalytics } from '../dataService';
import { formatDisplayName, removeTrailingSellerId } from '../displayFormatters'; import { formatDisplayName, removeTrailingSellerId } from '../displayFormatters';
@@ -15,6 +16,8 @@ const Sellers = () => {
const [sellers, setSellers] = useState<SellerAnalyticsItem[]>([]); const [sellers, setSellers] = useState<SellerAnalyticsItem[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(10);
useEffect(() => { useEffect(() => {
let isMounted = true; let isMounted = true;
@@ -39,6 +42,10 @@ const Sellers = () => {
const totalOrders = sellers.reduce((sum, seller) => sum + seller.orderCount, 0); const totalOrders = sellers.reduce((sum, seller) => sum + seller.orderCount, 0);
const totalCustomers = sellers.reduce((sum, seller) => sum + seller.customerCount, 0); const totalCustomers = sellers.reduce((sum, seller) => sum + seller.customerCount, 0);
const topSeller = sellers[0]; const topSeller = sellers[0];
const totalPages = Math.ceil(visibleSellers.length / itemsPerPage);
const safeCurrentPage = Math.min(currentPage, totalPages || 1);
const startIndex = (safeCurrentPage - 1) * itemsPerPage;
const paginatedSellers = visibleSellers.slice(startIndex, startIndex + itemsPerPage);
return ( return (
<div className="space-y-6"> <div className="space-y-6">
@@ -80,31 +87,46 @@ const Sellers = () => {
</div> </div>
<label className="relative block w-full lg:max-w-sm"> <label className="relative block w-full lg:max-w-sm">
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-dark-muted" /> <Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-dark-muted" />
<input value={search} onChange={event => setSearch(event.target.value)} placeholder="Buscar vendedor..." className="h-11 w-full rounded-xl border border-dark-border bg-dark-input pl-10 pr-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" /> <input value={search} onChange={event => { setSearch(event.target.value); setCurrentPage(1); }} placeholder="Buscar vendedor..." className="h-11 w-full rounded-xl border border-dark-border bg-dark-input pl-10 pr-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" />
</label> </label>
</div> </div>
{isLoading && !sellers.length ? ( {isLoading && !sellers.length ? (
<div className="space-y-3 p-5">{[1, 2, 3, 4].map(item => <div key={item} className="skeleton h-24" />)}</div> <div className="space-y-3 p-5">{[1, 2, 3, 4].map(item => <div key={item} className="skeleton h-24" />)}</div>
) : visibleSellers.length ? ( ) : visibleSellers.length ? (
<div className="divide-y divide-dark-border"> <>
{visibleSellers.map((seller, index) => ( <div className="overflow-x-auto">
<Link key={seller.id} to={`/sellers/${encodeURIComponent(seller.id)}`} className="group grid gap-4 px-4 py-4 transition-colors hover:bg-dark-input/35 sm:px-5 xl:grid-cols-[minmax(260px,1.25fr)_repeat(4,minmax(110px,0.55fr))_auto] xl:items-center"> <table className="w-full min-w-[980px] text-left text-sm">
<div className="flex min-w-0 items-center gap-3"> <thead className="border-b border-dark-border bg-dark-header text-dark-muted">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-brand-primary/25 bg-brand-primary/10 text-sm font-bold text-brand-primary">{index + 1}</div> <tr>
<div className="min-w-0"> <th className="px-6 py-4 text-[10px] font-bold uppercase tracking-wider">Posição</th>
<p className="truncate text-sm font-bold text-dark-text">{formatDisplayName(removeTrailingSellerId(seller.name))}</p> <th className="px-6 py-4 text-[10px] font-bold uppercase tracking-wider">Nome do vendedor</th>
<p className="mt-1 text-xs font-semibold text-dark-muted">{seller.id.startsWith('name:') ? 'Sem ID Tiny' : `ID ${seller.id}`}</p> <th className="px-6 py-4 text-[10px] font-bold uppercase tracking-wider">Receita</th>
</div> <th className="px-6 py-4 text-[10px] font-bold uppercase tracking-wider">Pedidos</th>
</div> <th className="px-6 py-4 text-[10px] font-bold uppercase tracking-wider">Clientes</th>
<div><p className="text-[10px] font-bold uppercase tracking-widest text-dark-muted xl:hidden">Receita</p><p className="text-sm font-bold text-brand-primary">{formatCurrency(seller.revenue)}</p></div> <th className="px-6 py-4 text-[10px] font-bold uppercase tracking-wider">Ticket médio</th>
<div><p className="text-[10px] font-bold uppercase tracking-widest text-dark-muted xl:hidden">Pedidos</p><p className="text-sm font-bold text-dark-text">{formatNumber(seller.orderCount)}</p></div> <th className="px-6 py-4 text-[10px] font-bold uppercase tracking-wider">Produtos vendidos</th>
<div><p className="text-[10px] font-bold uppercase tracking-widest text-dark-muted xl:hidden">Clientes</p><p className="text-sm font-bold text-dark-text">{formatNumber(seller.customerCount)}</p></div> <th className="px-6 py-4 text-right text-[10px] font-bold uppercase tracking-wider">Ações</th>
<div><p className="text-[10px] font-bold uppercase tracking-widest text-dark-muted xl:hidden">Ticket médio</p><p className="text-sm font-bold text-dark-text">{formatCurrency(seller.averageTicket)}</p></div> </tr>
<ArrowRight className="hidden h-5 w-5 text-dark-muted transition-transform group-hover:translate-x-1 group-hover:text-brand-primary xl:block" /> </thead>
</Link> <tbody className="divide-y divide-dark-border">
{paginatedSellers.map((seller, index) => (
<tr key={seller.id} className="group transition-colors hover:bg-dark-input/50">
<td className="px-6 py-2.5"><span className="inline-flex h-7 w-7 items-center justify-center rounded-full bg-dark-border text-xs font-bold text-dark-muted">{startIndex + index + 1}</span></td>
<td className="px-6 py-2.5"><p className="font-semibold text-dark-text">{formatDisplayName(removeTrailingSellerId(seller.name))}</p><p className="mt-1 text-[10px] font-semibold text-dark-muted">{seller.id.startsWith('name:') ? 'Sem ID Tiny' : `ID ${seller.id}`}</p></td>
<td className="px-6 py-2.5 font-bold text-brand-primary">{formatCurrency(seller.revenue)}</td>
<td className="px-6 py-2.5 font-semibold text-dark-text">{formatNumber(seller.orderCount)}</td>
<td className="px-6 py-2.5 font-semibold text-dark-text">{formatNumber(seller.customerCount)}</td>
<td className="px-6 py-2.5 font-semibold text-dark-text">{formatCurrency(seller.averageTicket)}</td>
<td className="px-6 py-2.5 text-xs font-medium text-dark-muted">{formatNumber(seller.quantitySold)} un. ({formatNumber(seller.orderCount)} {seller.orderCount === 1 ? 'pedido' : 'pedidos'})</td>
<td className="px-6 py-2.5 text-right"><Link to={`/sellers/${encodeURIComponent(seller.id)}`} className="inline-flex items-center text-xs font-bold text-brand-primary transition-opacity hover:opacity-80">Ver detalhes<ChevronRight className="ml-1 h-3.5 w-3.5" /></Link></td>
</tr>
))} ))}
</tbody>
</table>
</div> </div>
<PaginationControls totalItems={visibleSellers.length} currentPage={safeCurrentPage} totalPages={totalPages} pageSize={itemsPerPage} pageSizeOptions={[10, 20, 50]} itemLabel="vendedores" pageSizeLabel="itens por página" startIndex={startIndex} endIndex={Math.min(startIndex + itemsPerPage, visibleSellers.length)} onPageChange={setCurrentPage} onPageSizeChange={pageSize => { setItemsPerPage(pageSize); setCurrentPage(1); }} />
</>
) : ( ) : (
<div className="flex flex-col items-center px-6 py-16 text-center"><BriefcaseBusiness className="h-10 w-10 text-dark-muted" /><p className="mt-4 text-sm font-bold text-dark-text">Nenhum vendedor encontrado.</p><p className="mt-1 text-sm font-semibold text-dark-muted">Ajuste a busca ou o período selecionado.</p></div> <div className="flex flex-col items-center px-6 py-16 text-center"><BriefcaseBusiness className="h-10 w-10 text-dark-muted" /><p className="mt-4 text-sm font-bold text-dark-text">Nenhum vendedor encontrado.</p><p className="mt-1 text-sm font-semibold text-dark-muted">Ajuste a busca ou o período selecionado.</p></div>
)} )}