Refactor: Split TransactionsTable into components

Refactors the TransactionsTable component into smaller, more manageable components for improved code organization and readability.
This commit is contained in:
gpt-engineer-app[bot] 2025-06-24 18:44:40 +00:00
parent 47e301c9bf
commit c2c12ae5b3
7 changed files with 508 additions and 304 deletions

View File

@ -1,29 +1,12 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { format } from 'date-fns';
import { ChevronDown, Search, ChevronLeft, ChevronRight, Edit, Trash2 } from 'lucide-react';
import { Transaction } from '@/types/financialTypes';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { cn } from '@/lib/utils';
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog';
import { useToast } from '@/hooks/use-toast';
import { deleteTransacao } from '@/services/transacaoService';
import { Table } from '@/components/ui/table';
import TransactionTableSearch from './table/TransactionTableSearch';
import TransactionTableHeader from './table/TransactionTableHeader';
import TransactionTableBody from './table/TransactionTableBody';
import TransactionTablePagination from './table/TransactionTablePagination';
import TransactionDeleteDialog from './table/TransactionDeleteDialog';
import { useTransactionTableLogic } from './table/useTransactionTableLogic';
interface TransactionsTableProps {
transactions: Transaction[];
@ -40,304 +23,74 @@ const TransactionsTable = ({
onEdit,
onDelete
}: TransactionsTableProps) => {
const [searchQuery, setSearchQuery] = useState('');
const [sortColumn, setSortColumn] = useState<string>('quando');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const [currentPage, setCurrentPage] = useState(1);
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
const [transactionToDelete, setTransactionToDelete] = useState<Transaction | null>(null);
const { toast } = useToast();
const itemsPerPage = showPagination ? 10 : 5;
const handleSort = (column: string) => {
if (sortColumn === column) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortColumn(column);
setSortDirection('asc');
}
};
const {
searchQuery,
setSearchQuery,
sortColumn,
sortDirection,
currentPage,
setCurrentPage,
deleteConfirmOpen,
setDeleteConfirmOpen,
transactionToDelete,
totalPages,
paginatedTransactions,
filteredTransactions,
itemsPerPage,
formatCurrency,
handleSort,
handleDeleteClick,
handleConfirmDelete,
} = useTransactionTableLogic({ transactions, showPagination, onDelete });
const handleEditTransaction = (transaction: Transaction) => {
if (onEdit) {
onEdit(transaction);
} else {
console.log('Editar transação:', transaction);
// Implementar edição futura se não houver callback
}
};
const handleDeleteClick = (transaction: Transaction) => {
setTransactionToDelete(transaction);
setDeleteConfirmOpen(true);
};
const handleConfirmDelete = async () => {
if (!transactionToDelete) return;
try {
await deleteTransacao(transactionToDelete.id);
toast({
title: "Transação excluída",
description: "A transação foi removida com sucesso",
});
// Callback para recarregar os dados
if (onDelete) {
onDelete();
}
} catch (error) {
console.error('Erro ao excluir transação:', error);
toast({
title: "Erro",
description: "Não foi possível excluir a transação",
variant: "destructive"
});
} finally {
setDeleteConfirmOpen(false);
setTransactionToDelete(null);
}
};
const filteredTransactions = transactions.filter((transaction) => {
const query = searchQuery.toLowerCase();
return (
transaction.estabelecimento?.toLowerCase().includes(query) ||
transaction.detalhes?.toLowerCase().includes(query) ||
transaction.categoria?.toLowerCase().includes(query) ||
transaction.tipo?.toLowerCase().includes(query)
);
});
const sortedTransactions = [...filteredTransactions].sort((a, b) => {
if (sortColumn === 'valor') {
return sortDirection === 'asc' ? a.valor - b.valor : b.valor - a.valor;
}
if (sortColumn === 'quando') {
return sortDirection === 'asc'
? new Date(a.quando).getTime() - new Date(b.quando).getTime()
: new Date(b.quando).getTime() - new Date(a.quando).getTime();
}
const aValue = a[sortColumn as keyof Transaction]?.toString().toLowerCase() || '';
const bValue = b[sortColumn as keyof Transaction]?.toString().toLowerCase() || '';
return sortDirection === 'asc'
? aValue.localeCompare(bValue)
: bValue.localeCompare(aValue);
});
// Paginação
const totalPages = Math.ceil(sortedTransactions.length / itemsPerPage);
const paginatedTransactions = sortedTransactions.slice(
(currentPage - 1) * itemsPerPage,
currentPage * itemsPerPage
);
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL',
}).format(value);
};
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="relative w-full max-w-sm">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Buscar transações..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-8"
/>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="ml-2">
Filtrar <ChevronDown className="ml-1 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setSearchQuery('')}>
Todas
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setSearchQuery('receita')}>
Receitas
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setSearchQuery('despesa')}>
Despesas
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<TransactionTableSearch
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
/>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead
className="cursor-pointer w-[160px]"
onClick={() => handleSort('quando')}
>
Data {sortColumn === 'quando' && (sortDirection === 'asc' ? '↑' : '↓')}
</TableHead>
<TableHead
className="cursor-pointer"
onClick={() => handleSort('estabelecimento')}
>
Estabelecimento {sortColumn === 'estabelecimento' && (sortDirection === 'asc' ? '↑' : '↓')}
</TableHead>
<TableHead
className="cursor-pointer"
onClick={() => handleSort('detalhes')}
>
Detalhes {sortColumn === 'detalhes' && (sortDirection === 'asc' ? '↑' : '↓')}
</TableHead>
<TableHead
className="cursor-pointer"
onClick={() => handleSort('categoria')}
>
Categoria {sortColumn === 'categoria' && (sortDirection === 'asc' ? '↑' : '↓')}
</TableHead>
<TableHead
className="text-right cursor-pointer"
onClick={() => handleSort('valor')}
>
Valor {sortColumn === 'valor' && (sortDirection === 'asc' ? '↑' : '↓')}
</TableHead>
<TableHead className="w-[120px] text-right">Ações</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
Array(5).fill(0).map((_, i) => (
<TableRow key={`skeleton-${i}`}>
{Array(6).fill(0).map((_, j) => (
<TableCell key={`cell-${i}-${j}`} className="p-2">
<div className="h-4 bg-muted rounded animate-pulse-gentle" />
</TableCell>
))}
</TableRow>
))
) : paginatedTransactions.length === 0 ? (
<TableRow>
<TableCell colSpan={6} className="text-center py-8 text-muted-foreground">
{searchQuery ? 'Nenhuma transação encontrada' : 'Não há transações disponíveis'}
</TableCell>
</TableRow>
) : (
paginatedTransactions.map((transaction) => (
<TableRow key={transaction.id}>
<TableCell className="font-medium">
{format(new Date(transaction.quando), 'dd/MM/yyyy')}
</TableCell>
<TableCell>{transaction.estabelecimento}</TableCell>
<TableCell>{transaction.detalhes}</TableCell>
<TableCell>
<span className="inline-block px-2 py-1 text-xs font-medium rounded-md bg-secondary">
{transaction.categoria}
</span>
</TableCell>
<TableCell className={cn(
"text-right font-medium",
transaction.tipo === 'receita' ? "text-green-600" : "text-red-600"
)}>
{formatCurrency(Math.abs(transaction.valor))}
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end space-x-1">
<Button
variant="ghost"
size="icon"
onClick={() => handleEditTransaction(transaction)}
title="Editar"
className="hover:bg-blue-50 hover:text-blue-600"
>
<Edit className="h-4 w-4 text-blue-600" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => handleDeleteClick(transaction)}
title="Excluir"
className="hover:bg-red-50 hover:text-red-600"
>
<Trash2 className="h-4 w-4 text-red-600" />
</Button>
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
<TransactionTableHeader
sortColumn={sortColumn}
sortDirection={sortDirection}
onSort={handleSort}
/>
<TransactionTableBody
transactions={paginatedTransactions}
isLoading={isLoading}
onEdit={handleEditTransaction}
onDelete={handleDeleteClick}
formatCurrency={formatCurrency}
/>
</Table>
</div>
{showPagination && totalPages > 0 && (
<div className="flex items-center justify-between pt-2">
<div className="text-sm text-muted-foreground">
Mostrando <span className="font-medium">{Math.min(paginatedTransactions.length, itemsPerPage)}</span> de{" "}
<span className="font-medium">{filteredTransactions.length}</span> transações
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(currentPage - 1)}
disabled={currentPage === 1}
>
<ChevronLeft className="h-4 w-4" />
Anterior
</Button>
<div className="text-sm">
Página <span className="font-medium">{currentPage}</span> de{" "}
<span className="font-medium">{totalPages}</span>
</div>
<Button
variant="outline"
size="sm"
onClick={() => setCurrentPage(currentPage + 1)}
disabled={currentPage === totalPages}
>
Próxima
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
)}
{!showPagination && filteredTransactions.length > itemsPerPage && (
<div className="flex justify-center pt-2">
<Link to="/transacoes">
<Button
variant="outline"
size="sm"
>
Ver todas as transações
</Button>
</Link>
</div>
)}
<TransactionTablePagination
showPagination={showPagination}
currentPage={currentPage}
totalPages={totalPages}
itemsPerPage={itemsPerPage}
totalItems={filteredTransactions.length}
displayedItems={paginatedTransactions.length}
onPageChange={setCurrentPage}
/>
<AlertDialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Confirmar exclusão</AlertDialogTitle>
<AlertDialogDescription>
Tem certeza que deseja excluir esta transação? Esta ação não pode ser desfeita.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancelar</AlertDialogCancel>
<AlertDialogAction onClick={handleConfirmDelete} className="bg-red-600 hover:bg-red-700">
Excluir
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<TransactionDeleteDialog
isOpen={deleteConfirmOpen}
transaction={transactionToDelete}
onClose={() => setDeleteConfirmOpen(false)}
onConfirm={handleConfirmDelete}
/>
</div>
);
};

