Feat: Add group name input and transaction actions
- Added an input field in the GruposWhatsApp menu for users to name their WhatsApp groups, and store the name in the database. - Added delete and edit buttons to the transactions table, with database integration.
This commit is contained in:
parent
a12e1411ef
commit
c5f3a3d161
@ -2,7 +2,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { format } from 'date-fns';
|
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 { Transaction } from '@/types/financialTypes';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@ -21,22 +21,32 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from '@/components/ui/table';
|
} from '@/components/ui/table';
|
||||||
import { cn } from '@/lib/utils';
|
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 {
|
interface TransactionsTableProps {
|
||||||
transactions: Transaction[];
|
transactions: Transaction[];
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
showPagination?: boolean;
|
showPagination?: boolean;
|
||||||
|
onEdit?: (transaction: Transaction) => void;
|
||||||
|
onDelete?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TransactionsTable = ({
|
const TransactionsTable = ({
|
||||||
transactions,
|
transactions,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
showPagination = false
|
showPagination = false,
|
||||||
|
onEdit,
|
||||||
|
onDelete
|
||||||
}: TransactionsTableProps) => {
|
}: TransactionsTableProps) => {
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [sortColumn, setSortColumn] = useState<string>('quando');
|
const [sortColumn, setSortColumn] = useState<string>('quando');
|
||||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
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 itemsPerPage = showPagination ? 10 : 5;
|
||||||
|
|
||||||
const handleSort = (column: string) => {
|
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 filteredTransactions = transactions.filter((transaction) => {
|
||||||
const query = searchQuery.toLowerCase();
|
const query = searchQuery.toLowerCase();
|
||||||
return (
|
return (
|
||||||
@ -157,13 +208,14 @@ const TransactionsTable = ({
|
|||||||
>
|
>
|
||||||
Valor {sortColumn === 'valor' && (sortDirection === 'asc' ? '↑' : '↓')}
|
Valor {sortColumn === 'valor' && (sortDirection === 'asc' ? '↑' : '↓')}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
|
<TableHead className="w-[120px] text-right">Ações</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
Array(5).fill(0).map((_, i) => (
|
Array(5).fill(0).map((_, i) => (
|
||||||
<TableRow key={`skeleton-${i}`}>
|
<TableRow key={`skeleton-${i}`}>
|
||||||
{Array(5).fill(0).map((_, j) => (
|
{Array(6).fill(0).map((_, j) => (
|
||||||
<TableCell key={`cell-${i}-${j}`} className="p-2">
|
<TableCell key={`cell-${i}-${j}`} className="p-2">
|
||||||
<div className="h-4 bg-muted rounded animate-pulse-gentle" />
|
<div className="h-4 bg-muted rounded animate-pulse-gentle" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@ -172,7 +224,7 @@ const TransactionsTable = ({
|
|||||||
))
|
))
|
||||||
) : paginatedTransactions.length === 0 ? (
|
) : paginatedTransactions.length === 0 ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={5} className="text-center py-8 text-muted-foreground">
|
<TableCell colSpan={6} className="text-center py-8 text-muted-foreground">
|
||||||
{searchQuery ? 'Nenhuma transação encontrada' : 'Não há transações disponíveis'}
|
{searchQuery ? 'Nenhuma transação encontrada' : 'Não há transações disponíveis'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@ -195,6 +247,26 @@ const TransactionsTable = ({
|
|||||||
)}>
|
)}>
|
||||||
{formatCurrency(Math.abs(transaction.valor))}
|
{formatCurrency(Math.abs(transaction.valor))}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
<div className="flex justify-end space-x-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handleEditTransaction(transaction)}
|
||||||
|
title="Editar"
|
||||||
|
>
|
||||||
|
<Edit className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => handleDeleteClick(transaction)}
|
||||||
|
title="Excluir"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
@ -247,6 +319,23 @@ const TransactionsTable = ({
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,11 +5,13 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
import { Badge } from '@/components/ui/badge';
|
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 { cadastrarGrupoWhatsApp, listarGruposWhatsApp } from '@/services/gruposWhatsAppService';
|
||||||
import { WhatsAppGroup } from '@/types/financialTypes';
|
import { WhatsAppGroup } from '@/types/financialTypes';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
|
||||||
const GruposWhatsApp = () => {
|
const GruposWhatsApp = () => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@ -19,6 +21,7 @@ const GruposWhatsApp = () => {
|
|||||||
const [userEmail, setUserEmail] = useState<string>('');
|
const [userEmail, setUserEmail] = useState<string>('');
|
||||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
const [debugInfo, setDebugInfo] = useState<string | null>(null);
|
const [debugInfo, setDebugInfo] = useState<string | null>(null);
|
||||||
|
const [nomeGrupo, setNomeGrupo] = useState<string>('');
|
||||||
|
|
||||||
// Buscar os grupos do usuário ao carregar a página
|
// Buscar os grupos do usuário ao carregar a página
|
||||||
const buscarGrupos = async () => {
|
const buscarGrupos = async () => {
|
||||||
@ -64,10 +67,19 @@ const GruposWhatsApp = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!nomeGrupo.trim()) {
|
||||||
|
toast({
|
||||||
|
title: 'Atenção',
|
||||||
|
description: 'Digite um nome para o grupo',
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setCadastrando(true);
|
setCadastrando(true);
|
||||||
try {
|
try {
|
||||||
console.log("Iniciando processo de cadastro de grupo...");
|
console.log("Iniciando processo de cadastro de grupo...");
|
||||||
const grupo = await cadastrarGrupoWhatsApp();
|
const grupo = await cadastrarGrupoWhatsApp(nomeGrupo.trim());
|
||||||
|
|
||||||
if (grupo) {
|
if (grupo) {
|
||||||
let successMessage = 'Grupo registrado com sucesso';
|
let successMessage = 'Grupo registrado com sucesso';
|
||||||
@ -87,6 +99,9 @@ const GruposWhatsApp = () => {
|
|||||||
variant: variant,
|
variant: variant,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Resetar o campo de nome
|
||||||
|
setNomeGrupo('');
|
||||||
|
|
||||||
// Atualizar a lista de grupos
|
// Atualizar a lista de grupos
|
||||||
buscarGrupos();
|
buscarGrupos();
|
||||||
} else {
|
} else {
|
||||||
@ -138,22 +153,6 @@ const GruposWhatsApp = () => {
|
|||||||
<RefreshCw className={`h-4 w-4 mr-2 ${carregando ? 'animate-spin' : ''}`} />
|
<RefreshCw className={`h-4 w-4 mr-2 ${carregando ? 'animate-spin' : ''}`} />
|
||||||
{carregando ? 'Atualizando...' : 'Atualizar'}
|
{carregando ? 'Atualizando...' : 'Atualizar'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
onClick={handleCadastrarGrupo}
|
|
||||||
disabled={cadastrando || !userEmail}
|
|
||||||
>
|
|
||||||
{cadastrando ? (
|
|
||||||
<>
|
|
||||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
||||||
Cadastrando...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
|
||||||
Cadastrar Grupo
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -177,25 +176,58 @@ const GruposWhatsApp = () => {
|
|||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Como cadastrar um grupo</CardTitle>
|
<CardTitle>Cadastrar novo grupo</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Siga os passos abaixo para vincular um grupo do WhatsApp à sua conta
|
Preencha as informações abaixo para cadastrar um novo grupo do WhatsApp
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="nomeGrupo">Nome do grupo</Label>
|
||||||
|
<Input
|
||||||
|
id="nomeGrupo"
|
||||||
|
placeholder="Ex: Controle de Gastos da Família"
|
||||||
|
value={nomeGrupo}
|
||||||
|
onChange={(e) => setNomeGrupo(e.target.value)}
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Digite um nome descritivo para o grupo que você irá criar
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleCadastrarGrupo}
|
||||||
|
disabled={cadastrando || !userEmail || !nomeGrupo.trim()}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
{cadastrando ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||||
|
Cadastrando...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
|
Cadastrar Grupo
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="mt-4 border-t pt-4">
|
||||||
|
<h3 className="font-medium mb-2">Após cadastrar:</h3>
|
||||||
<ol className="list-decimal list-inside space-y-2">
|
<ol className="list-decimal list-inside space-y-2">
|
||||||
<li>Clique no botão "Cadastrar Grupo" acima</li>
|
|
||||||
<li>Adicione o número (61)99244-4275 ao grupo do WhatsApp que deseja automatizar</li>
|
<li>Adicione o número (61)99244-4275 ao grupo do WhatsApp que deseja automatizar</li>
|
||||||
<li>Envie uma mensagem neste grupo com o seguinte texto:</li>
|
<li>Envie uma mensagem neste grupo com o seguinte texto:</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<div className="bg-muted p-3 rounded-md font-mono">
|
<div className="bg-muted p-3 rounded-md font-mono mt-2">
|
||||||
{userEmail}
|
{userEmail}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground mt-2">
|
||||||
Copie e cole o email exatamente como aparece acima
|
Copie e cole o email exatamente como aparece acima
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@ -258,15 +290,6 @@ const GruposWhatsApp = () => {
|
|||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
<MessageSquare className="h-12 w-12 text-muted-foreground opacity-20 mx-auto mb-4" />
|
<MessageSquare className="h-12 w-12 text-muted-foreground opacity-20 mx-auto mb-4" />
|
||||||
<p className="text-muted-foreground">Você ainda não cadastrou nenhum grupo</p>
|
<p className="text-muted-foreground">Você ainda não cadastrou nenhum grupo</p>
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="mt-4"
|
|
||||||
onClick={handleCadastrarGrupo}
|
|
||||||
disabled={cadastrando || !userEmail}
|
|
||||||
>
|
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
|
||||||
Cadastrar seu primeiro grupo
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import Layout from '@/components/layout/Layout';
|
|||||||
import TransactionsTable from '@/components/dashboard/TransactionsTable';
|
import TransactionsTable from '@/components/dashboard/TransactionsTable';
|
||||||
import { TransactionForm } from '@/components/dashboard/TransactionForm';
|
import { TransactionForm } from '@/components/dashboard/TransactionForm';
|
||||||
import { Transaction } from '@/types/financialTypes';
|
import { Transaction } from '@/types/financialTypes';
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { getTransacoes } from '@/services/transacaoService';
|
import { getTransacoes } from '@/services/transacaoService';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { PlusCircle } from 'lucide-react';
|
import { PlusCircle } from 'lucide-react';
|
||||||
@ -22,6 +22,8 @@ const TransacoesPage = () => {
|
|||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
const [tipoForm, setTipoForm] = useState<'receita' | 'despesa'>('despesa');
|
const [tipoForm, setTipoForm] = useState<'receita' | 'despesa'>('despesa');
|
||||||
|
const [selectedTransaction, setSelectedTransaction] = useState<Transaction | null>(null);
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const loadTransactions = async () => {
|
const loadTransactions = async () => {
|
||||||
@ -49,13 +51,30 @@ const TransacoesPage = () => {
|
|||||||
|
|
||||||
const handleTransactionSuccess = () => {
|
const handleTransactionSuccess = () => {
|
||||||
setIsDialogOpen(false);
|
setIsDialogOpen(false);
|
||||||
|
setSelectedTransaction(null);
|
||||||
|
setIsEditing(false);
|
||||||
loadTransactions();
|
loadTransactions();
|
||||||
toast({
|
toast({
|
||||||
title: "Transação registrada",
|
title: isEditing ? "Transação atualizada" : "Transação registrada",
|
||||||
description: "A nova transação foi adicionada com sucesso",
|
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
|
// Separar transações em receitas e despesas
|
||||||
const receitas = transactions.filter(t => t.tipo === 'receita');
|
const receitas = transactions.filter(t => t.tipo === 'receita');
|
||||||
const despesas = transactions.filter(t => t.tipo === 'despesa');
|
const despesas = transactions.filter(t => t.tipo === 'despesa');
|
||||||
@ -73,6 +92,8 @@ const TransacoesPage = () => {
|
|||||||
|
|
||||||
const handleOpenDialog = (tipo: 'receita' | 'despesa') => {
|
const handleOpenDialog = (tipo: 'receita' | 'despesa') => {
|
||||||
setTipoForm(tipo);
|
setTipoForm(tipo);
|
||||||
|
setIsEditing(false);
|
||||||
|
setSelectedTransaction(null);
|
||||||
setIsDialogOpen(true);
|
setIsDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -135,23 +156,33 @@ const TransacoesPage = () => {
|
|||||||
transactions={transactions}
|
transactions={transactions}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
showPagination={true}
|
showPagination={true}
|
||||||
|
onEdit={handleEditTransaction}
|
||||||
|
onDelete={loadTransactions}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
<Dialog open={isDialogOpen} onOpenChange={handleCloseDialog}>
|
||||||
<DialogContent className="sm:max-w-[500px]">
|
<DialogContent className="sm:max-w-[500px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{tipoForm === 'receita' ? 'Nova Receita' : 'Nova Despesa'}
|
{isEditing
|
||||||
|
? `Editar ${tipoForm === 'receita' ? 'Receita' : 'Despesa'}`
|
||||||
|
: `Nova ${tipoForm === 'receita' ? 'Receita' : 'Despesa'}`
|
||||||
|
}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
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'}.`
|
||||||
|
}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<TransactionForm
|
<TransactionForm
|
||||||
onSuccess={handleTransactionSuccess}
|
onSuccess={handleTransactionSuccess}
|
||||||
onCancel={() => setIsDialogOpen(false)}
|
onCancel={handleCloseDialog}
|
||||||
defaultTipo={tipoForm}
|
defaultTipo={tipoForm}
|
||||||
|
transaction={selectedTransaction}
|
||||||
|
isEditing={isEditing}
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@ -6,11 +6,12 @@ import { findOrCreateWhatsAppGroup, listWhatsAppGroups, updateWorkflowId } from
|
|||||||
/**
|
/**
|
||||||
* Main function to register a WhatsApp group and create its workflow
|
* Main function to register a WhatsApp group and create its workflow
|
||||||
* Orchestrates the process using the specialized services
|
* Orchestrates the process using the specialized services
|
||||||
|
* @param nomeGrupo Nome definido pelo usuário para o grupo
|
||||||
*/
|
*/
|
||||||
export async function cadastrarGrupoWhatsApp(): Promise<WhatsAppGroup | null> {
|
export async function cadastrarGrupoWhatsApp(nomeGrupo?: string): Promise<WhatsAppGroup | null> {
|
||||||
try {
|
try {
|
||||||
// Find or create a WhatsApp group for the current user
|
// 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 no group was found or created, return null
|
||||||
if (!group) {
|
if (!group) {
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { supabase } from "@/integrations/supabase/client";
|
import { supabase } from "@/integrations/supabase/client";
|
||||||
import { Transaction, CategorySummary } from "@/types/financialTypes";
|
import { Transaction, CategorySummary } from "@/types/financialTypes";
|
||||||
|
|
||||||
@ -292,3 +291,92 @@ export async function getMonthlyData() {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete uma transação específica
|
||||||
|
* @param id ID da transação a ser excluída
|
||||||
|
*/
|
||||||
|
export async function deleteTransacao(id: string): Promise<void> {
|
||||||
|
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<Transaction> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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
|
* 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
|
* @returns The created or existing WhatsApp group
|
||||||
*/
|
*/
|
||||||
export async function findOrCreateWhatsAppGroup(): Promise<WhatsAppGroup | null> {
|
export async function findOrCreateWhatsAppGroup(nomeGrupo?: string): Promise<WhatsAppGroup | null> {
|
||||||
try {
|
try {
|
||||||
// Obter o email do usuário do localStorage
|
// Obter o email do usuário do localStorage
|
||||||
const userEmail = localStorage.getItem('userEmail');
|
const userEmail = localStorage.getItem('userEmail');
|
||||||
@ -58,6 +59,21 @@ export async function findOrCreateWhatsAppGroup(): Promise<WhatsAppGroup | null>
|
|||||||
console.log('Grupo pendente já existe para este usuário:', existingGroups[0]);
|
console.log('Grupo pendente já existe para este usuário:', existingGroups[0]);
|
||||||
groupToUse = 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
|
// Se já existe um workflow_id, não precisamos criar novamente
|
||||||
if (groupToUse.workflow_id) {
|
if (groupToUse.workflow_id) {
|
||||||
console.log('Workflow já existe para este grupo:', groupToUse.workflow_id);
|
console.log('Workflow já existe para este grupo:', groupToUse.workflow_id);
|
||||||
@ -72,7 +88,8 @@ export async function findOrCreateWhatsAppGroup(): Promise<WhatsAppGroup | null>
|
|||||||
user_id: localStorage.getItem('userId') || '',
|
user_id: localStorage.getItem('userId') || '',
|
||||||
remote_jid: '',
|
remote_jid: '',
|
||||||
login: normalizedEmail,
|
login: normalizedEmail,
|
||||||
status: 'pendente'
|
status: 'pendente',
|
||||||
|
nome_grupo: nomeGrupo?.trim() || null
|
||||||
})
|
})
|
||||||
.select();
|
.select();
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user