forked from git.gladyson/openmonetis
refactor(lancamentos): melhora layout e UX da tabela
- Adiciona coluna de categoria com ícone - Move data de compra para dentro da célula de nome - Simplifica exibição de pagador removendo Badge - Refatora coluna conta/cartão com tooltips informativos - Adiciona suporte a liquidação para Transferência bancária e Pré-Pago - Remove imports não utilizados (RiBankCard2Line, RiBankLine) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
import { CategoryIcon } from "@/components/categorias/category-icon";
|
||||||
import { EmptyState } from "@/components/empty-state";
|
import { EmptyState } from "@/components/empty-state";
|
||||||
import MoneyValues from "@/components/money-values";
|
import MoneyValues from "@/components/money-values";
|
||||||
import { TypeBadge } from "@/components/type-badge";
|
import { TypeBadge } from "@/components/type-badge";
|
||||||
@@ -45,8 +46,6 @@ import {
|
|||||||
RiAddCircleFill,
|
RiAddCircleFill,
|
||||||
RiAddCircleLine,
|
RiAddCircleLine,
|
||||||
RiArrowLeftRightLine,
|
RiArrowLeftRightLine,
|
||||||
RiBankCard2Line,
|
|
||||||
RiBankLine,
|
|
||||||
RiChat1Line,
|
RiChat1Line,
|
||||||
RiCheckLine,
|
RiCheckLine,
|
||||||
RiDeleteBin5Line,
|
RiDeleteBin5Line,
|
||||||
@@ -69,6 +68,7 @@ import {
|
|||||||
RowSelectionState,
|
RowSelectionState,
|
||||||
SortingState,
|
SortingState,
|
||||||
useReactTable,
|
useReactTable,
|
||||||
|
VisibilityState,
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -152,13 +152,10 @@ const buildColumns = ({
|
|||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: "purchaseDate",
|
||||||
accessorKey: "purchaseDate",
|
accessorKey: "purchaseDate",
|
||||||
header: "Data",
|
header: () => null,
|
||||||
cell: ({ row }) => (
|
cell: () => null,
|
||||||
<span className="whitespace-nowrap text-muted-foreground">
|
|
||||||
{formatDate(row.original.purchaseDate)}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "name",
|
accessorKey: "name",
|
||||||
@@ -166,6 +163,7 @@ const buildColumns = ({
|
|||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
|
purchaseDate,
|
||||||
installmentCount,
|
installmentCount,
|
||||||
currentInstallment,
|
currentInstallment,
|
||||||
paymentMethod,
|
paymentMethod,
|
||||||
@@ -192,16 +190,21 @@ const buildColumns = ({
|
|||||||
return (
|
return (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<EstabelecimentoLogo name={name} size={28} />
|
<EstabelecimentoLogo name={name} size={28} />
|
||||||
<Tooltip>
|
<span className="flex flex-col">
|
||||||
<TooltipTrigger asChild>
|
<span className="text-[11px] text-muted-foreground">
|
||||||
<span className="line-clamp-2 max-w-[160px] font-semibold truncate">
|
{formatDate(purchaseDate)}
|
||||||
|
</span>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span className="line-clamp-2 max-w-[160px] font-semibold truncate">
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top" className="max-w-xs">
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</TooltipContent>
|
||||||
</TooltipTrigger>
|
</Tooltip>
|
||||||
<TooltipContent side="top" className="max-w-xs">
|
</span>
|
||||||
{name}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{isDivided && (
|
{isDivided && (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -359,23 +362,37 @@ const buildColumns = ({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "categoriaName",
|
||||||
|
header: "Categoria",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const { categoriaName, categoriaIcon } = row.original;
|
||||||
|
|
||||||
|
if (!categoriaName) {
|
||||||
|
return <span className="text-muted-foreground">—</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<CategoryIcon name={categoriaIcon} className="size-4" />
|
||||||
|
<span>{categoriaName}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "pagadorName",
|
accessorKey: "pagadorName",
|
||||||
header: "Pagador",
|
header: "Pagador",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const { pagadorId, pagadorName, pagadorAvatar } = row.original;
|
const { pagadorId, pagadorName, pagadorAvatar } = row.original;
|
||||||
|
|
||||||
if (!pagadorName) {
|
|
||||||
return <Badge variant="outline">—</Badge>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const label = pagadorName.trim() || "Sem pagador";
|
const label = pagadorName.trim() || "Sem pagador";
|
||||||
const displayName = label.split(/\s+/)[0] ?? label;
|
const displayName = label.split(/\s+/)[0] ?? label;
|
||||||
const avatarSrc = getAvatarSrc(pagadorAvatar);
|
const avatarSrc = getAvatarSrc(pagadorAvatar);
|
||||||
const initial = displayName.charAt(0).toUpperCase() || "?";
|
const initial = displayName.charAt(0).toUpperCase() || "?";
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
<Avatar className="size-6 border border-border/60 bg-background">
|
<Avatar className="size-7">
|
||||||
<AvatarImage src={avatarSrc} alt={`Avatar de ${label}`} />
|
<AvatarImage src={avatarSrc} alt={`Avatar de ${label}`} />
|
||||||
<AvatarFallback className="text-[10px] font-medium uppercase">
|
<AvatarFallback className="text-[10px] font-medium uppercase">
|
||||||
{initial}
|
{initial}
|
||||||
@@ -387,30 +404,18 @@ const buildColumns = ({
|
|||||||
|
|
||||||
if (!pagadorId) {
|
if (!pagadorId) {
|
||||||
return (
|
return (
|
||||||
<Badge
|
<span className="inline-flex items-center gap-2">{content}</span>
|
||||||
variant="outline"
|
|
||||||
className="max-w-[200px] px-2 py-0.5"
|
|
||||||
title={label}
|
|
||||||
>
|
|
||||||
<span className="inline-flex items-center gap-2">{content}</span>
|
|
||||||
</Badge>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Link
|
||||||
asChild
|
href={`/pagadores/${pagadorId}`}
|
||||||
variant="outline"
|
className="inline-flex items-center gap-2 hover:underline"
|
||||||
className="max-w-[200px] px-2 py-0.5"
|
title={label}
|
||||||
>
|
>
|
||||||
<Link
|
{content}
|
||||||
href={`/pagadores/${pagadorId}`}
|
</Link>
|
||||||
className="inline-flex items-center gap-2"
|
|
||||||
title={label}
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
</Link>
|
|
||||||
</Badge>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -427,6 +432,7 @@ const buildColumns = ({
|
|||||||
contaId,
|
contaId,
|
||||||
userId,
|
userId,
|
||||||
} = row.original;
|
} = row.original;
|
||||||
|
const isCartao = Boolean(cartaoName);
|
||||||
const label = cartaoName ?? contaName;
|
const label = cartaoName ?? contaName;
|
||||||
const logoSrc = resolveLogoSrc(cartaoLogo ?? contaLogo);
|
const logoSrc = resolveLogoSrc(cartaoLogo ?? contaLogo);
|
||||||
const href = cartaoId
|
const href = cartaoId
|
||||||
@@ -434,46 +440,57 @@ const buildColumns = ({
|
|||||||
: contaId
|
: contaId
|
||||||
? `/contas/${contaId}/extrato`
|
? `/contas/${contaId}/extrato`
|
||||||
: null;
|
: null;
|
||||||
const Icon = cartaoId ? RiBankCard2Line : contaId ? RiBankLine : null;
|
|
||||||
const isOwnData = userId === currentUserId;
|
const isOwnData = userId === currentUserId;
|
||||||
|
|
||||||
if (!label) {
|
|
||||||
return "—";
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<span className="inline-flex items-center gap-2">
|
||||||
{logoSrc ? (
|
{logoSrc && (
|
||||||
<Image
|
<Image
|
||||||
src={logoSrc}
|
src={logoSrc}
|
||||||
alt={`Logo de ${label}`}
|
alt={`Logo de ${label}`}
|
||||||
width={32}
|
width={30}
|
||||||
height={32}
|
height={30}
|
||||||
className="rounded-lg"
|
className="rounded-md"
|
||||||
/>
|
/>
|
||||||
) : null}
|
)}
|
||||||
<span className="truncate">{label}</span>
|
<span className="truncate">{label}</span>
|
||||||
{Icon ? (
|
</span>
|
||||||
<Icon className="size-4 text-muted-foreground" aria-hidden />
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isOwnData) {
|
if (!isOwnData || !href) {
|
||||||
return <div className="flex items-center gap-2">{content}</div>;
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>{content}</TooltipTrigger>
|
||||||
|
<TooltipContent side="top">
|
||||||
|
{isCartao ? "Cartão" : "Conta"}: {label}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Tooltip>
|
||||||
href={href ?? "#"}
|
<TooltipTrigger asChild>
|
||||||
className={cn(
|
<Link
|
||||||
"flex items-center gap-2",
|
href={href}
|
||||||
href ? "underline " : "pointer-events-none",
|
className="inline-flex items-center gap-2 hover:underline"
|
||||||
)}
|
>
|
||||||
aria-disabled={!href}
|
{logoSrc && (
|
||||||
>
|
<Image
|
||||||
{content}
|
src={logoSrc}
|
||||||
</Link>
|
alt={`Logo de ${label}`}
|
||||||
|
width={30}
|
||||||
|
height={30}
|
||||||
|
className="rounded-md"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span className="truncate">{label}</span>
|
||||||
|
</Link>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top">
|
||||||
|
{isCartao ? "Cartão" : "Conta"}: {label}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -494,6 +511,8 @@ const buildColumns = ({
|
|||||||
"Cartão de crédito",
|
"Cartão de crédito",
|
||||||
"Dinheiro",
|
"Dinheiro",
|
||||||
"Cartão de débito",
|
"Cartão de débito",
|
||||||
|
"Transferência bancária",
|
||||||
|
"Pré-Pago | VR/VA",
|
||||||
].includes(paymentMethod);
|
].includes(paymentMethod);
|
||||||
|
|
||||||
if (!showSettlementButton) {
|
if (!showSettlementButton) {
|
||||||
@@ -504,7 +523,9 @@ const buildColumns = ({
|
|||||||
paymentMethod === "Pix" ||
|
paymentMethod === "Pix" ||
|
||||||
paymentMethod === "Boleto" ||
|
paymentMethod === "Boleto" ||
|
||||||
paymentMethod === "Dinheiro" ||
|
paymentMethod === "Dinheiro" ||
|
||||||
paymentMethod === "Cartão de débito";
|
paymentMethod === "Cartão de débito" ||
|
||||||
|
paymentMethod === "Transferência bancária" ||
|
||||||
|
paymentMethod === "Pré-Pago | VR/VA";
|
||||||
const readOnly = row.original.readonly;
|
const readOnly = row.original.readonly;
|
||||||
const loading = isSettlementLoading(row.original.id);
|
const loading = isSettlementLoading(row.original.id);
|
||||||
const settled = Boolean(row.original.isSettled);
|
const settled = Boolean(row.original.isSettled);
|
||||||
@@ -512,11 +533,15 @@ const buildColumns = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
variant={settled ? "secondary" : "ghost"}
|
variant={"outline"}
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
onClick={() => handleToggleSettlement(row.original)}
|
onClick={() => handleToggleSettlement(row.original)}
|
||||||
disabled={loading || readOnly || !canToggleSettlement}
|
disabled={loading || readOnly || !canToggleSettlement}
|
||||||
className={canToggleSettlement ? undefined : "opacity-70"}
|
className={cn(
|
||||||
|
"border-none",
|
||||||
|
!canToggleSettlement && "opacity-70 ",
|
||||||
|
settled && "border-none",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
@@ -673,6 +698,9 @@ export function LancamentosTable({
|
|||||||
const [sorting, setSorting] = useState<SortingState>([
|
const [sorting, setSorting] = useState<SortingState>([
|
||||||
{ id: "purchaseDate", desc: true },
|
{ id: "purchaseDate", desc: true },
|
||||||
]);
|
]);
|
||||||
|
const [columnVisibility] = useState<VisibilityState>({
|
||||||
|
purchaseDate: false,
|
||||||
|
});
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 30,
|
pageSize: 30,
|
||||||
@@ -714,6 +742,7 @@ export function LancamentosTable({
|
|||||||
columns,
|
columns,
|
||||||
state: {
|
state: {
|
||||||
sorting,
|
sorting,
|
||||||
|
columnVisibility,
|
||||||
pagination,
|
pagination,
|
||||||
rowSelection,
|
rowSelection,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user