Run SQL to add WhatsApp group features.

This commit is contained in:
gpt-engineer-app[bot] 2025-05-20 00:59:19 +00:00
parent 5a16246b23
commit 8435c03464
7 changed files with 314 additions and 138 deletions

View File

@ -8,6 +8,7 @@ import NotFound from '@/pages/NotFound';
import Auth from '@/pages/Auth';
import Metas from '@/pages/Metas';
import WhatsApp from '@/pages/WhatsApp';
import GruposWhatsApp from '@/pages/GruposWhatsApp';
import { Toaster } from '@/components/ui/toaster';
import './App.css';
import ProtectedRoute from '@/components/auth/ProtectedRoute';
@ -47,6 +48,11 @@ function App() {
<WhatsApp />
</ProtectedRoute>
} />
<Route path="/grupos-whatsapp" element={
<ProtectedRoute>
<GruposWhatsApp />
</ProtectedRoute>
} />
<Route path="/login" element={<Auth />} />
<Route path="/auth" element={<Auth />} />
<Route path="*" element={<NotFound />} />

View File

@ -3,9 +3,9 @@ import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import { cn } from '@/lib/utils';
import { Button } from "@/components/ui/button";
import { ChevronLeft, ChevronRight, Home, CalendarDays, DollarSign, PieChart, Flag, MessageCircle } from 'lucide-react';
import { ChevronLeft, ChevronRight, Home, CalendarDays, DollarSign, PieChart, Flag, MessageCircle, Users } from 'lucide-react';
// Updated navItems with the new WhatsApp connection page
// Atualizado para incluir a página de grupos do WhatsApp
const navItems = [
{
name: 'Dashboard',
@ -37,6 +37,11 @@ const navItems = [
path: '/whatsapp',
icon: <MessageCircle className="mr-2 h-5 w-5" />
},
{
name: 'Grupos WhatsApp',
path: '/grupos-whatsapp',
icon: <Users className="mr-2 h-5 w-5" />
},
];
interface SidebarProps {

View File

@ -5,19 +5,19 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
success:
"border-transparent bg-finance-green text-white shadow hover:bg-finance-green/80",
"border-transparent bg-green-500 text-white hover:bg-green-600",
},
},
defaultVariants: {

View File

@ -124,22 +124,28 @@ export type Database = {
Row: {
created_at: string | null
id: number
login: string | null
nome_grupo: string | null
remote_jid: string
status: string | null
user_id: string
}
Insert: {
created_at?: string | null
id?: number
login?: string | null
nome_grupo?: string | null
remote_jid: string
status?: string | null
user_id: string
}
Update: {
created_at?: string | null
id?: number
login?: string | null
nome_grupo?: string | null
remote_jid?: string
status?: string | null
user_id?: string
}
Relationships: []
@ -249,138 +255,6 @@ export type Database = {
}
Relationships: []
}
"transacoes_5f8e8181-e850-4551-88e4-bbbdc64a57ca": {
Row: {
categoria: string | null
created_at: string
detalhes: string | null
estabelecimento: string | null
id: number
quando: string | null
tipo: string | null
usuario_id: string | null
valor: number | null
}
Insert: {
categoria?: string | null
created_at?: string
detalhes?: string | null
estabelecimento?: string | null
id?: number
quando?: string | null
tipo?: string | null
usuario_id?: string | null
valor?: number | null
}
Update: {
categoria?: string | null
created_at?: string
detalhes?: string | null
estabelecimento?: string | null
id?: number
quando?: string | null
tipo?: string | null
usuario_id?: string | null
valor?: number | null
}
Relationships: [
{
foreignKeyName: "transacoes_5f8e8181-e850-4551-88e4-bbbdc64a57ca_usuario_id_fkey"
columns: ["usuario_id"]
isOneToOne: false
referencedRelation: "usuarios"
referencedColumns: ["id"]
},
]
}
"transacoes_9f267008-9128-4a2f-b730-de0a0b5602a9": {
Row: {
categoria: string | null
created_at: string
detalhes: string | null
estabelecimento: string | null
id: number
quando: string | null
tipo: string | null
usuario_id: string | null
valor: number | null
}
Insert: {
categoria?: string | null
created_at?: string
detalhes?: string | null
estabelecimento?: string | null
id?: number
quando?: string | null
tipo?: string | null
usuario_id?: string | null
valor?: number | null
}
Update: {
categoria?: string | null
created_at?: string
detalhes?: string | null
estabelecimento?: string | null
id?: number
quando?: string | null
tipo?: string | null
usuario_id?: string | null
valor?: number | null
}
Relationships: [
{
foreignKeyName: "transacoes_9f267008-9128-4a2f-b730-de0a0b5602a9_usuario_id_fkey"
columns: ["usuario_id"]
isOneToOne: false
referencedRelation: "usuarios"
referencedColumns: ["id"]
},
]
}
"transacoes_d2ca2174-fb8f-41d0-9897-a71a4af474a8": {
Row: {
categoria: string | null
created_at: string
detalhes: string | null
estabelecimento: string | null
id: number
quando: string | null
tipo: string | null
usuario_id: string | null
valor: number | null
}
Insert: {
categoria?: string | null
created_at?: string
detalhes?: string | null
estabelecimento?: string | null
id?: number
quando?: string | null
tipo?: string | null
usuario_id?: string | null
valor?: number | null
}
Update: {
categoria?: string | null
created_at?: string
detalhes?: string | null
estabelecimento?: string | null
id?: number
quando?: string | null
tipo?: string | null
usuario_id?: string | null
valor?: number | null
}
Relationships: [
{
foreignKeyName: "transacoes_d2ca2174-fb8f-41d0-9897-a71a4af474a8_usuario_id_fkey"
columns: ["usuario_id"]
isOneToOne: false
referencedRelation: "usuarios"
referencedColumns: ["id"]
},
]
}
usuarios: {
Row: {
created_at: string

View File

@ -0,0 +1,205 @@
import { useState, useEffect } from 'react';
import Layout from '@/components/layout/Layout';
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 { cadastrarGrupoWhatsApp, listarGruposWhatsApp } from '@/services/gruposWhatsAppService';
import { WhatsAppGroup } from '@/types/financialTypes';
import { useToast } from '@/hooks/use-toast';
const GruposWhatsApp = () => {
const { toast } = useToast();
const [grupos, setGrupos] = useState<WhatsAppGroup[]>([]);
const [carregando, setCarregando] = useState<boolean>(true);
const [cadastrando, setCadastrando] = useState<boolean>(false);
const [userEmail, setUserEmail] = useState<string>('');
// Buscar os grupos do usuário ao carregar a página
const buscarGrupos = async () => {
setCarregando(true);
try {
const gruposData = await listarGruposWhatsApp();
setGrupos(gruposData);
} catch (error) {
console.error('Erro ao buscar grupos:', error);
toast({
title: 'Erro',
description: 'Não foi possível carregar seus grupos do WhatsApp',
variant: 'destructive',
});
} finally {
setCarregando(false);
}
};
useEffect(() => {
// Obter o email do usuário do localStorage
const email = localStorage.getItem('userEmail');
if (email) {
setUserEmail(email.trim().toLowerCase());
}
buscarGrupos();
}, []);
// Cadastrar novo grupo
const handleCadastrarGrupo = async () => {
setCadastrando(true);
try {
const novoGrupo = await cadastrarGrupoWhatsApp();
if (novoGrupo) {
toast({
title: 'Grupo registrado com sucesso',
description: 'Siga as instruções para completar o vínculo',
variant: 'default',
});
// Atualizar a lista de grupos
buscarGrupos();
} else {
toast({
title: 'Erro',
description: 'Não foi possível registrar o grupo',
variant: 'destructive',
});
}
} catch (error) {
console.error('Erro ao cadastrar grupo:', error);
toast({
title: 'Erro',
description: 'Não foi possível registrar o grupo',
variant: 'destructive',
});
} finally {
setCadastrando(false);
}
};
return (
<Layout>
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold tracking-tight">Grupos do WhatsApp</h1>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={buscarGrupos}
disabled={carregando}
>
<RefreshCw className={`h-4 w-4 mr-2 ${carregando ? 'animate-spin' : ''}`} />
{carregando ? 'Atualizando...' : 'Atualizar'}
</Button>
<Button
onClick={handleCadastrarGrupo}
disabled={cadastrando}
>
{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>
<Card>
<CardHeader>
<CardTitle>Como cadastrar um grupo</CardTitle>
<CardDescription>
Siga os passos abaixo para vincular um grupo do WhatsApp à sua conta
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<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>Envie uma mensagem neste grupo com o seguinte texto:</li>
</ol>
<div className="bg-muted p-3 rounded-md font-mono">
{userEmail}
</div>
<p className="text-sm text-muted-foreground">
Copie e cole o email exatamente como aparece acima
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Seus grupos cadastrados</CardTitle>
<CardDescription>
Lista de grupos do WhatsApp que você cadastrou
</CardDescription>
</CardHeader>
<CardContent>
{carregando ? (
<div className="flex justify-center py-8">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
</div>
) : grupos.length > 0 ? (
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Nome do grupo</TableHead>
<TableHead>Status</TableHead>
<TableHead>Cadastro</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{grupos.map((grupo) => (
<TableRow key={grupo.id}>
<TableCell className="font-medium">{grupo.id}</TableCell>
<TableCell>
{grupo.nome_grupo || (
<span className="text-muted-foreground italic">
{grupo.remote_jid ? 'Sem nome definido' : 'Aguardando vínculo...'}
</span>
)}
</TableCell>
<TableCell>
<Badge variant={grupo.remote_jid ? "success" : "outline"}>
{grupo.remote_jid ? 'Ativo' : 'Pendente'}
</Badge>
</TableCell>
<TableCell>{new Date(grupo.created_at).toLocaleDateString()}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
) : (
<div className="text-center py-8">
<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>
<Button
variant="outline"
className="mt-4"
onClick={handleCadastrarGrupo}
disabled={cadastrando}
>
<Plus className="h-4 w-4 mr-2" />
Cadastrar seu primeiro grupo
</Button>
</div>
)}
</CardContent>
</Card>
</div>
</Layout>
);
};
export default GruposWhatsApp;

View File

@ -0,0 +1,74 @@
import { supabase } from "@/integrations/supabase/client";
import { WhatsAppGroup } from "@/types/financialTypes";
export async function cadastrarGrupoWhatsApp(): Promise<WhatsAppGroup | null> {
try {
// 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 null;
}
// Normalizar o email (minúsculo e sem espaços)
const normalizedEmail = userEmail.trim().toLowerCase();
// Criar registro na tabela grupos_whatsapp
const { data, error } = await supabase
.from('grupos_whatsapp')
.insert([
{
user_id: localStorage.getItem('userId') || '',
login: normalizedEmail,
status: 'pendente'
}
])
.select();
if (error) {
console.error('Erro ao cadastrar grupo do WhatsApp:', error);
throw new Error('Não foi possível cadastrar o grupo de WhatsApp');
}
console.log('Grupo WhatsApp cadastrado com sucesso:', data);
return data[0];
} catch (error) {
console.error('Erro ao cadastrar grupo do WhatsApp:', error);
return null;
}
}
export async function listarGruposWhatsApp(): Promise<WhatsAppGroup[]> {
try {
// 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();
// Buscar grupos associados ao email do usuário
const { data, error } = await supabase
.from('grupos_whatsapp')
.select('*')
.eq('login', normalizedEmail)
.order('created_at', { ascending: false });
if (error) {
console.error('Erro ao listar grupos do WhatsApp:', error);
throw new Error('Não foi possível listar os grupos de WhatsApp');
}
console.log('Grupos WhatsApp encontrados:', data);
return data || [];
} catch (error) {
console.error('Erro ao listar grupos do WhatsApp:', error);
return [];
}
}

View File

@ -1,4 +1,5 @@
export interface Transaction {
id: string;
user: string;
@ -56,3 +57,14 @@ export interface ResultadoMeta {
percentual_atingido: number;
meta_batida: boolean;
}
export interface WhatsAppGroup {
id: number;
user_id: string;
remote_jid: string | null;
nome_grupo: string | null;
created_at: string;
status: string;
login: string;
}