diff --git a/src/components/dashboard/TransactionsTable.tsx b/src/components/dashboard/TransactionsTable.tsx index 3c8c10e..65716e9 100644 --- a/src/components/dashboard/TransactionsTable.tsx +++ b/src/components/dashboard/TransactionsTable.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { Link } from 'react-router-dom'; import { format } from 'date-fns'; -import { ChevronDown, Search, ChevronLeft, ChevronRight } from 'lucide-react'; +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'; @@ -21,22 +21,32 @@ import { 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'; interface TransactionsTableProps { transactions: Transaction[]; isLoading?: boolean; showPagination?: boolean; + onEdit?: (transaction: Transaction) => void; + onDelete?: () => void; } const TransactionsTable = ({ transactions, isLoading = false, - showPagination = false + showPagination = false, + onEdit, + onDelete }: TransactionsTableProps) => { const [searchQuery, setSearchQuery] = useState(''); const [sortColumn, setSortColumn] = useState('quando'); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); const [currentPage, setCurrentPage] = useState(1); + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); + const [transactionToDelete, setTransactionToDelete] = useState(null); + const { toast } = useToast(); const itemsPerPage = showPagination ? 10 : 5; const handleSort = (column: string) => { @@ -48,6 +58,47 @@ const TransactionsTable = ({ } }; + 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 ( @@ -157,13 +208,14 @@ const TransactionsTable = ({ > Valor {sortColumn === 'valor' && (sortDirection === 'asc' ? '↑' : '↓')} + Ações {isLoading ? ( Array(5).fill(0).map((_, i) => ( - {Array(5).fill(0).map((_, j) => ( + {Array(6).fill(0).map((_, j) => (
@@ -172,7 +224,7 @@ const TransactionsTable = ({ )) ) : paginatedTransactions.length === 0 ? ( - + {searchQuery ? 'Nenhuma transação encontrada' : 'Não há transações disponíveis'} @@ -195,6 +247,26 @@ const TransactionsTable = ({ )}> {formatCurrency(Math.abs(transaction.valor))} + +
+ + +
+
)) )} @@ -247,6 +319,23 @@ const TransactionsTable = ({
)} + + + + + Confirmar exclusão + + Tem certeza que deseja excluir esta transação? Esta ação não pode ser desfeita. + + + + Cancelar + + Excluir + + + + ); }; diff --git a/src/pages/GruposWhatsApp.tsx b/src/pages/GruposWhatsApp.tsx index a8a259f..51b4287 100644 --- a/src/pages/GruposWhatsApp.tsx +++ b/src/pages/GruposWhatsApp.tsx @@ -5,11 +5,13 @@ import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Badge } from '@/components/ui/badge'; -import { Loader2, Plus, RefreshCw, MessageSquare } from 'lucide-react'; +import { Loader2, Plus, RefreshCw, MessageSquare, Edit } from 'lucide-react'; import { cadastrarGrupoWhatsApp, listarGruposWhatsApp } from '@/services/gruposWhatsAppService'; import { WhatsAppGroup } from '@/types/financialTypes'; import { useToast } from '@/hooks/use-toast'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; const GruposWhatsApp = () => { const { toast } = useToast(); @@ -19,6 +21,7 @@ const GruposWhatsApp = () => { const [userEmail, setUserEmail] = useState(''); const [errorMessage, setErrorMessage] = useState(null); const [debugInfo, setDebugInfo] = useState(null); + const [nomeGrupo, setNomeGrupo] = useState(''); // Buscar os grupos do usuário ao carregar a página const buscarGrupos = async () => { @@ -64,10 +67,19 @@ const GruposWhatsApp = () => { return; } + if (!nomeGrupo.trim()) { + toast({ + title: 'Atenção', + description: 'Digite um nome para o grupo', + variant: 'destructive', + }); + return; + } + setCadastrando(true); try { console.log("Iniciando processo de cadastro de grupo..."); - const grupo = await cadastrarGrupoWhatsApp(); + const grupo = await cadastrarGrupoWhatsApp(nomeGrupo.trim()); if (grupo) { let successMessage = 'Grupo registrado com sucesso'; @@ -87,6 +99,9 @@ const GruposWhatsApp = () => { variant: variant, }); + // Resetar o campo de nome + setNomeGrupo(''); + // Atualizar a lista de grupos buscarGrupos(); } else { @@ -138,22 +153,6 @@ const GruposWhatsApp = () => { {carregando ? 'Atualizando...' : 'Atualizar'} - @@ -177,25 +176,58 @@ const GruposWhatsApp = () => { - Como cadastrar um grupo + Cadastrar novo grupo - Siga os passos abaixo para vincular um grupo do WhatsApp à sua conta + Preencha as informações abaixo para cadastrar um novo grupo do WhatsApp -
    -
  1. Clique no botão "Cadastrar Grupo" acima
  2. -
  3. Adicione o número (61)99244-4275 ao grupo do WhatsApp que deseja automatizar
  4. -
  5. Envie uma mensagem neste grupo com o seguinte texto:
  6. -
- -
- {userEmail} +
+ + setNomeGrupo(e.target.value)} + /> +

+ Digite um nome descritivo para o grupo que você irá criar +

-

- Copie e cole o email exatamente como aparece acima -

+ + +
+

Após cadastrar:

+
    +
  1. Adicione o número (61)99244-4275 ao grupo do WhatsApp que deseja automatizar
  2. +
  3. Envie uma mensagem neste grupo com o seguinte texto:
  4. +
+ +
+ {userEmail} +
+ +

+ Copie e cole o email exatamente como aparece acima +

+
@@ -258,15 +290,6 @@ const GruposWhatsApp = () => {

Você ainda não cadastrou nenhum grupo

-
)} diff --git a/src/pages/Transacoes.tsx b/src/pages/Transacoes.tsx index 1ada3a8..cd965eb 100644 --- a/src/pages/Transacoes.tsx +++ b/src/pages/Transacoes.tsx @@ -4,7 +4,7 @@ import Layout from '@/components/layout/Layout'; import TransactionsTable from '@/components/dashboard/TransactionsTable'; import { TransactionForm } from '@/components/dashboard/TransactionForm'; import { Transaction } from '@/types/financialTypes'; -import { useToast } from "@/components/ui/use-toast"; +import { useToast } from "@/hooks/use-toast"; import { getTransacoes } from '@/services/transacaoService'; import { Button } from '@/components/ui/button'; import { PlusCircle } from 'lucide-react'; @@ -22,6 +22,8 @@ const TransacoesPage = () => { const [isLoading, setIsLoading] = useState(true); const [isDialogOpen, setIsDialogOpen] = useState(false); const [tipoForm, setTipoForm] = useState<'receita' | 'despesa'>('despesa'); + const [selectedTransaction, setSelectedTransaction] = useState(null); + const [isEditing, setIsEditing] = useState(false); const { toast } = useToast(); const loadTransactions = async () => { @@ -49,13 +51,30 @@ const TransacoesPage = () => { const handleTransactionSuccess = () => { setIsDialogOpen(false); + setSelectedTransaction(null); + setIsEditing(false); loadTransactions(); toast({ - title: "Transação registrada", - description: "A nova transação foi adicionada com sucesso", + title: isEditing ? "Transação atualizada" : "Transação registrada", + description: isEditing + ? "A transação foi atualizada com sucesso" + : "A nova transação foi adicionada com sucesso", }); }; + const handleEditTransaction = (transaction: Transaction) => { + setSelectedTransaction(transaction); + setTipoForm(transaction.tipo as 'receita' | 'despesa'); + setIsEditing(true); + setIsDialogOpen(true); + }; + + const handleCloseDialog = () => { + setIsDialogOpen(false); + setSelectedTransaction(null); + setIsEditing(false); + }; + // Separar transações em receitas e despesas const receitas = transactions.filter(t => t.tipo === 'receita'); const despesas = transactions.filter(t => t.tipo === 'despesa'); @@ -73,6 +92,8 @@ const TransacoesPage = () => { const handleOpenDialog = (tipo: 'receita' | 'despesa') => { setTipoForm(tipo); + setIsEditing(false); + setSelectedTransaction(null); setIsDialogOpen(true); }; @@ -135,23 +156,33 @@ const TransacoesPage = () => { transactions={transactions} isLoading={isLoading} showPagination={true} + onEdit={handleEditTransaction} + onDelete={loadTransactions} />
- + - {tipoForm === 'receita' ? 'Nova Receita' : 'Nova Despesa'} + {isEditing + ? `Editar ${tipoForm === 'receita' ? 'Receita' : 'Despesa'}` + : `Nova ${tipoForm === 'receita' ? 'Receita' : 'Despesa'}` + } - Preencha os campos para registrar uma nova {tipoForm === 'receita' ? 'receita' : 'despesa'}. + {isEditing + ? 'Edite os campos para atualizar a transação.' + : `Preencha os campos para registrar uma nova ${tipoForm === 'receita' ? 'receita' : 'despesa'}.` + } setIsDialogOpen(false)} + onCancel={handleCloseDialog} defaultTipo={tipoForm} + transaction={selectedTransaction} + isEditing={isEditing} /> diff --git a/src/services/gruposWhatsAppService.ts b/src/services/gruposWhatsAppService.ts index 2093df9..8429999 100644 --- a/src/services/gruposWhatsAppService.ts +++ b/src/services/gruposWhatsAppService.ts @@ -6,11 +6,12 @@ import { findOrCreateWhatsAppGroup, listWhatsAppGroups, updateWorkflowId } from /** * Main function to register a WhatsApp group and create its workflow * Orchestrates the process using the specialized services + * @param nomeGrupo Nome definido pelo usuário para o grupo */ -export async function cadastrarGrupoWhatsApp(): Promise { +export async function cadastrarGrupoWhatsApp(nomeGrupo?: string): Promise { try { // Find or create a WhatsApp group for the current user - const group = await findOrCreateWhatsAppGroup(); + const group = await findOrCreateWhatsAppGroup(nomeGrupo); // If no group was found or created, return null if (!group) { diff --git a/src/services/transacaoService.ts b/src/services/transacaoService.ts index c27196b..f8c73c0 100644 --- a/src/services/transacaoService.ts +++ b/src/services/transacaoService.ts @@ -1,4 +1,3 @@ - import { supabase } from "@/integrations/supabase/client"; import { Transaction, CategorySummary } from "@/types/financialTypes"; @@ -292,3 +291,92 @@ export async function getMonthlyData() { return []; } } + +/** + * Delete uma transação específica + * @param id ID da transação a ser excluída + */ +export async function deleteTransacao(id: string): Promise { + console.log(`Excluindo transação com ID: ${id}`); + + try { + const { error } = await supabase + .from('transacoes') + .delete() + .eq('id', id); + + if (error) { + console.error('Erro ao excluir transação:', error); + throw new Error('Não foi possível excluir a transação'); + } + + console.log('Transação excluída com sucesso'); + } catch (error) { + console.error('Erro ao excluir transação:', error); + throw error; + } +} + +/** + * Atualiza os dados de uma transação existente + * @param transaction A transação com os dados atualizados + * @returns A transação atualizada + */ +export async function updateTransacao(transaction: Transaction): Promise { + console.log(`Atualizando transação com ID: ${transaction.id}`, transaction); + + // 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'); + throw new Error('Usuário não autenticado'); + } + + // Normalizar o email (minúsculo e sem espaços) + const normalizedEmail = userEmail.trim().toLowerCase(); + + const transacaoData = { + login: normalizedEmail, + valor: transaction.tipo === 'receita' ? Math.abs(transaction.valor) : Math.abs(transaction.valor) * -1, + quando: transaction.quando, + detalhes: transaction.detalhes, + estabelecimento: transaction.estabelecimento, + tipo: transaction.tipo, + categoria: transaction.categoria + }; + + try { + const { data, error } = await supabase + .from('transacoes') + .update(transacaoData) + .eq('id', transaction.id) + .select('*') + .single(); + + if (error) { + console.error('Erro ao atualizar transação:', error); + throw new Error('Não foi possível atualizar a transação'); + } + + console.log('Transação atualizada com sucesso:', data); + + // Transformar para o formato esperado + return { + id: data.id.toString(), + user: data.user || '', + login: data.login || normalizedEmail, + created_at: data.created_at, + valor: data.tipo === 'receita' ? Math.abs(data.valor || 0) : -Math.abs(data.valor || 0), + quando: data.quando || new Date().toISOString(), + detalhes: data.detalhes || '', + estabelecimento: data.estabelecimento || '', + tipo: data.tipo?.toLowerCase() || 'despesa', + categoria: data.categoria || 'Outros', + grupo_id: data.grupo_id || null + }; + } catch (error) { + console.error('Erro ao atualizar transação:', error); + throw error; + } +} diff --git a/src/services/whatsAppGroupsService.ts b/src/services/whatsAppGroupsService.ts index eafcf0b..105b630 100644 --- a/src/services/whatsAppGroupsService.ts +++ b/src/services/whatsAppGroupsService.ts @@ -29,9 +29,10 @@ export async function updateWorkflowId(groupId: number, workflowId: string): Pro /** * Gets an existing or creates a new WhatsApp group for a user + * @param nomeGrupo Optional name for the group * @returns The created or existing WhatsApp group */ -export async function findOrCreateWhatsAppGroup(): Promise { +export async function findOrCreateWhatsAppGroup(nomeGrupo?: string): Promise { try { // Obter o email do usuário do localStorage const userEmail = localStorage.getItem('userEmail'); @@ -58,6 +59,21 @@ export async function findOrCreateWhatsAppGroup(): Promise console.log('Grupo pendente já existe para este usuário:', existingGroups[0]); groupToUse = existingGroups[0]; + // Atualizar nome do grupo se foi fornecido + if (nomeGrupo && nomeGrupo.trim() !== '' && nomeGrupo !== groupToUse.nome_grupo) { + console.log('Atualizando nome do grupo existente para:', nomeGrupo); + const { error } = await supabase + .from('grupos_whatsapp') + .update({ nome_grupo: nomeGrupo.trim() }) + .eq('id', groupToUse.id); + + if (error) { + console.error('Erro ao atualizar nome do grupo:', error); + } else { + groupToUse.nome_grupo = nomeGrupo.trim(); + } + } + // Se já existe um workflow_id, não precisamos criar novamente if (groupToUse.workflow_id) { console.log('Workflow já existe para este grupo:', groupToUse.workflow_id); @@ -72,7 +88,8 @@ export async function findOrCreateWhatsAppGroup(): Promise user_id: localStorage.getItem('userId') || '', remote_jid: '', login: normalizedEmail, - status: 'pendente' + status: 'pendente', + nome_grupo: nomeGrupo?.trim() || null }) .select();