diff --git a/src/components/whatsapp/InstanceCard.tsx b/src/components/whatsapp/InstanceCard.tsx index 199c63e..c6ae9b3 100644 --- a/src/components/whatsapp/InstanceCard.tsx +++ b/src/components/whatsapp/InstanceCard.tsx @@ -1,63 +1,261 @@ +import { useState } from 'react'; import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; -import { QrCode, Smartphone } from 'lucide-react'; +import { + QrCode, + Smartphone, + RefreshCw, + X, + PowerOff, + CircleDot, + CircleOff +} from 'lucide-react'; import { WhatsAppInstance } from '@/types/whatsAppTypes'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; interface InstanceCardProps { instance: WhatsAppInstance; onViewQrCode: (instance: WhatsAppInstance) => void; onDelete: (instanceId: string) => void; + onRestart: (instance: WhatsAppInstance) => Promise; + onLogout: (instance: WhatsAppInstance) => Promise; + onSetPresence: (instance: WhatsAppInstance, presence: 'online' | 'offline') => Promise; } -const InstanceCard = ({ instance, onViewQrCode, onDelete }: InstanceCardProps) => { +const InstanceCard = ({ + instance, + onViewQrCode, + onDelete, + onRestart, + onLogout, + onSetPresence +}: InstanceCardProps) => { + const [loading, setLoading] = useState(null); + const [confirmAction, setConfirmAction] = useState<{ + open: boolean; + title: string; + description: string; + action: () => Promise; + actionLabel: string; + } | null>(null); + + // Função para processar uma ação com confirmação + const handleConfirmAction = ( + title: string, + description: string, + action: () => Promise, + actionLabel: string = "Confirmar" + ) => { + setConfirmAction({ + open: true, + title, + description, + action, + actionLabel + }); + }; + + // Executa a ação após confirmação + const executeAction = async () => { + if (!confirmAction) return; + + try { + setLoading(confirmAction.title); + await confirmAction.action(); + } catch (error) { + console.error("Error executing action:", error); + } finally { + setLoading(null); + setConfirmAction(null); + } + }; + + // Handler para reiniciar instância + const handleRestart = () => { + handleConfirmAction( + "Reiniciar Instância", + `Deseja realmente reiniciar a instância "${instance.instanceName}"? A conexão atual será fechada e restabelecida.`, + async () => { + await onRestart(instance); + }, + "Reiniciar" + ); + }; + + // Handler para deslogar instância + const handleLogout = () => { + handleConfirmAction( + "Desconectar Instância", + `Deseja realmente desconectar a instância "${instance.instanceName}"? Você precisará escanear o QR Code novamente para reconectar.`, + async () => { + await onLogout(instance); + }, + "Desconectar" + ); + }; + + // Handler para apagar instância + const handleDelete = () => { + handleConfirmAction( + "Excluir Instância", + `Deseja realmente excluir a instância "${instance.instanceName}"? Esta ação não pode ser desfeita.`, + async () => { + onDelete(instance.instanceId); + }, + "Excluir" + ); + }; + + // Handler para definir presença online + const handleSetOnline = () => { + handleConfirmAction( + "Definir Presença Online", + `Deseja alterar o status da instância "${instance.instanceName}" para Online?`, + async () => { + await onSetPresence(instance, 'online'); + }, + "Definir Online" + ); + }; + + // Handler para definir presença offline + const handleSetOffline = () => { + handleConfirmAction( + "Definir Presença Offline", + `Deseja alterar o status da instância "${instance.instanceName}" para Offline?`, + async () => { + await onSetPresence(instance, 'offline'); + }, + "Definir Offline" + ); + }; + return ( - - - {instance.instanceName} - - -
-
- - {instance.phoneNumber} + <> + + + {instance.instanceName} + + +
+
+ + {instance.phoneNumber} +
+
+ {instance.connectionState === 'open' ? ( + + + Status: Conectado + + ) : ( + + + Status: Desconectado + + )} +
-
- {instance.connectionState === 'open' ? ( - - - Status: Conectado - - ) : ( - - - Status: Desconectado - - )} + + +
+ +
-
-
- - - - -
+
+ + +
+
+ + +
+ + + + !open && setConfirmAction(null)}> + + + {confirmAction?.title} + + {confirmAction?.description} + + + + Cancelar + + {loading === confirmAction?.title ? "Processando..." : confirmAction?.actionLabel} + + + + + ); }; diff --git a/src/components/whatsapp/InstanceList.tsx b/src/components/whatsapp/InstanceList.tsx index ef2f871..21eedf1 100644 --- a/src/components/whatsapp/InstanceList.tsx +++ b/src/components/whatsapp/InstanceList.tsx @@ -1,15 +1,31 @@ +import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import InstanceCard from './InstanceCard'; import { WhatsAppInstance } from '@/types/whatsAppTypes'; +import { RefreshCw } from 'lucide-react'; interface InstanceListProps { instances: WhatsAppInstance[]; onViewQrCode: (instance: WhatsAppInstance) => void; onDelete: (instanceId: string) => void; + onRestart: (instance: WhatsAppInstance) => Promise; + onLogout: (instance: WhatsAppInstance) => Promise; + onSetPresence: (instance: WhatsAppInstance, presence: 'online' | 'offline') => Promise; + onRefreshInstances: () => Promise; + isRefreshing: boolean; } -const InstanceList = ({ instances, onViewQrCode, onDelete }: InstanceListProps) => { +const InstanceList = ({ + instances, + onViewQrCode, + onDelete, + onRestart, + onLogout, + onSetPresence, + onRefreshInstances, + isRefreshing +}: InstanceListProps) => { if (instances.length === 0) { return ( @@ -25,7 +41,18 @@ const InstanceList = ({ instances, onViewQrCode, onDelete }: InstanceListProps) return (
-

Instâncias Criadas

+
+

Instâncias Criadas

+ +
{instances.map((instance) => ( ))}
diff --git a/src/pages/WhatsApp.tsx b/src/pages/WhatsApp.tsx index 687942e..b4e4df1 100644 --- a/src/pages/WhatsApp.tsx +++ b/src/pages/WhatsApp.tsx @@ -7,7 +7,12 @@ import { fetchQrCode, fetchConnectionState, saveInstancesToLocalStorage, - loadInstancesFromLocalStorage + loadInstancesFromLocalStorage, + fetchAllInstances, + restartInstance, + logoutInstance, + deleteInstance, + setInstancePresence } from '@/services/whatsAppService'; import CreateInstanceForm from '@/components/whatsapp/CreateInstanceForm'; import InstanceList from '@/components/whatsapp/InstanceList'; @@ -20,6 +25,7 @@ const WhatsApp = () => { const [qrDialogOpen, setQrDialogOpen] = useState(false); const [statusCheckInterval, setStatusCheckInterval] = useState(null); const [currentUserId, setCurrentUserId] = useState(''); + const [isRefreshing, setIsRefreshing] = useState(false); // Get current user ID and load instances on component mount useEffect(() => { @@ -113,6 +119,155 @@ const WhatsApp = () => { } }; + // Handler para atualizar a lista de instâncias do servidor + const handleRefreshInstances = 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 { + const response = await fetchAllInstances(); + console.log("Fetched instances from server:", response); + + if (response.instances && Array.isArray(response.instances)) { + // Filtra instâncias para mostrar apenas as do usuário atual + // E mapeia para o formato correto com userId + const serverInstances: WhatsAppInstance[] = response.instances + .filter(serverInstance => { + // Implemente sua lógica de filtro para o usuário atual (se necessário) + // Por padrão, vamos assumir que todas as instâncias são do usuário atual + return true; + }) + .map(serverInstance => ({ + instanceName: serverInstance.instanceName, + instanceId: serverInstance.instanceName, + phoneNumber: serverInstance.number || 'Desconhecido', + userId: currentUserId, + connectionState: serverInstance.state || 'closed', + status: serverInstance.status || 'unknown' + })); + + // Mesclando instâncias do servidor com as locais (para não perder dados locais) + const localInstanceIds = new Set(instances.map(i => i.instanceId)); + const newInstances = [ + ...instances, + ...serverInstances.filter(i => !localInstanceIds.has(i.instanceId)) + ]; + + setInstances(newInstances); + 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 + checkAllInstancesStatus(); + } + }; + + // Handler for quando uma instância é reiniciada + const handleRestartInstance = async (instance: WhatsAppInstance) => { + try { + await restartInstance(instance.instanceName); + + // Atualiza o estado da instância para "connecting" + setInstances(prev => + prev.map(i => + i.instanceId === instance.instanceId + ? { ...i, connectionState: 'connecting' as const } + : i + ) + ); + + 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 { + await logoutInstance(instance.instanceName); + + // Atualiza o estado da instância para "closed" + setInstances(prev => + prev.map(i => + i.instanceId === instance.instanceId + ? { ...i, connectionState: 'closed' as const } + : i + ) + ); + + 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 { + await setInstancePresence(instance.instanceName, presence); + + toast({ + title: "Sucesso", + description: `Instância ${instance.instanceName} agora está ${presence === 'online' ? 'Online' : 'Offline'}` + }); + + // Não precisamos alterar o estado da instância aqui, pois isso não afeta o connectionState + + } 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 a new instance is created const handleInstanceCreated = async (newInstance: WhatsAppInstance) => { console.log("New instance created, adding to instances list:", newInstance); @@ -152,23 +307,49 @@ const WhatsApp = () => { }; // Handler for when an instance is deleted - const handleDeleteInstance = (instanceId: string) => { + const handleDeleteInstance = async (instanceId: string) => { console.log(`Deleting instance with ID: ${instanceId}`); - setInstances(prevInstances => { - const filtered = prevInstances.filter(instance => instance.instanceId !== instanceId); - console.log("Updated instances after deletion:", filtered); - return filtered; - }); - // If we're viewing QR code for this instance, close the dialog - if (activeInstance?.instanceId === instanceId) { - setQrDialogOpen(false); + // Find the instance before deleting it + const instanceToDelete = instances.find(i => i.instanceId === instanceId); + + if (!instanceToDelete) { + toast({ + title: "Erro", + description: "Instância não encontrada", + variant: "destructive", + }); + return; } - toast({ - title: "Instância removida", - description: "A instância do WhatsApp foi removida com sucesso.", - }); + try { + // Call API to delete instance + await deleteInstance(instanceToDelete.instanceName); + + // Remove from local state if API call was successful + setInstances(prevInstances => { + const filtered = prevInstances.filter(instance => instance.instanceId !== instanceId); + console.log("Updated instances after deletion:", filtered); + return filtered; + }); + + // If we're viewing QR code for this instance, close the dialog + if (activeInstance?.instanceId === instanceId) { + setQrDialogOpen(false); + } + + 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); + toast({ + title: "Erro", + description: "Falha ao excluir a instância. Tente novamente.", + variant: "destructive", + }); + } }; return ( @@ -185,7 +366,12 @@ const WhatsApp = () => { {/* QR Code Dialog */} diff --git a/src/services/whatsAppService.ts b/src/services/whatsAppService.ts index 2c8c387..d63acff 100644 --- a/src/services/whatsAppService.ts +++ b/src/services/whatsAppService.ts @@ -69,6 +69,121 @@ export const fetchConnectionState = async (instanceName: string): Promise<'open' } }; +// Nova função para listar todas as instâncias +export const fetchAllInstances = async (): Promise => { + try { + const response = await fetch(`https://${SERVER_URL}/fetch-instances`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'apikey': API_KEY + } + }); + + if (!response.ok) { + throw new Error('Erro ao buscar instâncias'); + } + + return response.json(); + } catch (error) { + console.error("Error fetching all instances:", error); + throw error; + } +}; + +// Função para reiniciar uma instância +export const restartInstance = async (instanceName: string): Promise => { + try { + const response = await fetch(`https://${SERVER_URL}/instance/restart/${encodeURIComponent(instanceName)}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'apikey': API_KEY + } + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || 'Erro ao reiniciar instância'); + } + + return response.json(); + } catch (error) { + console.error(`Error restarting instance ${instanceName}:`, error); + throw error; + } +}; + +// Função para desconectar (logout) uma instância +export const logoutInstance = async (instanceName: string): Promise => { + try { + const response = await fetch(`https://${SERVER_URL}/instance/logout/${encodeURIComponent(instanceName)}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'apikey': API_KEY + } + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || 'Erro ao desconectar instância'); + } + + return response.json(); + } catch (error) { + console.error(`Error logging out instance ${instanceName}:`, error); + throw error; + } +}; + +// Função para excluir uma instância +export const deleteInstance = async (instanceName: string): Promise => { + try { + const response = await fetch(`https://${SERVER_URL}/instance/${encodeURIComponent(instanceName)}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'apikey': API_KEY + } + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || 'Erro ao excluir instância'); + } + + return response.json(); + } catch (error) { + console.error(`Error deleting instance ${instanceName}:`, error); + throw error; + } +}; + +// Função para definir presença online/offline +export const setInstancePresence = async (instanceName: string, presence: 'online' | 'offline'): Promise => { + try { + const response = await fetch(`https://${SERVER_URL}/instance/setPresence/${encodeURIComponent(instanceName)}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'apikey': API_KEY + }, + body: JSON.stringify({ presence }) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || `Erro ao definir presença para ${presence}`); + } + + return response.json(); + } catch (error) { + console.error(`Error setting presence to ${presence} for instance ${instanceName}:`, error); + throw error; + } +}; + export const saveInstancesToLocalStorage = ( instances: WhatsAppInstance[], currentUserId: string diff --git a/src/types/whatsAppTypes.ts b/src/types/whatsAppTypes.ts index 9bf08df..c98e44a 100644 --- a/src/types/whatsAppTypes.ts +++ b/src/types/whatsAppTypes.ts @@ -5,6 +5,7 @@ export interface WhatsAppInstance { phoneNumber: string; userId: string; status?: string; - qrcode?: string; + qrcode?: string | null; connectionState?: 'open' | 'closed' | 'connecting'; + presence?: 'online' | 'offline'; }