View File

@ -0,0 +1,33 @@
import { Transaction } from '@/types/financialTypes';
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog';
interface TransactionDeleteDialogProps {
isOpen: boolean;
transaction: Transaction | null;
onClose: () => void;
onConfirm: () => void;
}
const TransactionDeleteDialog = ({ isOpen, transaction, onClose, onConfirm }: TransactionDeleteDialogProps) => {
return (
<AlertDialog open={isOpen} onOpenChange={onClose}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Confirmar exclusão</AlertDialogTitle>
<AlertDialogDescription>
Tem certeza que deseja excluir esta transação? Esta ação não pode ser desfeita.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancelar</AlertDialogCancel>
<AlertDialogAction onClick={onConfirm} className="bg-red-600 hover:bg-red-700">
Excluir
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};
export default TransactionDeleteDialog;

View File

@ -0,0 +1,100 @@
import { format } from 'date-fns';
import { Edit, Trash2 } from 'lucide-react';
import { Transaction } from '@/types/financialTypes';
import { Button } from '@/components/ui/button';
import { TableBody, TableCell, TableRow } from '@/components/ui/table';
import { cn } from '@/lib/utils';
interface TransactionTableBodyProps {
transactions: Transaction[];
isLoading: boolean;
onEdit: (transaction: Transaction) => void;
onDelete: (transaction: Transaction) => void;
formatCurrency: (value: number) => string;
}
const TransactionTableBody = ({
transactions,
isLoading,
onEdit,
onDelete,
formatCurrency
}: TransactionTableBodyProps) => {
if (isLoading) {
return (
<TableBody>
{Array(5).fill(0).map((_, i) => (
<TableRow key={`skeleton-${i}`}>
{Array(6).fill(0).map((_, j) => (
<TableCell key={`cell-${i}-${j}`} className="p-2">
<div className="h-4 bg-muted rounded animate-pulse-gentle" />
</TableCell>
))}
</TableRow>
))}
</TableBody>
);
}
if (transactions.length === 0) {
return (
<TableBody>
<TableRow>
<TableCell colSpan={6} className="text-center py-8 text-muted-foreground">
Nenhuma transação encontrada
</TableCell>
</TableRow>
</TableBody>
);
}
return (
<TableBody>
{transactions.map((transaction) => (
<TableRow key={transaction.id}>
<TableCell className="font-medium">
{format(new Date(transaction.quando), 'dd/MM/yyyy')}
</TableCell>
<TableCell>{transaction.estabelecimento}</TableCell>
<TableCell>{transaction.detalhes}</TableCell>
<TableCell>
<span className="inline-block px-2 py-1 text-xs font-medium rounded-md bg-secondary">
{transaction.categoria}
</span>
</TableCell>
<TableCell className={cn(
"text-right font-medium",
transaction.tipo === 'receita' ? "text-green-600" : "text-red-600"
)}>
{formatCurrency(Math.abs(transaction.valor))}
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end space-x-1">
<Button
variant="ghost"
size="icon"
onClick={() => onEdit(transaction)}
title="Editar"
className="hover:bg-blue-50 hover:text-blue-600"
>
<Edit className="h-4 w-4 text-blue-600" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => onDelete(transaction)}
title="Excluir"
className="hover:bg-red-50 hover:text-red-600"
>
<Trash2 className="h-4 w-4 text-red-600" />
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
);
};
export default TransactionTableBody;

View File

@ -0,0 +1,54 @@
import { TableHead, TableHeader, TableRow } from '@/components/ui/table';
interface TransactionTableHeaderProps {
sortColumn: string;
sortDirection: 'asc' | 'desc';
onSort: (column: string) => void;
}
const TransactionTableHeader = ({ sortColumn, sortDirection, onSort }: TransactionTableHeaderProps) => {
const getSortIcon = (column: string) => {
return sortColumn === column ? (sortDirection === 'asc' ? '↑' : '↓') : '';
};
return (
<TableHeader>
<TableRow>
<TableHead
className="cursor-pointer w-[160px]"
onClick={() => onSort('quando')}
>
Data {getSortIcon('quando')}
</TableHead>
<TableHead
className="cursor-pointer"
onClick={() => onSort('estabelecimento')}
>
Estabelecimento {getSortIcon('estabelecimento')}
</TableHead>
<TableHead
className="cursor-pointer"
onClick={() => onSort('detalhes')}
>
Detalhes {getSortIcon('detalhes')}
</TableHead>
<TableHead
className="cursor-pointer"
onClick={() => onSort('categoria')}
>
Categoria {getSortIcon('categoria')}
</TableHead>
<TableHead
className="text-right cursor-pointer"
onClick={() => onSort('valor')}
>
Valor {getSortIcon('valor')}
</TableHead>
<TableHead className="w-[120px] text-right">Ações</TableHead>
</TableRow>
</TableHeader>
);
};
export default TransactionTableHeader;

View File

@ -0,0 +1,78 @@
import { Link } from 'react-router-dom';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
interface TransactionTablePaginationProps {
showPagination: boolean;
currentPage: number;
totalPages: number;
itemsPerPage: number;
totalItems: number;
displayedItems: number;
onPageChange: (page: number) => void;
}
const TransactionTablePagination = ({
showPagination,
currentPage,
totalPages,
itemsPerPage,
totalItems,
displayedItems,
onPageChange
}: TransactionTablePaginationProps) => {
if (showPagination && totalPages > 0) {
return (
<div className="flex items-center justify-between pt-2">
<div className="text-sm text-muted-foreground">
Mostrando <span className="font-medium">{Math.min(displayedItems, itemsPerPage)}</span> de{" "}
<span className="font-medium">{totalItems}</span> transações
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}
>
<ChevronLeft className="h-4 w-4" />
Anterior
</Button>
<div className="text-sm">
Página <span className="font-medium">{currentPage}</span> de{" "}
<span className="font-medium">{totalPages}</span>
</div>
<Button
variant="outline"
size="sm"
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
Próxima
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
);
}
if (!showPagination && totalItems > itemsPerPage) {
return (
<div className="flex justify-center pt-2">
<Link to="/transacoes">
<Button
variant="outline"
size="sm"
>
Ver todas as transações
</Button>
</Link>
</div>
);
}
return null;
};
export default TransactionTablePagination;

View File

@ -0,0 +1,52 @@
import { useState } from 'react';
import { Search, ChevronDown } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
interface TransactionTableSearchProps {
searchQuery: string;
onSearchChange: (query: string) => void;
}
const TransactionTableSearch = ({ searchQuery, onSearchChange }: TransactionTableSearchProps) => {
return (
<div className="flex items-center justify-between">
<div className="relative w-full max-w-sm">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Buscar transações..."
value={searchQuery}
onChange={(e) => onSearchChange(e.target.value)}
className="pl-8"
/>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="ml-2">
Filtrar <ChevronDown className="ml-1 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => onSearchChange('')}>
Todas
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onSearchChange('receita')}>
Receitas
</DropdownMenuItem>
<DropdownMenuItem onClick={() => onSearchChange('despesa')}>
Despesas
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
};
export default TransactionTableSearch;

View File

@ -0,0 +1,134 @@
import { useState, useMemo } from 'react';
import { Transaction } from '@/types/financialTypes';
import { useToast } from '@/hooks/use-toast';
import { deleteTransacao } from '@/services/transacaoService';
interface UseTransactionTableLogicProps {
transactions: Transaction[];
showPagination?: boolean;
onDelete?: () => void;
}
export const useTransactionTableLogic = ({
transactions,
showPagination = false,
onDelete
}: UseTransactionTableLogicProps) => {
const [searchQuery, setSearchQuery] = useState('');
const [sortColumn, setSortColumn] = useState<string>('quando');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const [currentPage, setCurrentPage] = useState(1);
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
const [transactionToDelete, setTransactionToDelete] = useState<Transaction | null>(null);
const { toast } = useToast();
const itemsPerPage = showPagination ? 10 : 5;
const handleSort = (column: string) => {
if (sortColumn === column) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortColumn(column);
setSortDirection('asc');
}
};
const handleDeleteClick = (transaction: Transaction) => {
setTransactionToDelete(transaction);
setDeleteConfirmOpen(true);
};
const handleConfirmDelete = async () => {
if (!transactionToDelete) return;
try {
await deleteTransacao(transactionToDelete.id);
toast({
title: "Transação excluída",
description: "A transação foi removida com sucesso",
});
if (onDelete) {
onDelete();
}
} catch (error) {
console.error('Erro ao excluir transação:', error);
toast({
title: "Erro",
description: "Não foi possível excluir a transação",
variant: "destructive"
});
} finally {
setDeleteConfirmOpen(false);
setTransactionToDelete(null);
}
};
const filteredTransactions = useMemo(() => {
return transactions.filter((transaction) => {
const query = searchQuery.toLowerCase();
return (
transaction.estabelecimento?.toLowerCase().includes(query) ||
transaction.detalhes?.toLowerCase().includes(query) ||
transaction.categoria?.toLowerCase().includes(query) ||
transaction.tipo?.toLowerCase().includes(query)
);
});
}, [transactions, searchQuery]);
const sortedTransactions = useMemo(() => {
return [...filteredTransactions].sort((a, b) => {
if (sortColumn === 'valor') {
return sortDirection === 'asc' ? a.valor - b.valor : b.valor - a.valor;
}
if (sortColumn === 'quando') {
return sortDirection === 'asc'
? new Date(a.quando).getTime() - new Date(b.quando).getTime()
: new Date(b.quando).getTime() - new Date(a.quando).getTime();
}
const aValue = a[sortColumn as keyof Transaction]?.toString().toLowerCase() || '';
const bValue = b[sortColumn as keyof Transaction]?.toString().toLowerCase() || '';
return sortDirection === 'asc'
? aValue.localeCompare(bValue)
: bValue.localeCompare(aValue);
});
}, [filteredTransactions, sortColumn, sortDirection]);
const totalPages = Math.ceil(sortedTransactions.length / itemsPerPage);
const paginatedTransactions = useMemo(() => {
return sortedTransactions.slice(
(currentPage - 1) * itemsPerPage,
currentPage * itemsPerPage
);
}, [sortedTransactions, currentPage, itemsPerPage]);
const formatCurrency = (value: number) => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL',
}).format(value);
};
return {
searchQuery,
setSearchQuery,
sortColumn,
sortDirection,
currentPage,
setCurrentPage,
deleteConfirmOpen,
setDeleteConfirmOpen,
transactionToDelete,
totalPages,
paginatedTransactions,
filteredTransactions,
itemsPerPage,
formatCurrency,
handleSort,
handleDeleteClick,
handleConfirmDelete,
};
};