From 0d58128969e5bbfdc7cfc52e83e812b9352edac8 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:21:30 +0000 Subject: [PATCH] Implement credit card and expense features. Add credit card registration form with required fields and auto-generated code. Implement expense registration form with card selection and required fields. Establish association logic between expenses and cards based on user and card codes. Ensure data privacy and validation. --- src/components/credito/CartaoCreditoForm.tsx | 94 ++++++++++- src/components/credito/CartaoCreditoList.tsx | 4 + .../credito/DespesaCartaoFormSelect.tsx | 156 ++++++++++++------ .../transacoes/TransactionSummaryCards.tsx | 1 + src/services/cartaoCreditoService.ts | 102 ++++++++++-- src/types/cartaoTypes.ts | 6 + 6 files changed, 296 insertions(+), 67 deletions(-) diff --git a/src/components/credito/CartaoCreditoForm.tsx b/src/components/credito/CartaoCreditoForm.tsx index 4d8e473..d8bec5e 100644 --- a/src/components/credito/CartaoCreditoForm.tsx +++ b/src/components/credito/CartaoCreditoForm.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; @@ -15,9 +15,31 @@ import { } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +// Opções para as listas suspensas +const bandeirasCartao = [ + 'Visa', 'Mastercard', 'Elo', 'American Express', 'Hipercard', + 'Diners Club', 'Credicard', 'Sorocred', 'Cabal', 'Banescard', + 'Sicoobcard', 'Sicredi', 'Alelo', 'Ticket', 'VR', 'ValeCard', 'Havan' +]; + +const bancos = [ + 'Itaú', 'Bradesco', 'Santander', 'Banco do Brasil', 'Caixa', 'Nubank', + 'Inter', 'C6 Bank', 'Next', 'Original', 'Pan', 'Banco Bari', + 'Sicoob', 'Sicredi', 'Neon', 'BTG Pactual', 'PagBank', 'Will Bank', 'XP' +]; const cartaoSchema = z.object({ nome: z.string().min(1, { message: 'Nome do cartão é obrigatório' }), + bandeira: z.string({ required_error: 'Selecione uma bandeira' }), + banco: z.string({ required_error: 'Selecione um banco' }), }); type CartaoFormValues = z.infer; @@ -35,14 +57,30 @@ export function CartaoCreditoForm({ onSuccess, onCancel }: CartaoCreditoFormProp resolver: zodResolver(cartaoSchema), defaultValues: { nome: '', + bandeira: '', + banco: '', } }); + // Função para gerar um código único para o cartão + const gerarCartaoCodigo = (nome: string, banco: string) => { + const randomStr = Math.random().toString(36).substring(2, 8); + return `${banco.toLowerCase()}_${nome.toLowerCase().replace(/\s/g, '')}_${randomStr}`; + }; + async function onSubmit(data: CartaoFormValues) { setIsSubmitting(true); try { - const resultado = await criarCartao(data.nome); + // Gerar código único para o cartão + const cartao_codigo = gerarCartaoCodigo(data.nome, data.banco); + + const resultado = await criarCartao( + data.nome, + data.bandeira, + data.banco, + cartao_codigo + ); if (resultado) { toast({ @@ -79,13 +117,63 @@ export function CartaoCreditoForm({ onSuccess, onCancel }: CartaoCreditoFormProp Nome do Cartão - + )} /> + ( + + Bandeira + + + + )} + /> + + ( + + Banco Emissor + + + + )} + /> +
+ + + + + date > new Date() || date < new Date("1900-01-01") + } + initialFocus + locale={ptBR} + /> + + )} @@ -181,7 +229,7 @@ export function DespesaCartaoFormSelect({ cartoes, onSuccess, onCancel }: Despes Descrição - + diff --git a/src/components/transacoes/TransactionSummaryCards.tsx b/src/components/transacoes/TransactionSummaryCards.tsx index 9d32adf..f21e483 100644 --- a/src/components/transacoes/TransactionSummaryCards.tsx +++ b/src/components/transacoes/TransactionSummaryCards.tsx @@ -24,6 +24,7 @@ export const TransactionSummaryCards = ({ + Ganhos do mês diff --git a/src/services/cartaoCreditoService.ts b/src/services/cartaoCreditoService.ts index 943a56f..990b338 100644 --- a/src/services/cartaoCreditoService.ts +++ b/src/services/cartaoCreditoService.ts @@ -29,7 +29,7 @@ export async function getCartoes(): Promise { // Buscar o total de despesas para cada cartão const cartoesComTotal = await Promise.all( data.map(async (cartao: CartaoCredito) => { - const total = await getTotalDespesasCartao(cartao.id); + const total = await getTotalDespesasCartao(cartao.cartao_codigo); return { ...cartao, total_despesas: total }; }) ); @@ -41,8 +41,41 @@ export async function getCartoes(): Promise { } } +// Verificar se já existe um cartão com o mesmo nome e banco para o usuário +export async function verificarCartaoExistente(nome: string, banco: 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; + } + + // Normalizar o email (minúsculo e sem espaços) + const normalizedEmail = userEmail.trim().toLowerCase(); + + try { + const { data, error, count } = await supabase + .from('cartoes_credito') + .select('*', { count: 'exact' }) + .eq('login', normalizedEmail) + .eq('nome', nome) + .eq('banco', banco); + + if (error) { + console.error('Erro ao verificar cartão existente:', error); + return false; + } + + return count ? count > 0 : false; + } catch (error) { + console.error('Erro ao verificar cartão existente:', error); + return false; + } +} + // Função para criar um novo cartão de crédito -export async function criarCartao(nome: string): Promise { +export async function criarCartao(nome: string, bandeira: string, banco: string, cartao_codigo: 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) @@ -56,11 +89,20 @@ export async function criarCartao(nome: string): Promise { // 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}.`); + } + try { const { data, error } = await supabase .from('cartoes_credito') .insert([{ nome: nome, + bandeira: bandeira, + banco: banco, + cartao_codigo: cartao_codigo, user_id: userId, login: normalizedEmail }]) @@ -74,7 +116,7 @@ export async function criarCartao(nome: string): Promise { return data[0]; } catch (error) { console.error('Erro ao criar cartão:', error); - return null; + throw error; } } @@ -100,12 +142,24 @@ export async function getCartao(cartaoId: string): Promise } // Função para buscar despesas de um cartão -export async function getDespesasCartao(cartaoId: string): Promise { +export async function getDespesasCartao(cartaoCodigo: 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 []; + } + + // Normalizar o email (minúsculo e sem espaços) + const normalizedEmail = userEmail.trim().toLowerCase(); + try { const { data, error } = await supabase .from('despesas_cartao') .select('*') - .eq('cartao_id', cartaoId) + .eq('cartao_codigo', cartaoCodigo) + .eq('login', normalizedEmail) .order('data_despesa', { ascending: false }); if (error) { @@ -121,15 +175,31 @@ export async function getDespesasCartao(cartaoId: string): Promise { +export async function criarDespesa(cartao_id: string, cartao_codigo: string, valor: number, data_despesa: string, descricao: 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) + const userId = localStorage.getItem('userId'); + + if (!userEmail || !userId) { + console.error('Dados do usuário não encontrados no localStorage'); + return null; + } + + // Normalizar o email (minúsculo e sem espaços) + const normalizedEmail = userEmail.trim().toLowerCase(); + try { const { data, error } = await supabase .from('despesas_cartao') .insert([{ - cartao_id: cartaoId, + cartao_id: cartao_id, + cartao_codigo: cartao_codigo, valor: valor, data_despesa: data_despesa, - descricao: descricao + descricao: descricao, + login: normalizedEmail, + user_id: userId }]) .select(); @@ -146,12 +216,24 @@ export async function criarDespesa(cartaoId: string, valor: number, data_despesa } // Função para calcular o total de despesas de um cartão -export async function getTotalDespesasCartao(cartaoId: string): Promise { +export async function getTotalDespesasCartao(cartaoCodigo: 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 0; + } + + // Normalizar o email (minúsculo e sem espaços) + const normalizedEmail = userEmail.trim().toLowerCase(); + try { const { data, error } = await supabase .from('despesas_cartao') .select('valor') - .eq('cartao_id', cartaoId); + .eq('cartao_codigo', cartaoCodigo) + .eq('login', normalizedEmail); if (error) { console.error('Erro ao calcular total de despesas:', error); diff --git a/src/types/cartaoTypes.ts b/src/types/cartaoTypes.ts index 23f100d..856c21f 100644 --- a/src/types/cartaoTypes.ts +++ b/src/types/cartaoTypes.ts @@ -2,6 +2,9 @@ export interface CartaoCredito { id: string; nome: string; + bandeira?: string; + banco?: string; + cartao_codigo: string; user_id: string; login?: string; created_at: string; @@ -11,6 +14,9 @@ export interface CartaoCredito { export interface DespesaCartao { id: string; cartao_id: string; + cartao_codigo: string; + user_id?: string; + login?: string; valor: number; data_despesa: string; descricao: string;