diff --git a/package-lock.json b/package-lock.json index 6bdb41c..cdeff6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", - "date-fns": "^4.1.0", + "date-fns": "^3.6.0", "embla-carousel-react": "^8.3.0", "input-otp": "^1.2.4", "lucide-react": "^0.462.0", @@ -4196,9 +4196,9 @@ } }, "node_modules/date-fns": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", - "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", "license": "MIT", "funding": { "type": "github", diff --git a/package.json b/package.json index 919c784..f8810fa 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", - "date-fns": "^4.1.0", + "date-fns": "^3.6.0", "embla-carousel-react": "^8.3.0", "input-otp": "^1.2.4", "lucide-react": "^0.462.0", diff --git a/src/App.tsx b/src/App.tsx index 53de152..b910543 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3 +1,4 @@ + import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import Index from '@/pages/Index'; import Transacoes from '@/pages/Transacoes'; @@ -10,7 +11,6 @@ import WhatsApp from '@/pages/WhatsApp'; import { Toaster } from '@/components/ui/toaster'; import './App.css'; import ProtectedRoute from '@/components/auth/ProtectedRoute'; -import RodrigoAudio from './pages/RodrigoAudio'; function App() { return ( @@ -49,7 +49,6 @@ function App() { } /> } /> } /> - } /> } /> diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index 3994968..3d71cf9 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { Sidebar, defaultItems } from '@/components/layout/Sidebar'; +import Sidebar from '@/components/layout/Sidebar'; import Header from '@/components/layout/Header'; import { useToast } from "@/components/ui/use-toast"; import { Menu } from 'lucide-react'; @@ -39,11 +39,11 @@ const Layout: React.FC = ({ children }) => { - + ) : ( - + )}
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index bdde498..66f2318 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -1,74 +1,99 @@ -import React from "react"; -import { - LayoutDashboard, - Users, - Settings, - MessageSquare, - Headphones -} from "lucide-react"; +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 { MainNavItem } from "@/types"; -import { siteConfig } from "@/config/site"; +// Updated navItems with the new WhatsApp connection page +const navItems = [ + { + name: 'Dashboard', + path: '/dashboard', + icon: + }, + { + name: 'Transações', + path: '/transacoes', + icon: + }, + { + name: 'Categorias', + path: '/categorias', + icon: + }, + { + name: 'Metas', + path: '/metas', + icon: + }, + { + name: 'Calendário', + path: '/calendario', + icon: + }, + { + name: 'Conectar WhatsApp', + path: '/whatsapp', + icon: + }, +]; interface SidebarProps { - items?: MainNavItem[]; className?: string; } -export function Sidebar({ items, className = "" }: SidebarProps) { +const Sidebar = ({ className }: SidebarProps) => { + const location = useLocation(); + const [collapsed, setCollapsed] = React.useState(false); + + // Improved function to verify if the item is active based on exact path + const isItemActive = (path: string) => { + return location.pathname === path; + }; + return ( -
- - Logo - {siteConfig.name} - -
- {items?.map((item) => ( - item.href ? ( - - {item.icon} - {item.title} - - ) : null - ))} +
+
+ {!collapsed && ( +

FinDash

+ )} +
+ +
); -} +}; -export const defaultItems: MainNavItem[] = [ - { - title: "Dashboard", - href: "/", - icon: , - }, - { - title: "Usuários", - href: "/usuarios", - icon: , - }, - { - title: "WhatsApp", - href: "/whatsapp", - icon: , - }, - { - title: "Configurações", - href: "/configuracoes", - icon: , - }, - { - title: "Áudios Rodrigo", - href: "/rodrigo-audio", - icon: - }, -]; +export default Sidebar; diff --git a/src/components/usuarios/UsersDialog.tsx b/src/components/usuarios/UsersDialog.tsx deleted file mode 100644 index 99422a0..0000000 --- a/src/components/usuarios/UsersDialog.tsx +++ /dev/null @@ -1,45 +0,0 @@ - -import React from "react"; -import UsersList from "./UsersList"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { Button } from "@/components/ui/button"; -import { UserRoundSearch } from "lucide-react"; - -interface UsersDialogProps { - trigger?: React.ReactNode; -} - -export function UsersDialog({ trigger }: UsersDialogProps) { - return ( - - - {trigger || ( - - )} - - - - Gerenciamento de Usuários - - Visualize e gerencie os usuários cadastrados na plataforma - - -
- -
-
-
- ); -} - -export default UsersDialog; diff --git a/src/components/usuarios/UsersList.tsx b/src/components/usuarios/UsersList.tsx deleted file mode 100644 index a091e3c..0000000 --- a/src/components/usuarios/UsersList.tsx +++ /dev/null @@ -1,143 +0,0 @@ - -import React, { useState, useEffect } from 'react'; -import { format } from 'date-fns'; -import { ptBR } from 'date-fns/locale'; -import { UserInfo, getUsersRegisteredToday, getAllUsers } from '@/services/userService'; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { useToast } from "@/hooks/use-toast"; - -const UsersList: React.FC = () => { - const [usersToday, setUsersToday] = useState([]); - const [allUsers, setAllUsers] = useState([]); - const [loading, setLoading] = useState(false); - const [activeTab, setActiveTab] = useState("today"); - const { toast } = useToast(); - - const loadUsers = async (tab: string = activeTab) => { - setLoading(true); - try { - if (tab === "today") { - const users = await getUsersRegisteredToday(); - setUsersToday(users); - } else { - const users = await getAllUsers(); - setAllUsers(users); - } - } catch (error) { - console.error("Erro ao carregar usuários:", error); - toast({ - title: "Erro", - description: "Falha ao carregar dados de usuários.", - variant: "destructive", - }); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - loadUsers(); - }, []); - - const handleTabChange = (value: string) => { - setActiveTab(value); - loadUsers(value); - }; - - const formatDate = (dateString: string) => { - try { - return format(new Date(dateString), "dd/MM/yyyy HH:mm", { locale: ptBR }); - } catch (error) { - return "Data inválida"; - } - }; - - const displayUsers = activeTab === "today" ? usersToday : allUsers; - - return ( - - - Usuários Cadastrados - - {activeTab === "today" - ? `${usersToday.length} usuários cadastrados hoje` - : `${allUsers.length} usuários cadastrados no total`} - - - - - - Cadastrados Hoje - Todos os Usuários - - - - {loading ? ( -
Carregando...
- ) : usersToday.length > 0 ? ( -
- {usersToday.map((user) => ( -
-
{user.nome}
-
Email: {user.email}
- {user.empresa && ( -
Empresa: {user.empresa}
- )} -
- Cadastrado em: {formatDate(user.created_at)} -
-
- ))} -
- ) : ( -
Nenhum usuário cadastrado hoje.
- )} -
- - - {loading ? ( -
Carregando...
- ) : allUsers.length > 0 ? ( -
- {allUsers.map((user) => ( -
-
{user.nome}
-
Email: {user.email}
- {user.empresa && ( -
Empresa: {user.empresa}
- )} -
- Cadastrado em: {formatDate(user.created_at)} -
-
- ))} -
- ) : ( -
Nenhum usuário cadastrado.
- )} -
-
-
- - - -
- ); -}; - -export default UsersList; diff --git a/src/components/whatsapp/InstanceList.tsx b/src/components/whatsapp/InstanceList.tsx index 228a9c4..21eedf1 100644 --- a/src/components/whatsapp/InstanceList.tsx +++ b/src/components/whatsapp/InstanceList.tsx @@ -8,20 +8,20 @@ import { RefreshCw } from 'lucide-react'; interface InstanceListProps { instances: WhatsAppInstance[]; onViewQrCode: (instance: WhatsAppInstance) => void; - onDeleteInstance: (instanceId: string) => void; // Only requires instanceId - onRestartInstance: (instance: WhatsAppInstance) => Promise; - onLogoutInstance: (instance: WhatsAppInstance) => Promise; + onDelete: (instanceId: string) => void; + onRestart: (instance: WhatsAppInstance) => Promise; + onLogout: (instance: WhatsAppInstance) => Promise; onSetPresence: (instance: WhatsAppInstance, presence: 'online' | 'offline') => Promise; - onRefreshInstances?: () => Promise; - isRefreshing?: boolean; + onRefreshInstances: () => Promise; + isRefreshing: boolean; } const InstanceList = ({ instances, onViewQrCode, - onDeleteInstance, - onRestartInstance, - onLogoutInstance, + onDelete, + onRestart, + onLogout, onSetPresence, onRefreshInstances, isRefreshing @@ -43,17 +43,15 @@ const InstanceList = ({

Instâncias Criadas

- {onRefreshInstances && ( - - )} +
{instances.map((instance) => ( @@ -61,9 +59,9 @@ const InstanceList = ({ key={instance.instanceId} instance={instance} onViewQrCode={onViewQrCode} - onDelete={onDeleteInstance} - onRestart={onRestartInstance} - onLogout={onLogoutInstance} + onDelete={onDelete} + onRestart={onRestart} + onLogout={onLogout} onSetPresence={onSetPresence} /> ))} diff --git a/src/components/whatsapp/MessageWebhook.tsx b/src/components/whatsapp/MessageWebhook.tsx deleted file mode 100644 index 8d030b4..0000000 --- a/src/components/whatsapp/MessageWebhook.tsx +++ /dev/null @@ -1,103 +0,0 @@ - -import React, { useState } from 'react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; -import { useToast } from '@/hooks/use-toast'; -import { Label } from '@/components/ui/label'; -import { AudioMessage, storeAudioMessageForRodrigo } from '@/services/audioMessageService'; -import { WhatsAppInstance } from '@/types/whatsAppTypes'; - -interface MessageWebhookProps { - instance?: WhatsAppInstance; -} - -const MessageWebhook: React.FC = ({ instance }) => { - const { toast } = useToast(); - const [webhookUrl, setWebhookUrl] = useState(''); - - const handleSetWebhook = async () => { - if (!instance) { - toast({ - title: "Erro", - description: "Selecione uma instância primeiro", - variant: "destructive", - }); - return; - } - - try { - // Here you would set the webhook URL for the Evolution API - // This is a placeholder for the actual implementation - toast({ - title: "Webhook Configurado", - description: `Webhook configurado para ${instance.instanceName}`, - }); - - // Simulate receiving an audio message (for testing purposes) - const testAudioMessage: AudioMessage = { - sender_id: '120363420212322973@g.us', - sender_name: 'Teste', - instance_id: instance.instanceId, - audio_url: 'https://example.com/audio.mp3', - duration: 30, - metadata: { - test: true - } - }; - - await storeAudioMessageForRodrigo(testAudioMessage); - } catch (error) { - console.error('Error setting webhook:', error); - toast({ - title: "Erro", - description: "Erro ao configurar webhook", - variant: "destructive", - }); - } - }; - - return ( - - - Configurar Webhook para Mensagens - - Configure um webhook para receber mensagens da Evolution API. - Os áudios enviados para o grupo do Rodrigo serão armazenados automaticamente. - - - -
-
- - setWebhookUrl(e.target.value)} - /> -
- {instance ? ( -

- Configurando para: {instance.instanceName} -

- ) : ( -

- Selecione uma instância primeiro. -

- )} -
-
- - - -
- ); -}; - -export default MessageWebhook; diff --git a/src/components/whatsapp/QrCodeDialog.tsx b/src/components/whatsapp/QrCodeDialog.tsx index feae5a6..1270559 100644 --- a/src/components/whatsapp/QrCodeDialog.tsx +++ b/src/components/whatsapp/QrCodeDialog.tsx @@ -1,5 +1,5 @@ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { Dialog, DialogContent, @@ -10,22 +10,21 @@ import { import { Button } from '@/components/ui/button'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { RefreshCw } from 'lucide-react'; +import { WhatsAppInstance } from '@/types/whatsAppTypes'; import { fetchQrCode } from '@/services/whatsAppService'; import { useToast } from '@/hooks/use-toast'; interface QrCodeDialogProps { open: boolean; - setOpen: (open: boolean) => void; - instanceName: string; - phoneNumber: string; - onStatusCheck?: () => void; + onOpenChange: (open: boolean) => void; + activeInstance: WhatsAppInstance | null; + onStatusCheck: () => void; } const QrCodeDialog = ({ open, - setOpen, - instanceName, - phoneNumber, + onOpenChange, + activeInstance, onStatusCheck }: QrCodeDialogProps) => { const { toast } = useToast(); @@ -33,30 +32,23 @@ const QrCodeDialog = ({ const [qrCodeData, setQrCodeData] = useState(null); const [qrError, setQrError] = useState(null); - // Load QR code when dialog opens - useEffect(() => { - if (open && instanceName) { - handleRefreshQrCode(); - } - }, [open, instanceName]); - const handleOpenChange = (newOpen: boolean) => { - setOpen(newOpen); + onOpenChange(newOpen); // After dialog closes, trigger status check to update connection state - if (!newOpen && onStatusCheck) { + if (!newOpen) { onStatusCheck(); } }; const handleRefreshQrCode = async () => { - if (!instanceName) return; + if (!activeInstance) return; setLoadingQR(true); setQrError(null); try { - const data = await fetchQrCode(instanceName); + const data = await fetchQrCode(activeInstance.instanceName); console.log('QR Code API response:', data); // Using the "base64" field from the response as the QR code data @@ -83,7 +75,7 @@ const QrCodeDialog = ({ - Conectar WhatsApp - {instanceName} + Conectar WhatsApp - {activeInstance?.instanceName} Escaneie o QR Code com seu WhatsApp para finalizar a conexão @@ -117,7 +109,7 @@ const QrCodeDialog = ({ )}
- {instanceName && ( + {activeInstance && (
diff --git a/src/components/whatsapp/index.ts b/src/components/whatsapp/index.ts deleted file mode 100644 index aa33559..0000000 --- a/src/components/whatsapp/index.ts +++ /dev/null @@ -1,8 +0,0 @@ - -// Export all WhatsApp components for easier imports -export { default as CreateInstanceForm } from './CreateInstanceForm'; -export { default as InstanceList } from './InstanceList'; -export { default as InstanceStats } from './InstanceStats'; -export { default as QrCodeDialog } from './QrCodeDialog'; -export { default as InstanceCard } from './InstanceCard'; -export { default as MessageWebhook } from './MessageWebhook'; diff --git a/src/config/site.ts b/src/config/site.ts deleted file mode 100644 index 4e2a4e8..0000000 --- a/src/config/site.ts +++ /dev/null @@ -1,9 +0,0 @@ - -export const siteConfig = { - name: "WhatsApp Dashboard", - description: "Gerenciamento de instâncias de WhatsApp", - mainNav: [], - links: { - github: "https://github.com/", - }, -}; diff --git a/src/hooks/useWhatsAppActions.ts b/src/hooks/useWhatsAppActions.ts index 6f7ab12..f1126ae 100644 --- a/src/hooks/useWhatsAppActions.ts +++ b/src/hooks/useWhatsAppActions.ts @@ -1,3 +1,186 @@ -// Re-export from the refactored location -export { useWhatsAppActions } from './whatsapp/useWhatsAppActions'; +import { useState } from 'react'; +import { useToast } from '@/hooks/use-toast'; +import { WhatsAppInstance } from '@/types/whatsAppTypes'; +import { + restartInstance, + logoutInstance, + deleteInstance, + setInstancePresence, + fetchQrCode +} from '@/services/whatsAppService'; + +export const useWhatsAppActions = ( + updateInstance: (instance: WhatsAppInstance) => void, + removeInstance: (instanceId: string) => void, + checkAllInstancesStatus: () => Promise +) => { + const { toast } = useToast(); + const [activeInstance, setActiveInstance] = useState(null); + const [qrDialogOpen, setQrDialogOpen] = useState(false); + + // Handler for quando uma instância é reiniciada + const handleRestartInstance = async (instance: WhatsAppInstance) => { + try { + console.log(`Attempting to restart instance ${instance.instanceName}`); + await restartInstance(instance.instanceName); + + // Atualiza o estado da instância para "connecting" + const updatedInstance = { + ...instance, + connectionState: 'connecting' as const + }; + console.log(`Instance ${instance.instanceName} restart initiated, setting state to connecting`, updatedInstance); + updateInstance(updatedInstance); + + toast({ + title: "Sucesso", + description: `Instância ${instance.instanceName} reiniciada com sucesso` + }); + + // Verifica o status após um breve delay para dar tempo de atualizar + setTimeout(() => checkAllInstancesStatus(), 3000); + + } catch (error) { + console.error(`Error restarting instance ${instance.instanceName}:`, error); + toast({ + title: "Erro", + description: `Falha ao reiniciar a instância ${instance.instanceName}`, + variant: "destructive", + }); + } + }; + + // Handler for quando uma instância é desconectada + const handleLogoutInstance = async (instance: WhatsAppInstance) => { + try { + console.log(`Attempting to logout instance ${instance.instanceName}`); + await logoutInstance(instance.instanceName); + + // Atualiza o estado da instância para "closed" + const updatedInstance = { + ...instance, + connectionState: 'closed' as const, + status: 'disconnected' + }; + console.log(`Instance ${instance.instanceName} logout successful, updating instance state`, updatedInstance); + updateInstance(updatedInstance); + + toast({ + title: "Sucesso", + description: `Instância ${instance.instanceName} desconectada com sucesso` + }); + + } catch (error) { + console.error(`Error logging out instance ${instance.instanceName}:`, error); + toast({ + title: "Erro", + description: `Falha ao desconectar a instância ${instance.instanceName}`, + variant: "destructive", + }); + } + }; + + // Handler for quando a presença é alterada + const handleSetPresence = async (instance: WhatsAppInstance, presence: 'online' | 'offline') => { + try { + console.log(`Setting presence to ${presence} for instance ${instance.instanceName}`); + await setInstancePresence(instance.instanceName, presence); + + const updatedInstance = { + ...instance, + presence: presence + }; + console.log(`Updated instance with new presence status`, updatedInstance); + updateInstance(updatedInstance); + + toast({ + title: "Sucesso", + description: `Instância ${instance.instanceName} agora está ${presence === 'online' ? 'Online' : 'Offline'}` + }); + + } catch (error) { + console.error(`Error setting presence to ${presence} for instance ${instance.instanceName}:`, error); + toast({ + title: "Erro", + description: `Falha ao definir presença ${presence} para ${instance.instanceName}`, + variant: "destructive", + }); + } + }; + + // Handler for when an instance is deleted + const handleDeleteInstance = async (instanceId: string, instanceName: string) => { + console.log(`Deleting instance with ID: ${instanceId}, name: ${instanceName}`); + + try { + // Call API to delete instance + const response = await deleteInstance(instanceName); + console.log(`Deletion API response for instance ${instanceName}:`, response); + + // Remove from local state + removeInstance(instanceId); + + // If we're viewing QR code for this instance, close the dialog + if (activeInstance?.instanceId === instanceId) { + setQrDialogOpen(false); + setActiveInstance(null); + } + + toast({ + title: "Instância removida", + description: "A instância do WhatsApp foi removida com sucesso.", + }); + } catch (error) { + console.error(`Error deleting instance with ID ${instanceId}:`, error); + + // Even if the API call fails, we still want to remove the instance from local storage + // This handles the case when instances are in inconsistent state with the server + removeInstance(instanceId); + + if (activeInstance?.instanceId === instanceId) { + setQrDialogOpen(false); + setActiveInstance(null); + } + + toast({ + title: "Aviso", + description: "Instância removida localmente, mas pode haver falha na comunicação com o servidor.", + variant: "destructive", + }); + } + }; + + // Handler for when QR code dialog is requested + const handleViewQrCode = async (instance: WhatsAppInstance) => { + console.log(`Opening QR code dialog for instance: ${instance.instanceName}`); + setActiveInstance(instance); + setQrDialogOpen(true); + + try { + const data = await fetchQrCode(instance.instanceName); + console.log('QR Code API response:', data); + + // If the fetchQrCode call was successful, QrCodeDialog will handle showing the QR code + } catch (error) { + console.error("Error initiating QR code fetch:", error); + toast({ + title: "Erro ao obter QR Code", + description: "Falha ao iniciar obtenção de QR Code. Tente novamente.", + variant: "destructive", + }); + setQrDialogOpen(false); + } + }; + + return { + activeInstance, + qrDialogOpen, + setQrDialogOpen, + handleRestartInstance, + handleLogoutInstance, + handleSetPresence, + handleDeleteInstance, + handleViewQrCode + }; +}; diff --git a/src/hooks/useWhatsAppInstances.ts b/src/hooks/useWhatsAppInstances.ts index 40ef46f..4fc64b4 100644 --- a/src/hooks/useWhatsAppInstances.ts +++ b/src/hooks/useWhatsAppInstances.ts @@ -1,3 +1,249 @@ -// Re-export from the refactored location -export { useWhatsAppInstances } from './whatsapp/useWhatsAppInstances'; +import { useState, useEffect, useCallback } from 'react'; +import { useToast } from '@/hooks/use-toast'; +import { WhatsAppInstance } from '@/types/whatsAppTypes'; +import { + saveInstancesToLocalStorage, + loadInstancesFromLocalStorage, + fetchAllInstances, + fetchConnectionState +} from '@/services/whatsAppService'; + +export const useWhatsAppInstances = () => { + const { toast } = useToast(); + const [instances, setInstances] = useState([]); + const [currentUserId, setCurrentUserId] = useState(''); + const [isRefreshing, setIsRefreshing] = useState(false); + + // Get current user ID and load instances on component mount + useEffect(() => { + const userId = localStorage.getItem('userId') || ''; + console.log("Current userId from localStorage:", userId); + setCurrentUserId(userId); + + if (userId) { + const userInstances = loadInstancesFromLocalStorage(userId); + console.log('Loaded user instances:', userInstances); + setInstances(userInstances); + } + }, []); // Only run once on mount + + // Save instances to localStorage whenever they change + useEffect(() => { + if (currentUserId && instances.length > 0) { + console.log('Saving instances to localStorage:', instances); + saveInstancesToLocalStorage(instances, currentUserId); + } + }, [instances, currentUserId]); + + // Function to check connection status for all instances + const checkAllInstancesStatus = useCallback(async () => { + if (instances.length === 0) return; + + console.log(`Checking connection status for ${instances.length} instances`); + + try { + const updatedInstances = await Promise.all( + instances.map(async (instance) => { + try { + console.log(`Checking status for ${instance.instanceName}`); + const state = await fetchConnectionState(instance.instanceName); + console.log(`Status for ${instance.instanceName}: ${state}`); + + // Only update connectionState if it's different + if (instance.connectionState !== state) { + // Ensure the state is one of the valid values or treat as 'closed' + let validState: 'open' | 'closed' | 'connecting'; + if (state === 'open' || state === 'connecting') { + validState = state; + } else { + validState = 'closed'; + } + + return { + ...instance, + connectionState: validState, + status: state === 'open' ? 'connected' : + state === 'connecting' ? 'connecting' : 'disconnected' + }; + } + return instance; + } catch (error) { + console.error(`Error checking status for ${instance.instanceName}:`, error); + + // If the instance doesn't exist on the server, mark it as closed + if (error instanceof Error && error.message.includes("does not exist")) { + return { + ...instance, + connectionState: 'closed' as const, + status: 'disconnected' + }; + } + + // Keep the current state in case of other errors + return instance; + } + }) + ); + + // Only update state if there are actual changes + const hasChanges = JSON.stringify(updatedInstances) !== JSON.stringify(instances); + if (hasChanges) { + console.log('Updated instances after status check:', updatedInstances); + setInstances(updatedInstances); + } else { + console.log('No changes in instance status'); + } + } catch (error) { + console.error("Error checking instances status:", error); + } + }, [instances]); + + // Handler para atualizar a lista de instâncias do servidor + const refreshInstances = async () => { + if (!currentUserId) { + toast({ + title: "Erro de autenticação", + description: "Você precisa estar logado para atualizar as instâncias", + variant: "destructive", + }); + return; + } + + setIsRefreshing(true); + try { + console.log("Fetching instances from server..."); + const response = await fetchAllInstances(); + console.log("Fetched instances from server:", response); + + if (response.instances && Array.isArray(response.instances)) { + // Mapeia as instâncias do servidor para o formato correto com userId + const serverInstances: WhatsAppInstance[] = response.instances + .map(serverInstance => { + // Ensure we convert server state to valid connectionState + let connectionState: 'open' | 'closed' | 'connecting'; + const state = serverInstance.state || 'closed'; + + if (state === 'open' || state === 'connecting') { + connectionState = state; + } else { + connectionState = 'closed'; + } + + return { + instanceName: serverInstance.instanceName, + instanceId: serverInstance.instanceName, // Using instanceName as the ID for consistency + phoneNumber: serverInstance.number || 'Desconhecido', + userId: currentUserId, + connectionState: connectionState, + status: connectionState === 'open' ? 'connected' : + connectionState === 'connecting' ? 'connecting' : 'disconnected' + }; + }); + + console.log("Mapped server instances:", serverInstances); + + // Identifica instâncias novas que não existem localmente + const localInstanceIds = new Set(instances.map(i => i.instanceId)); + const newServerInstances = serverInstances.filter(i => !localInstanceIds.has(i.instanceId)); + + // Atualiza instâncias existentes com dados do servidor + const updatedExistingInstances = instances.map(localInstance => { + const serverMatch = serverInstances.find(si => si.instanceId === localInstance.instanceId); + if (serverMatch) { + return { + ...localInstance, + connectionState: serverMatch.connectionState, + status: serverMatch.status + }; + } + return localInstance; + }); + + // Combina tudo + const allInstances = [...updatedExistingInstances, ...newServerInstances]; + + console.log("Combined instances after refresh:", allInstances); + setInstances(allInstances); + + toast({ + title: "Sucesso", + description: `${serverInstances.length} instâncias encontradas no servidor`, + }); + } else { + toast({ + title: "Aviso", + description: "Nenhuma instância encontrada no servidor", + }); + } + } catch (error) { + console.error("Erro ao buscar instâncias:", error); + toast({ + title: "Erro", + description: "Falha ao buscar instâncias do servidor", + variant: "destructive", + }); + } finally { + setIsRefreshing(false); + // Verificar status após atualizar a lista + await checkAllInstancesStatus(); + } + }; + + // Add a new instance + const addInstance = (newInstance: WhatsAppInstance) => { + console.log("New instance created, adding to instances list:", newInstance); + + // Verifica se já existe uma instância com o mesmo ID + const existingIndex = instances.findIndex(inst => inst.instanceId === newInstance.instanceId); + + if (existingIndex >= 0) { + // Atualiza a instância existente + console.log(`Instance with ID ${newInstance.instanceId} already exists, updating it`); + setInstances(prevInstances => + prevInstances.map((instance, index) => + index === existingIndex ? newInstance : instance + ) + ); + } else { + // Adiciona nova instância + setInstances(prevInstances => [...prevInstances, newInstance]); + } + }; + + // Remove an instance + const removeInstance = (instanceId: string) => { + console.log(`Removing instance with ID: ${instanceId}`); + setInstances(prevInstances => { + const filtered = prevInstances.filter(instance => instance.instanceId !== instanceId); + console.log("Updated instances after deletion:", filtered); + return filtered; + }); + }; + + // Update an instance + const updateInstance = (updatedInstance: WhatsAppInstance) => { + console.log(`Updating instance: ${updatedInstance.instanceName}`, updatedInstance); + setInstances(prevInstances => { + const newInstances = prevInstances.map(instance => + instance.instanceId === updatedInstance.instanceId + ? updatedInstance + : instance + ); + + console.log("Updated instances:", newInstances); + return newInstances; + }); + }; + + return { + instances, + isRefreshing, + currentUserId, + addInstance, + removeInstance, + updateInstance, + refreshInstances, + checkAllInstancesStatus + }; +}; diff --git a/src/hooks/whatsapp/instanceStatusUtils.ts b/src/hooks/whatsapp/instanceStatusUtils.ts deleted file mode 100644 index 8f6c231..0000000 --- a/src/hooks/whatsapp/instanceStatusUtils.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { WhatsAppInstance } from '@/types/whatsAppTypes'; -import { fetchConnectionState } from '@/services/whatsAppService'; - -/** - * Checks the connection status for a single WhatsApp instance - */ -export const checkInstanceStatus = async (instance: WhatsAppInstance): Promise => { - try { - console.log(`Checking status for ${instance.instanceName}`); - const state = await fetchConnectionState(instance.instanceName); - console.log(`Status for ${instance.instanceName}: ${state}`); - - // Only update connectionState if it's different - if (instance.connectionState !== state) { - // Ensure the state is one of the valid values or treat as 'closed' - let validState: 'open' | 'closed' | 'connecting'; - if (state === 'open' || state === 'connecting') { - validState = state; - } else { - validState = 'closed'; - } - - return { - ...instance, - connectionState: validState, - status: state === 'open' ? 'connected' : - state === 'connecting' ? 'connecting' : 'disconnected' - }; - } - return instance; - } catch (error) { - console.error(`Error checking status for ${instance.instanceName}:`, error); - - // If the instance doesn't exist on the server, mark it as closed - if (error instanceof Error && error.message.includes("does not exist")) { - return { - ...instance, - connectionState: 'closed' as const, - status: 'disconnected' - }; - } - - // Keep the current state in case of other errors - return instance; - } -}; - -/** - * Maps server instances to the local WhatsAppInstance format - */ -export const mapServerInstancesToLocal = ( - serverInstances: any[], - currentUserId: string -): WhatsAppInstance[] => { - return serverInstances.map(serverInstance => { - // Ensure we convert server state to valid connectionState - let connectionState: 'open' | 'closed' | 'connecting'; - const state = serverInstance.state || 'closed'; - - if (state === 'open' || state === 'connecting') { - connectionState = state; - } else { - connectionState = 'closed'; - } - - return { - instanceName: serverInstance.instanceName, - instanceId: serverInstance.instanceName, // Using instanceName as the ID for consistency - phoneNumber: serverInstance.number || 'Desconhecido', - userId: currentUserId, - connectionState: connectionState, - status: connectionState === 'open' ? 'connected' : - connectionState === 'connecting' ? 'connecting' : 'disconnected' - }; - }); -}; diff --git a/src/hooks/whatsapp/instanceStorageUtils.ts b/src/hooks/whatsapp/instanceStorageUtils.ts deleted file mode 100644 index 496c5e9..0000000 --- a/src/hooks/whatsapp/instanceStorageUtils.ts +++ /dev/null @@ -1,31 +0,0 @@ - -import { WhatsAppInstance } from '@/types/whatsAppTypes'; - -/** - * Load WhatsApp instances from localStorage for a specific user - */ -export const loadInstancesFromLocalStorage = (userId: string): WhatsAppInstance[] => { - try { - const storedData = localStorage.getItem(`whatsAppInstances_${userId}`); - if (storedData) { - const parsedData = JSON.parse(storedData); - console.log(`Loaded ${parsedData.length} instances from localStorage for user ${userId}`); - return parsedData; - } - } catch (error) { - console.error('Error loading WhatsApp instances from localStorage:', error); - } - return []; -}; - -/** - * Save WhatsApp instances to localStorage for a specific user - */ -export const saveInstancesToLocalStorage = (instances: WhatsAppInstance[], userId: string): void => { - try { - console.log(`Saving ${instances.length} instances to localStorage for user ${userId}`); - localStorage.setItem(`whatsAppInstances_${userId}`, JSON.stringify(instances)); - } catch (error) { - console.error('Error saving WhatsApp instances to localStorage:', error); - } -}; diff --git a/src/hooks/whatsapp/types.ts b/src/hooks/whatsapp/types.ts deleted file mode 100644 index ef3654d..0000000 --- a/src/hooks/whatsapp/types.ts +++ /dev/null @@ -1,18 +0,0 @@ - -import { WhatsAppInstance } from '@/types/whatsAppTypes'; - -export interface WhatsAppInstancesState { - instances: WhatsAppInstance[]; - currentUserId: string; - isRefreshing: boolean; -} - -export interface WhatsAppInstancesActions { - addInstance: (newInstance: WhatsAppInstance) => void; - removeInstance: (instanceId: string) => void; - updateInstance: (updatedInstance: WhatsAppInstance) => void; - refreshInstances: () => Promise; - checkAllInstancesStatus: () => Promise; -} - -export type WhatsAppInstancesHookReturn = WhatsAppInstancesState & WhatsAppInstancesActions; diff --git a/src/hooks/whatsapp/useInstanceConnection.ts b/src/hooks/whatsapp/useInstanceConnection.ts deleted file mode 100644 index 6588bca..0000000 --- a/src/hooks/whatsapp/useInstanceConnection.ts +++ /dev/null @@ -1,82 +0,0 @@ - -import { useState } from 'react'; -import { useToast } from '@/hooks/use-toast'; -import { WhatsAppInstance } from '@/types/whatsAppTypes'; -import { restartInstance, logoutInstance } from '@/services/whatsAppService'; - -/** - * Hook for handling WhatsApp instance connection actions like restart and logout - */ -export const useInstanceConnection = ( - updateInstance: (instance: WhatsAppInstance) => void, - checkAllInstancesStatus: () => Promise -) => { - const { toast } = useToast(); - - // Handler for when an instance is restarted - const handleRestartInstance = async (instance: WhatsAppInstance) => { - try { - console.log(`Attempting to restart instance ${instance.instanceName}`); - await restartInstance(instance.instanceName); - - // Update the instance state to "connecting" - const updatedInstance = { - ...instance, - connectionState: 'connecting' as const - }; - console.log(`Instance ${instance.instanceName} restart initiated, setting state to connecting`, updatedInstance); - updateInstance(updatedInstance); - - toast({ - title: "Sucesso", - description: `Instância ${instance.instanceName} reiniciada com sucesso` - }); - - // Check status after a brief delay to give time to update - setTimeout(() => checkAllInstancesStatus(), 3000); - - } catch (error) { - console.error(`Error restarting instance ${instance.instanceName}:`, error); - toast({ - title: "Erro", - description: `Falha ao reiniciar a instância ${instance.instanceName}`, - variant: "destructive", - }); - } - }; - - // Handler for when an instance is disconnected - const handleLogoutInstance = async (instance: WhatsAppInstance) => { - try { - console.log(`Attempting to logout instance ${instance.instanceName}`); - await logoutInstance(instance.instanceName); - - // Update the instance state to "closed" - const updatedInstance = { - ...instance, - connectionState: 'closed' as const, - status: 'disconnected' - }; - console.log(`Instance ${instance.instanceName} logout successful, updating instance state`, updatedInstance); - updateInstance(updatedInstance); - - toast({ - title: "Sucesso", - description: `Instância ${instance.instanceName} desconectada com sucesso` - }); - - } catch (error) { - console.error(`Error logging out instance ${instance.instanceName}:`, error); - toast({ - title: "Erro", - description: `Falha ao desconectar a instância ${instance.instanceName}`, - variant: "destructive", - }); - } - }; - - return { - handleRestartInstance, - handleLogoutInstance - }; -}; diff --git a/src/hooks/whatsapp/useInstanceDeletion.ts b/src/hooks/whatsapp/useInstanceDeletion.ts deleted file mode 100644 index 4c0b260..0000000 --- a/src/hooks/whatsapp/useInstanceDeletion.ts +++ /dev/null @@ -1,47 +0,0 @@ - -import { useToast } from '@/hooks/use-toast'; -import { deleteInstance } from '@/services/whatsAppService'; - -/** - * Hook for handling WhatsApp instance deletion - */ -export const useInstanceDeletion = ( - removeInstance: (instanceId: string) => void -) => { - const { toast } = useToast(); - - // Handler for when an instance is deleted - const handleDeleteInstance = async (instanceId: string, instanceName: string) => { - console.log(`Deleting instance with ID: ${instanceId}, name: ${instanceName}`); - - try { - // Call API to delete instance - const response = await deleteInstance(instanceName); - console.log(`Deletion API response for instance ${instanceName}:`, response); - - // Remove from local state - removeInstance(instanceId); - - toast({ - title: "Instância removida", - description: "A instância do WhatsApp foi removida com sucesso.", - }); - } catch (error) { - console.error(`Error deleting instance with ID ${instanceId}:`, error); - - // Even if the API call fails, we still want to remove the instance from local storage - // This handles the case when instances are in inconsistent state with the server - removeInstance(instanceId); - - toast({ - title: "Aviso", - description: "Instância removida localmente, mas pode haver falha na comunicação com o servidor.", - variant: "destructive", - }); - } - }; - - return { - handleDeleteInstance - }; -}; diff --git a/src/hooks/whatsapp/useInstancePresence.ts b/src/hooks/whatsapp/useInstancePresence.ts deleted file mode 100644 index e906bfa..0000000 --- a/src/hooks/whatsapp/useInstancePresence.ts +++ /dev/null @@ -1,45 +0,0 @@ - -import { useToast } from '@/hooks/use-toast'; -import { WhatsAppInstance } from '@/types/whatsAppTypes'; -import { setInstancePresence } from '@/services/whatsAppService'; - -/** - * Hook for handling WhatsApp instance presence (online/offline) - */ -export const useInstancePresence = ( - updateInstance: (instance: WhatsAppInstance) => void -) => { - const { toast } = useToast(); - - // Handler for when presence is changed - const handleSetPresence = async (instance: WhatsAppInstance, presence: 'online' | 'offline') => { - try { - console.log(`Setting presence to ${presence} for instance ${instance.instanceName}`); - await setInstancePresence(instance.instanceName, presence); - - const updatedInstance = { - ...instance, - presence: presence - }; - console.log(`Updated instance with new presence status`, updatedInstance); - updateInstance(updatedInstance); - - toast({ - title: "Sucesso", - description: `Instância ${instance.instanceName} agora está ${presence === 'online' ? 'Online' : 'Offline'}` - }); - - } catch (error) { - console.error(`Error setting presence to ${presence} for instance ${instance.instanceName}:`, error); - toast({ - title: "Erro", - description: `Falha ao definir presença ${presence} para ${instance.instanceName}`, - variant: "destructive", - }); - } - }; - - return { - handleSetPresence - }; -}; diff --git a/src/hooks/whatsapp/useQrCodeDialog.ts b/src/hooks/whatsapp/useQrCodeDialog.ts deleted file mode 100644 index a6a6bbf..0000000 --- a/src/hooks/whatsapp/useQrCodeDialog.ts +++ /dev/null @@ -1,43 +0,0 @@ - -import { useState } from 'react'; -import { useToast } from '@/hooks/use-toast'; -import { WhatsAppInstance } from '@/types/whatsAppTypes'; -import { fetchQrCode } from '@/services/whatsAppService'; - -/** - * Hook for handling WhatsApp QR code dialog functionality - */ -export const useQrCodeDialog = () => { - const { toast } = useToast(); - const [activeInstance, setActiveInstance] = useState(null); - const [qrDialogOpen, setQrDialogOpen] = useState(false); - - // Handler for when QR code dialog is requested - const handleViewQrCode = async (instance: WhatsAppInstance) => { - console.log(`Opening QR code dialog for instance: ${instance.instanceName}`); - setActiveInstance(instance); - setQrDialogOpen(true); - - try { - const data = await fetchQrCode(instance.instanceName); - console.log('QR Code API response:', data); - - // If the fetchQrCode call was successful, QrCodeDialog will handle showing the QR code - } catch (error) { - console.error("Error initiating QR code fetch:", error); - toast({ - title: "Erro ao obter QR Code", - description: "Falha ao iniciar obtenção de QR Code. Tente novamente.", - variant: "destructive", - }); - setQrDialogOpen(false); - } - }; - - return { - activeInstance, - qrDialogOpen, - setQrDialogOpen, - handleViewQrCode - }; -}; diff --git a/src/hooks/whatsapp/useWhatsAppActions.ts b/src/hooks/whatsapp/useWhatsAppActions.ts deleted file mode 100644 index 92b4da1..0000000 --- a/src/hooks/whatsapp/useWhatsAppActions.ts +++ /dev/null @@ -1,40 +0,0 @@ - -import { WhatsAppInstance } from '@/types/whatsAppTypes'; -import { useInstanceConnection } from './useInstanceConnection'; -import { useInstancePresence } from './useInstancePresence'; -import { useInstanceDeletion } from './useInstanceDeletion'; -import { useQrCodeDialog } from './useQrCodeDialog'; - -/** - * Main hook that combines all WhatsApp instance actions - */ -export const useWhatsAppActions = ( - updateInstance: (instance: WhatsAppInstance) => void, - removeInstance: (instanceId: string) => void, - checkAllInstancesStatus: () => Promise -) => { - // Combine all the action hooks - const { handleRestartInstance, handleLogoutInstance } = - useInstanceConnection(updateInstance, checkAllInstancesStatus); - - const { handleSetPresence } = - useInstancePresence(updateInstance); - - const { handleDeleteInstance } = - useInstanceDeletion(removeInstance); - - const { activeInstance, qrDialogOpen, setQrDialogOpen, handleViewQrCode } = - useQrCodeDialog(); - - return { - // Re-export all actions and state from the specialized hooks - activeInstance, - qrDialogOpen, - setQrDialogOpen, - handleRestartInstance, - handleLogoutInstance, - handleSetPresence, - handleDeleteInstance, - handleViewQrCode - }; -}; diff --git a/src/hooks/whatsapp/useWhatsAppInstances.ts b/src/hooks/whatsapp/useWhatsAppInstances.ts deleted file mode 100644 index 650fdca..0000000 --- a/src/hooks/whatsapp/useWhatsAppInstances.ts +++ /dev/null @@ -1,196 +0,0 @@ - -import { useState, useEffect, useCallback } from 'react'; -import { useToast } from '@/hooks/use-toast'; -import { WhatsAppInstance } from '@/types/whatsAppTypes'; -import { fetchAllInstances } from '@/services/whatsAppService'; -import { - loadInstancesFromLocalStorage, - saveInstancesToLocalStorage -} from './instanceStorageUtils'; -import { - checkInstanceStatus, - mapServerInstancesToLocal -} from './instanceStatusUtils'; -import { WhatsAppInstancesHookReturn } from './types'; - -/** - * Hook for managing WhatsApp instances - */ -export const useWhatsAppInstances = (): WhatsAppInstancesHookReturn => { - const { toast } = useToast(); - const [instances, setInstances] = useState([]); - const [currentUserId, setCurrentUserId] = useState(''); - const [isRefreshing, setIsRefreshing] = useState(false); - - // Get current user ID and load instances on component mount - useEffect(() => { - const userId = localStorage.getItem('userId') || ''; - console.log("Current userId from localStorage:", userId); - setCurrentUserId(userId); - - if (userId) { - const userInstances = loadInstancesFromLocalStorage(userId); - console.log('Loaded user instances:', userInstances); - setInstances(userInstances); - } - }, []); - - // Save instances to localStorage whenever they change - useEffect(() => { - if (currentUserId && instances.length > 0) { - console.log('Saving instances to localStorage:', instances); - saveInstancesToLocalStorage(instances, currentUserId); - } - }, [instances, currentUserId]); - - // Check connection status for all instances - const checkAllInstancesStatus = useCallback(async () => { - if (instances.length === 0) return; - - console.log(`Checking connection status for ${instances.length} instances`); - - try { - const updatedInstances = await Promise.all( - instances.map(async (instance) => checkInstanceStatus(instance)) - ); - - // Only update state if there are actual changes - const hasChanges = JSON.stringify(updatedInstances) !== JSON.stringify(instances); - if (hasChanges) { - console.log('Updated instances after status check:', updatedInstances); - setInstances(updatedInstances); - } else { - console.log('No changes in instance status'); - } - } catch (error) { - console.error("Error checking instances status:", error); - } - }, [instances]); - - // Refresh instances from the server - const refreshInstances = async () => { - if (!currentUserId) { - toast({ - title: "Erro de autenticação", - description: "Você precisa estar logado para atualizar as instâncias", - variant: "destructive", - }); - return; - } - - setIsRefreshing(true); - try { - console.log("Fetching instances from server..."); - const response = await fetchAllInstances(); - console.log("Fetched instances from server:", response); - - if (response.instances && Array.isArray(response.instances)) { - // Map server instances to local format - const serverInstances = mapServerInstancesToLocal(response.instances, currentUserId); - - console.log("Mapped server instances:", serverInstances); - - // Find new instances that don't exist locally - const localInstanceIds = new Set(instances.map(i => i.instanceId)); - const newServerInstances = serverInstances.filter(i => !localInstanceIds.has(i.instanceId)); - - // Update existing instances with server data - const updatedExistingInstances = instances.map(localInstance => { - const serverMatch = serverInstances.find(si => si.instanceId === localInstance.instanceId); - if (serverMatch) { - return { - ...localInstance, - connectionState: serverMatch.connectionState, - status: serverMatch.status - }; - } - return localInstance; - }); - - // Combine everything - const allInstances = [...updatedExistingInstances, ...newServerInstances]; - - console.log("Combined instances after refresh:", allInstances); - setInstances(allInstances); - - toast({ - title: "Sucesso", - description: `${serverInstances.length} instâncias encontradas no servidor`, - }); - } else { - toast({ - title: "Aviso", - description: "Nenhuma instância encontrada no servidor", - }); - } - } catch (error) { - console.error("Erro ao buscar instâncias:", error); - toast({ - title: "Erro", - description: "Falha ao buscar instâncias do servidor", - variant: "destructive", - }); - } finally { - setIsRefreshing(false); - // Check status after updating the list - await checkAllInstancesStatus(); - } - }; - - // Add a new instance - const addInstance = (newInstance: WhatsAppInstance) => { - console.log("New instance created, adding to instances list:", newInstance); - - // Check if instance with same ID already exists - const existingIndex = instances.findIndex(inst => inst.instanceId === newInstance.instanceId); - - if (existingIndex >= 0) { - // Update existing instance - console.log(`Instance with ID ${newInstance.instanceId} already exists, updating it`); - setInstances(prevInstances => - prevInstances.map((instance, index) => - index === existingIndex ? newInstance : instance - ) - ); - } else { - // Add new instance - setInstances(prevInstances => [...prevInstances, newInstance]); - } - }; - - // Remove an instance - const removeInstance = (instanceId: string) => { - console.log(`Removing instance with ID: ${instanceId}`); - setInstances(prevInstances => { - const filtered = prevInstances.filter(instance => instance.instanceId !== instanceId); - console.log("Updated instances after deletion:", filtered); - return filtered; - }); - }; - - // Update an instance - const updateInstance = (updatedInstance: WhatsAppInstance) => { - console.log(`Updating instance: ${updatedInstance.instanceName}`, updatedInstance); - setInstances(prevInstances => { - const newInstances = prevInstances.map(instance => - instance.instanceId === updatedInstance.instanceId - ? updatedInstance - : instance - ); - - console.log("Updated instances:", newInstances); - return newInstances; - }); - }; - - return { - instances, - isRefreshing, - currentUserId, - addInstance, - removeInstance, - updateInstance, - refreshInstances, - checkAllInstancesStatus - }; -}; diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index 6f141f6..69116ba 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -147,42 +147,6 @@ export type Database = { } Relationships: [] } - rodrigo_audio_messages: { - Row: { - audio_url: string | null - created_at: string - duration: number | null - id: string - instance_id: string - message_id: string | null - metadata: Json | null - sender_id: string - sender_name: string | null - } - Insert: { - audio_url?: string | null - created_at?: string - duration?: number | null - id?: string - instance_id: string - message_id?: string | null - metadata?: Json | null - sender_id: string - sender_name?: string | null - } - Update: { - audio_url?: string | null - created_at?: string - duration?: number | null - id?: string - instance_id?: string - message_id?: string | null - metadata?: Json | null - sender_id?: string - sender_name?: string | null - } - Relationships: [] - } transacoes: { Row: { categoria: string | null @@ -190,7 +154,6 @@ export type Database = { detalhes: string | null estabelecimento: string | null id: number - login: string | null quando: string | null tipo: string | null user: string | null @@ -202,7 +165,6 @@ export type Database = { detalhes?: string | null estabelecimento?: string | null id?: number - login?: string | null quando?: string | null tipo?: string | null user?: string | null @@ -214,7 +176,6 @@ export type Database = { detalhes?: string | null estabelecimento?: string | null id?: number - login?: string | null quando?: string | null tipo?: string | null user?: string | null diff --git a/src/pages/RodrigoAudio.tsx b/src/pages/RodrigoAudio.tsx deleted file mode 100644 index b77b344..0000000 --- a/src/pages/RodrigoAudio.tsx +++ /dev/null @@ -1,84 +0,0 @@ - -import React, { useEffect, useState } from 'react'; -import Layout from '@/components/layout/Layout'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { AudioMessage, getRodrigoAudioMessages } from '@/services/audioMessageService'; -import { format } from 'date-fns'; - -const RodrigoAudio = () => { - const [audioMessages, setAudioMessages] = useState([]); - const [isLoading, setIsLoading] = useState(false); - - const fetchAudioMessages = async () => { - setIsLoading(true); - try { - const messages = await getRodrigoAudioMessages(); - setAudioMessages(messages); - } catch (error) { - console.error('Error fetching Rodrigo audio messages:', error); - } finally { - setIsLoading(false); - } - }; - - useEffect(() => { - fetchAudioMessages(); - }, []); - - return ( - -
-
-

Áudios do Rodrigo

- -
- - {audioMessages.length === 0 ? ( - - -
-

Nenhuma mensagem de áudio encontrada para Rodrigo.

-
-
-
- ) : ( -
- {audioMessages.map((message) => ( - - - - {message.sender_name || 'Usuário Desconhecido'} - - - -
-

- Data: {message.created_at ? format(new Date(message.created_at), 'dd/MM/yyyy HH:mm') : 'N/A'} -

-

- Duração: {message.duration ? `${message.duration}s` : 'N/A'} -

- {message.audio_url && ( - - )} -
-
-
- ))} -
- )} -
-
- ); -}; - -export default RodrigoAudio; diff --git a/src/pages/WhatsApp.tsx b/src/pages/WhatsApp.tsx index e4a59be..383189f 100644 --- a/src/pages/WhatsApp.tsx +++ b/src/pages/WhatsApp.tsx @@ -1,27 +1,25 @@ -import React, { useState, useEffect } from 'react'; +import { useEffect } from 'react'; import Layout from '@/components/layout/Layout'; -import { - CreateInstanceForm, - InstanceList, - InstanceStats, - QrCodeDialog -} from '@/components/whatsapp'; -import { Button } from '@/components/ui/button'; -import UsersDialog from '@/components/usuarios/UsersDialog'; +import { WhatsAppInstance } from '@/types/whatsAppTypes'; +import CreateInstanceForm from '@/components/whatsapp/CreateInstanceForm'; +import InstanceList from '@/components/whatsapp/InstanceList'; +import InstanceStats from '@/components/whatsapp/InstanceStats'; +import QrCodeDialog from '@/components/whatsapp/QrCodeDialog'; import { useWhatsAppInstances } from '@/hooks/useWhatsAppInstances'; import { useWhatsAppActions } from '@/hooks/useWhatsAppActions'; +import { useToast } from '@/hooks/use-toast'; const WhatsApp = () => { + const { toast } = useToast(); const { instances, - isRefreshing, - currentUserId, + isRefreshing, addInstance, removeInstance, updateInstance, - refreshInstances, - checkAllInstancesStatus + refreshInstances, + checkAllInstancesStatus } = useWhatsAppInstances(); const { @@ -35,85 +33,119 @@ const WhatsApp = () => { handleViewQrCode } = useWhatsAppActions(updateInstance, removeInstance, checkAllInstancesStatus); - // Poll for status updates every 30 seconds + // Set up periodic status checks useEffect(() => { - let interval: ReturnType | null = null; + console.log("Setting up periodic status checks, current instances:", instances.length); - const startPolling = () => { - interval = setInterval(() => { - console.log("Polling for WhatsApp instance status updates"); - checkAllInstancesStatus(); - }, 30000); // Check every 30 seconds - }; + // Check status initially + if (instances.length > 0) { + checkAllInstancesStatus(); + } - // Start polling - startPolling(); - - // Cleanup on unmount + // Set up interval for periodic checks (every 30 seconds) + const interval = setInterval(() => { + if (instances.length > 0) { + console.log("Running periodic status check"); + checkAllInstancesStatus(); + } + }, 30000); // 30 seconds + + // Clean up interval when component unmounts return () => { if (interval) { clearInterval(interval); } }; - }, [checkAllInstancesStatus]); + }, [instances.length, checkAllInstancesStatus]); - // Create a wrapper function that matches the expected signature - const handleDeleteInstanceWrapper = (instanceId: string) => { - const instance = instances.find(inst => inst.instanceId === instanceId); - if (instance) { - handleDeleteInstance(instanceId, instance.instanceName); - } else { - console.error(`Instance with ID ${instanceId} not found`); + // Run refresh instances on initial load to get server instances + useEffect(() => { + const initialLoad = async () => { + if (instances.length === 0) { + try { + await refreshInstances(); + } catch (error) { + console.error("Error on initial instance refresh:", error); + } + } + }; + + initialLoad(); + }, []); + + // Handler for when a new instance is created + const handleInstanceCreated = async (newInstance: WhatsAppInstance) => { + console.log('New instance to be added:', newInstance); + addInstance(newInstance); + + // If there's a QR code in the response, show it + if (newInstance.qrcode) { + handleViewQrCode(newInstance); + } + + // Trigger a status check for all instances + try { + await checkAllInstancesStatus(); + } catch (error) { + console.error("Error checking status after instance creation:", error); + toast({ + title: "Aviso", + description: "Instância criada, mas não foi possível verificar o status. Tente atualizar a lista manualmente.", + variant: "default", + }); } }; + // Handler for when an instance is deleted + const handleDeleteInstanceWrapper = (instanceId: string) => { + console.log(`Instance deletion requested for ID: ${instanceId}`); + const instanceToDelete = instances.find(i => i.instanceId === instanceId); + if (instanceToDelete) { + handleDeleteInstance(instanceId, instanceToDelete.instanceName); + } else { + console.error(`Instance with ID ${instanceId} not found for deletion`); + toast({ + title: "Erro", + description: "Instância não encontrada para exclusão", + variant: "destructive", + }); + } + }; + + console.log('Current instances in WhatsApp component:', instances); + return ( -
-
-

WhatsApp Instances

-
- - -
+
+
+

Conectar WhatsApp

- -
-
- -
- -
-
-
- -
-
- - {activeInstance && ( - - )} + + {/* Form to create a new instance */} + + + {/* Stats component */} + + + {/* List of created instances */} + + + {/* QR Code Dialog */} +
); diff --git a/src/services/audioMessageService.ts b/src/services/audioMessageService.ts deleted file mode 100644 index 5317198..0000000 --- a/src/services/audioMessageService.ts +++ /dev/null @@ -1,104 +0,0 @@ - -import { supabase } from '@/integrations/supabase/client'; -import { useToast } from '@/hooks/use-toast'; - -// Define types for audio message data -export interface AudioMessage { - id?: string; - created_at?: string; - sender_id: string; - sender_name?: string; - message_id?: string; - audio_url?: string; - duration?: number; - instance_id: string; - metadata?: Record; -} - -// Constants for specific users -const RODRIGO_GROUP_ID = '120363420212322973@g.us'; -const RODRIGO_EMAIL = 'rodrigobm10@gmail.com'; - -/** - * Stores an audio message from a specific sender to Rodrigo's database - */ -export const storeAudioMessageForRodrigo = async (audioMessage: AudioMessage): Promise => { - try { - console.log(`Storing audio message for Rodrigo from sender ${audioMessage.sender_id}`); - - // Ensure this is a message for Rodrigo's group - if (audioMessage.sender_id !== RODRIGO_GROUP_ID) { - console.log('Message not for Rodrigo group, skipping storage'); - return false; - } - - // Insert the audio message into Rodrigo's table - const { data, error } = await supabase - .from('rodrigo_audio_messages') - .insert([audioMessage]); - - if (error) { - console.error('Error storing audio message for Rodrigo:', error); - return false; - } - - console.log('Successfully stored audio message for Rodrigo', data); - return true; - } catch (error) { - console.error('Exception storing audio message for Rodrigo:', error); - return false; - } -}; - -/** - * Retrieves all audio messages stored for Rodrigo - */ -export const getRodrigoAudioMessages = async (): Promise => { - try { - const { data, error } = await supabase - .from('rodrigo_audio_messages') - .select('*') - .order('created_at', { ascending: false }); - - if (error) { - console.error('Error fetching Rodrigo audio messages:', error); - return []; - } - - return data as AudioMessage[]; - } catch (error) { - console.error('Exception fetching Rodrigo audio messages:', error); - return []; - } -}; - -/** - * Hook for managing audio messages for Rodrigo - */ -export const useRodrigoAudioMessages = () => { - const { toast } = useToast(); - - const handleNewAudioMessage = async (audioMessage: AudioMessage): Promise => { - const success = await storeAudioMessageForRodrigo(audioMessage); - - if (success) { - toast({ - title: "Áudio gravado", - description: `Áudio de ${audioMessage.sender_name || 'usuário'} foi gravado para o Rodrigo.`, - }); - return true; - } else { - toast({ - title: "Erro", - description: "Não foi possível gravar o áudio para o Rodrigo.", - variant: "destructive", - }); - return false; - } - }; - - return { - handleNewAudioMessage, - getRodrigoAudioMessages - }; -}; diff --git a/src/services/userService.ts b/src/services/userService.ts deleted file mode 100644 index 986f228..0000000 --- a/src/services/userService.ts +++ /dev/null @@ -1,62 +0,0 @@ - -import { supabase } from "@/integrations/supabase/client"; -import { format } from "date-fns"; - -export interface UserInfo { - id: string; - nome: string; - email: string; - empresa: string | null; - created_at: string; -} - -export const getUsersRegisteredToday = async (): Promise => { - try { - console.log("Buscando usuários cadastrados hoje..."); - // Obtém a data atual no formato YYYY-MM-DD - const today = format(new Date(), 'yyyy-MM-dd'); - - // Busca usuários cadastrados hoje - const { data, error } = await supabase - .from("usuarios") - .select("*") - .gte('created_at', `${today}T00:00:00`) // Data início (hoje 00:00:00) - .lte('created_at', `${today}T23:59:59`) // Data fim (hoje 23:59:59) - .order('created_at', { ascending: false }); - - if (error) { - console.error("Erro ao buscar usuários:", error); - throw error; - } - - console.log(`Encontrados ${data?.length || 0} usuários cadastrados hoje:`, data); - - return data as UserInfo[]; - } catch (error) { - console.error("Erro ao buscar usuários cadastrados hoje:", error); - throw error; - } -}; - -export const getAllUsers = async (): Promise => { - try { - console.log("Buscando todos os usuários..."); - - const { data, error } = await supabase - .from("usuarios") - .select("*") - .order('created_at', { ascending: false }); - - if (error) { - console.error("Erro ao buscar usuários:", error); - throw error; - } - - console.log(`Encontrados ${data?.length || 0} usuários no total`); - - return data as UserInfo[]; - } catch (error) { - console.error("Erro ao buscar todos os usuários:", error); - throw error; - } -}; diff --git a/src/services/webhookService.ts b/src/services/webhookService.ts deleted file mode 100644 index acb0e87..0000000 --- a/src/services/webhookService.ts +++ /dev/null @@ -1,125 +0,0 @@ - -import { AudioMessage, storeAudioMessageForRodrigo } from './audioMessageService'; - -// Constants for specific users -const RODRIGO_GROUP_ID = '120363420212322973@g.us'; -const RODRIGO_EMAIL = 'rodrigobm10@gmail.com'; - -interface WebhookMessageEvent { - instance: { - id: string; - name: string; - }; - message: { - id: string; - from: string; - fromMe: boolean; - to: string; - body: string; - type: string; - caption?: string; - timestamp: number; - hasMedia: boolean; - mediaUrl?: string; - mediaMimeType?: string; - mediaDuration?: number; - sender?: { - id: string; - name?: string; - pushname?: string; - }; - }; -} - -/** - * Process an incoming webhook event from the Evolution API - */ -export const processWebhookEvent = async (event: any): Promise => { - try { - console.log('Processing webhook event:', JSON.stringify(event)); - - // Ensure this is a message event - if (!event.message) { - console.log('Not a message event, skipping'); - return false; - } - - const messageEvent = event as WebhookMessageEvent; - - // Check if this is an audio message - if (messageEvent.message.type === 'audio' || messageEvent.message.type === 'voice' || messageEvent.message.type === 'ptt') { - return await processAudioMessage(messageEvent); - } - - return false; - } catch (error) { - console.error('Error processing webhook event:', error); - return false; - } -}; - -/** - * Process an audio message from the webhook event - */ -const processAudioMessage = async (event: WebhookMessageEvent): Promise => { - // Check if the message is for Rodrigo's group - if (event.message.to === RODRIGO_GROUP_ID || event.message.from === RODRIGO_GROUP_ID) { - console.log(`Audio message for Rodrigo detected from ${event.message.from}`); - - const audioMessage: AudioMessage = { - sender_id: event.message.from, - sender_name: event.message.sender?.name || event.message.sender?.pushname, - message_id: event.message.id, - audio_url: event.message.mediaUrl, - duration: event.message.mediaDuration, - instance_id: event.instance.id, - metadata: { - timestamp: event.message.timestamp, - mediaType: event.message.mediaMimeType, - caption: event.message.caption - } - }; - - return await storeAudioMessageForRodrigo(audioMessage); - } - - return false; -}; - -/** - * Configure a webhook for an instance in the Evolution API - */ -export const configureWebhook = async ( - instanceName: string, - webhookUrl: string, - apiKey: string -): Promise => { - try { - const serverUrl = "evolutionapi2.innova1001.com.br"; - - const response = await fetch(`https://${serverUrl}/instance/webhook/${encodeURIComponent(instanceName)}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'apikey': apiKey - }, - body: JSON.stringify({ - url: webhookUrl, - events: ['messages', 'messages.ack', 'messages.upsert'] - }) - }); - - if (!response.ok) { - const errorData = await response.json(); - console.error('Error configuring webhook:', errorData); - return false; - } - - const data = await response.json(); - console.log('Webhook configuration response:', data); - return true; - } catch (error) { - console.error('Error configuring webhook:', error); - return false; - } -}; diff --git a/src/types/index.ts b/src/types/index.ts deleted file mode 100644 index 6e41671..0000000 --- a/src/types/index.ts +++ /dev/null @@ -1,10 +0,0 @@ - -import { ReactNode } from "react"; - -export interface MainNavItem { - title: string; - href?: string; - icon?: ReactNode; - disabled?: boolean; - external?: boolean; -} diff --git a/supabase/config.toml b/supabase/config.toml index f283032..46af35f 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -1,6 +1 @@ - -project_id = "tnurlgbvfsxwqgwxamni" - -# Enable webhook edge function without JWT verification -[functions.whatsapp-webhook] -verify_jwt = false +project_id = "tnurlgbvfsxwqgwxamni" \ No newline at end of file diff --git a/supabase/functions/whatsapp-webhook/index.ts b/supabase/functions/whatsapp-webhook/index.ts deleted file mode 100644 index 66beb8e..0000000 --- a/supabase/functions/whatsapp-webhook/index.ts +++ /dev/null @@ -1,132 +0,0 @@ - -import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; -import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.4"; - -// Constants for specific users -const RODRIGO_GROUP_ID = '120363420212322973@g.us'; -const RODRIGO_EMAIL = 'rodrigobm10@gmail.com'; - -const corsHeaders = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Headers": - "authorization, x-client-info, apikey, content-type", -}; - -interface AudioMessage { - sender_id: string; - sender_name?: string; - message_id?: string; - audio_url?: string; - duration?: number; - instance_id: string; - metadata?: Record; -} - -const supabaseUrl = Deno.env.get('SUPABASE_URL') || ''; -const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') || ''; - -const supabase = createClient(supabaseUrl, supabaseServiceKey); - -async function storeAudioMessageForRodrigo(audioMessage: AudioMessage) { - try { - // Insert the audio message into Rodrigo's table - const { data, error } = await supabase - .from('rodrigo_audio_messages') - .insert([audioMessage]); - - if (error) { - console.error('Error storing audio message for Rodrigo:', error); - return false; - } - - console.log('Successfully stored audio message for Rodrigo', data); - return true; - } catch (error) { - console.error('Exception storing audio message for Rodrigo:', error); - return false; - } -} - -// Process the webhook event and store audio messages for Rodrigo -async function processWebhookEvent(event: any): Promise { - try { - console.log('Processing webhook event:', JSON.stringify(event)); - - // Ensure this is a message event - if (!event.message) { - console.log('Not a message event, skipping'); - return false; - } - - // Check if this is an audio message - const messageType = event.message.type; - if (messageType === 'audio' || messageType === 'voice' || messageType === 'ptt') { - // Check if the message is for Rodrigo's group - if (event.message.to === RODRIGO_GROUP_ID || event.message.from === RODRIGO_GROUP_ID) { - console.log(`Audio message for Rodrigo detected from ${event.message.from}`); - - const audioMessage: AudioMessage = { - sender_id: event.message.from, - sender_name: event.message.sender?.name || event.message.sender?.pushname, - message_id: event.message.id, - audio_url: event.message.mediaUrl, - duration: event.message.mediaDuration, - instance_id: event.instance.id, - metadata: { - timestamp: event.message.timestamp, - mediaType: event.message.mediaMimeType, - caption: event.message.caption - } - }; - - return await storeAudioMessageForRodrigo(audioMessage); - } - } - - return false; - } catch (error) { - console.error('Error processing webhook event:', error); - return false; - } -} - -serve(async (req) => { - // Handle CORS preflight requests - if (req.method === "OPTIONS") { - return new Response(null, { headers: corsHeaders }); - } - - try { - if (req.method === "POST") { - const event = await req.json(); - - // Process the webhook event - const success = await processWebhookEvent(event); - - return new Response( - JSON.stringify({ success }), - { - headers: { ...corsHeaders, "Content-Type": "application/json" }, - status: 200, - } - ); - } - - return new Response( - JSON.stringify({ error: "Method not allowed" }), - { - headers: { ...corsHeaders, "Content-Type": "application/json" }, - status: 405, - } - ); - } catch (error) { - console.error("Error handling webhook:", error); - return new Response( - JSON.stringify({ error: "Internal server error" }), - { - headers: { ...corsHeaders, "Content-Type": "application/json" }, - status: 500, - } - ); - } -});