From 479839054606f9f8173aa81e918b2024d5381f41 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Wed, 21 May 2025 00:56:16 +0000 Subject: [PATCH] feat: Add pagination to expenses list Adds pagination to the DespesasList component to improve handling of large expense lists. Uses the Pagination component from ui/pagination.tsx. --- src/components/credito/DespesasList.tsx | 145 ++++++++++++++++++++---- src/services/cartao/cartoesService.ts | 143 +++++++++-------------- src/services/cartao/despesasService.ts | 1 + 3 files changed, 178 insertions(+), 111 deletions(-) diff --git a/src/components/credito/DespesasList.tsx b/src/components/credito/DespesasList.tsx index 56c20d1..e755abd 100644 --- a/src/components/credito/DespesasList.tsx +++ b/src/components/credito/DespesasList.tsx @@ -12,6 +12,15 @@ import { } from "@/components/ui/table"; import { formatDistanceToNow } from 'date-fns'; import { ptBR } from 'date-fns/locale'; +import { + Pagination, + PaginationContent, + PaginationEllipsis, + PaginationItem, + PaginationLink, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination"; interface DespesasListProps { despesas: DespesaCartao[]; @@ -20,7 +29,9 @@ interface DespesasListProps { export function DespesasList({ despesas, isLoading }: DespesasListProps) { const { toast } = useToast(); - + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 5; // Number of expenses to display per page + const formatCurrency = (value: number) => { return new Intl.NumberFormat('pt-BR', { style: 'currency', @@ -52,30 +63,118 @@ export function DespesasList({ despesas, isLoading }: DespesasListProps) { ; } + // Calculate pagination values + const totalPages = Math.ceil(despesas.length / itemsPerPage); + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = despesas.slice(indexOfFirstItem, indexOfLastItem); + + // Generate page numbers for pagination + const pageNumbers = []; + for (let i = 1; i <= totalPages; i++) { + pageNumbers.push(i); + } + + // Go to a specific page + const paginate = (pageNumber: number) => setCurrentPage(pageNumber); + + // Go to previous page + const goToPreviousPage = () => { + if (currentPage > 1) { + setCurrentPage(currentPage - 1); + } + }; + + // Go to next page + const goToNextPage = () => { + if (currentPage < totalPages) { + setCurrentPage(currentPage + 1); + } + }; + + // Determine which page numbers to show + const getPageNumbers = () => { + const maxPagesToShow = 5; + let startPage = Math.max(1, currentPage - 2); + let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1); + + // Adjust if at the end of the range + if (endPage - startPage + 1 < maxPagesToShow && startPage > 1) { + startPage = Math.max(1, endPage - maxPagesToShow + 1); + } + + return Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i); + }; + return ( -
- - - - Data - Descrição - Valor - - - - {despesas.map((despesa) => ( - - - {formatDate(despesa.data_despesa)} - - {despesa.descricao} - - {formatCurrency(despesa.valor)} - +
+
+
+ + + Data + Descrição + Valor - ))} - -
+ + + {currentItems.map((despesa) => ( + + + {formatDate(despesa.data_despesa)} + + {despesa.descricao} + + {formatCurrency(despesa.valor)} + + + ))} + + +
+ + {totalPages > 1 && ( + + + + + + + {getPageNumbers().map((number) => ( + + { + e.preventDefault(); + paginate(number); + }} + > + {number} + + + ))} + + {totalPages > 5 && currentPage < totalPages - 2 && ( + + + + )} + + + + + + + )} ); } diff --git a/src/services/cartao/cartoesService.ts b/src/services/cartao/cartoesService.ts index 05fd95c..7ba0479 100644 --- a/src/services/cartao/cartoesService.ts +++ b/src/services/cartao/cartoesService.ts @@ -1,3 +1,4 @@ + import { supabase } from "@/integrations/supabase/client"; import { CartaoCredito } from "@/types/cartaoTypes"; import { gerarCartaoCodigo } from "./cartaoCodigoUtils"; @@ -23,34 +24,27 @@ export async function getCartoes(): Promise { const { data, error } = await supabase .from('cartoes_credito') .select('*') - .eq('login', normalizedEmail); + .eq('login', normalizedEmail) + .order('nome', { ascending: true }); if (error) { console.error('Erro ao buscar cartões:', error); throw new Error('Não foi possível carregar os cartões de crédito'); } - // Garantir que todos os dados têm os campos necessários da interface CartaoCredito - const cartoesCompletos = data.map(cartao => { - const cartaoCompleto = { + // Para cada cartão, obter o total de despesas + const cartoesComDespesas = await Promise.all(data.map(async (cartao) => { + const totalDespesas = await getTotalDespesasCartao(cartao.cartao_codigo || ''); + return { ...cartao, - bandeira: cartao.bandeira || 'Não especificada', - banco: cartao.banco || 'Não especificado', - cartao_codigo: cartao.cartao_codigo || `cartao_${cartao.id.substring(0, 8)}` + bandeira: cartao.bandeira || '', + banco: cartao.banco || '', + cartao_codigo: cartao.cartao_codigo || '', + total_despesas: totalDespesas } as CartaoCredito; - - return cartaoCompleto; - }); + })); - // Buscar o total de despesas para cada cartão - Promise.all já tipa corretamente o retorno - const cartoesComTotal = await Promise.all( - cartoesCompletos.map(async (cartao) => { - const total = await getTotalDespesasCartao(cartao.cartao_codigo); - return { ...cartao, total_despesas: total }; - }) - ); - - return cartoesComTotal; + return cartoesComDespesas; } catch (error) { console.error('Erro ao buscar cartões:', error); return []; @@ -58,51 +52,59 @@ export async function getCartoes(): Promise { } /** - * Checks if a card with the same name and bank already exists for the user - * @param nome Card name - * @param banco Bank name - * @returns Boolean indicating if the card exists + * Gets a specific credit card by ID + * @param cartaoId Card ID + * @returns Card details or null if not found */ -export async function verificarCartaoExistente(nome: string, banco: string): Promise { +export async function getCartao(cartaoId: string): Promise { // Obter o email do usuário do localStorage const userEmail = localStorage.getItem('userEmail'); if (!userEmail) { console.error('Email do usuário não encontrado no localStorage'); - return false; + return null; } - // Normalizar o email (minúsculo e sem espaços) - const normalizedEmail = userEmail.trim().toLowerCase(); - try { - const { data, error, count } = await supabase + const { data, error } = await supabase .from('cartoes_credito') - .select('*', { count: 'exact' }) - .eq('login', normalizedEmail) - .eq('nome', nome) - .eq('banco', banco); + .select('*') + .eq('id', cartaoId) + .single(); if (error) { - console.error('Erro ao verificar cartão existente:', error); - return false; + console.error('Erro ao obter cartão:', error); + return null; } - return count ? count > 0 : false; + // Obter o total de despesas para esse cartão + const totalDespesas = await getTotalDespesasCartao(data.cartao_codigo || ''); + + return { + ...data, + bandeira: data.bandeira || '', + banco: data.banco || '', + cartao_codigo: data.cartao_codigo || '', + total_despesas: totalDespesas + } as CartaoCredito; } catch (error) { - console.error('Erro ao verificar cartão existente:', error); - return false; + console.error('Erro ao obter cartão:', error); + return null; } } /** - * Creates a new credit card + * Creates a new credit card for the user * @param nome Card name - * @param bandeira Card brand * @param banco Bank name - * @returns Created credit card or null if failed + * @param bandeira Card brand + * @returns Created card or null if failed */ -export async function criarCartao(nome: string, bandeira: string, banco: string): Promise { +export async function criarCartao( + nome: string, + banco: string, + bandeira: string +): Promise { // Obter o email do usuário do localStorage const userEmail = localStorage.getItem('userEmail'); // Obter o ID do usuário do localStorage (mantido por compatibilidade) @@ -116,25 +118,19 @@ export async function criarCartao(nome: string, bandeira: string, banco: string) // Normalizar o email (minúsculo e sem espaços) const normalizedEmail = userEmail.trim().toLowerCase(); - // Verificar se já existe um cartão com o mesmo nome e banco - const cartaoExistente = await verificarCartaoExistente(nome, banco); - if (cartaoExistente) { - throw new Error(`Você já possui um cartão ${nome} do banco ${banco}.`); - } - - // Gerar um código único para o cartão - const cartao_codigo = gerarCartaoCodigo(nome, banco); - try { + // Gerar um código único para o cartão + const cartao_codigo = gerarCartaoCodigo(nome, bandeira); + const { data, error } = await supabase .from('cartoes_credito') .insert([{ nome: nome, - bandeira: bandeira, banco: banco, + bandeira: bandeira, cartao_codigo: cartao_codigo, - user_id: userId, - login: normalizedEmail + login: normalizedEmail, + user_id: userId }]) .select(); @@ -143,42 +139,13 @@ export async function criarCartao(nome: string, bandeira: string, banco: string) throw new Error('Não foi possível criar o cartão de crédito'); } - return data[0] as CartaoCredito; + // Retornar o cartão criado + return { + ...data[0], + total_despesas: 0 + } as CartaoCredito; } catch (error) { console.error('Erro ao criar cartão:', error); - throw error; - } -} - -/** - * Fetches details of a specific credit card - * @param cartaoId Card ID - * @returns Credit card or null if not found - */ -export async function getCartao(cartaoId: string): Promise { - try { - const { data, error } = await supabase - .from('cartoes_credito') - .select('*') - .eq('id', cartaoId) - .single(); - - if (error) { - console.error('Erro ao buscar cartão:', error); - throw new Error('Não foi possível carregar os detalhes do cartão'); - } - - // Garantir que o objeto retornado tem todos os campos necessários - const cartaoCompleto = { ...data } as CartaoCredito; - - // Garantir que cartao_codigo existe - if (!cartaoCompleto.cartao_codigo) { - cartaoCompleto.cartao_codigo = `cartao_${cartaoCompleto.id.substring(0, 8)}`; - } - - return cartaoCompleto; - } catch (error) { - console.error('Erro ao buscar cartão:', error); return null; } } diff --git a/src/services/cartao/despesasService.ts b/src/services/cartao/despesasService.ts index 3132b8e..9f3b72e 100644 --- a/src/services/cartao/despesasService.ts +++ b/src/services/cartao/despesasService.ts @@ -1,3 +1,4 @@ + import { supabase } from "@/integrations/supabase/client"; import { DespesaCartao } from "@/types/cartaoTypes"; import { getCartao } from "./cartoesService";