Add advanced instance management features
Implement instance listing, restart, disconnect, delete, and presence setting functionalities with confirmation dialogs and UI updates.
This commit is contained in:
parent
686b682c19
commit
b0d6e88a76
@ -1,63 +1,261 @@
|
|||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card';
|
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
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 { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from "@/components/ui/alert-dialog";
|
||||||
|
|
||||||
interface InstanceCardProps {
|
interface InstanceCardProps {
|
||||||
instance: WhatsAppInstance;
|
instance: WhatsAppInstance;
|
||||||
onViewQrCode: (instance: WhatsAppInstance) => void;
|
onViewQrCode: (instance: WhatsAppInstance) => void;
|
||||||
onDelete: (instanceId: string) => void;
|
onDelete: (instanceId: string) => void;
|
||||||
|
onRestart: (instance: WhatsAppInstance) => Promise<void>;
|
||||||
|
onLogout: (instance: WhatsAppInstance) => Promise<void>;
|
||||||
|
onSetPresence: (instance: WhatsAppInstance, presence: 'online' | 'offline') => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InstanceCard = ({ instance, onViewQrCode, onDelete }: InstanceCardProps) => {
|
const InstanceCard = ({
|
||||||
|
instance,
|
||||||
|
onViewQrCode,
|
||||||
|
onDelete,
|
||||||
|
onRestart,
|
||||||
|
onLogout,
|
||||||
|
onSetPresence
|
||||||
|
}: InstanceCardProps) => {
|
||||||
|
const [loading, setLoading] = useState<string | null>(null);
|
||||||
|
const [confirmAction, setConfirmAction] = useState<{
|
||||||
|
open: boolean;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
action: () => Promise<void>;
|
||||||
|
actionLabel: string;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
// Função para processar uma ação com confirmação
|
||||||
|
const handleConfirmAction = (
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
action: () => Promise<void>,
|
||||||
|
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 (
|
return (
|
||||||
<Card className="h-full">
|
<>
|
||||||
<CardHeader className="pb-2">
|
<Card className="h-full">
|
||||||
<CardTitle className="text-lg">{instance.instanceName}</CardTitle>
|
<CardHeader className="pb-2">
|
||||||
</CardHeader>
|
<CardTitle className="text-lg">{instance.instanceName}</CardTitle>
|
||||||
<CardContent className="pb-2">
|
</CardHeader>
|
||||||
<div className="space-y-2">
|
<CardContent className="pb-2">
|
||||||
<div className="flex items-center text-sm">
|
<div className="space-y-2">
|
||||||
<Smartphone className="h-4 w-4 mr-2 text-gray-500" />
|
<div className="flex items-center text-sm">
|
||||||
<span>{instance.phoneNumber}</span>
|
<Smartphone className="h-4 w-4 mr-2 text-gray-500" />
|
||||||
|
<span>{instance.phoneNumber}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center text-sm">
|
||||||
|
{instance.connectionState === 'open' ? (
|
||||||
|
<Badge variant="outline" className="flex items-center gap-1 text-green-500 border-green-300">
|
||||||
|
<span className="inline-block w-2 h-2 rounded-full bg-green-500"></span>
|
||||||
|
Status: Conectado
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<Badge variant="outline" className="flex items-center gap-1 text-red-500 border-red-300">
|
||||||
|
<span className="inline-block w-2 h-2 rounded-full bg-red-500"></span>
|
||||||
|
Status: Desconectado
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center text-sm">
|
</CardContent>
|
||||||
{instance.connectionState === 'open' ? (
|
<CardFooter className="flex flex-col gap-2">
|
||||||
<Badge variant="success" className="flex items-center gap-1">
|
<div className="flex justify-between w-full">
|
||||||
<span className="inline-block w-2 h-2 rounded-full bg-green-500"></span>
|
<Button
|
||||||
Status: Conectado
|
variant="outline"
|
||||||
</Badge>
|
size="sm"
|
||||||
) : (
|
onClick={() => onViewQrCode(instance)}
|
||||||
<Badge variant="outline" className="flex items-center gap-1 text-red-500 border-red-300">
|
className="flex items-center"
|
||||||
<span className="inline-block w-2 h-2 rounded-full bg-red-500"></span>
|
>
|
||||||
Status: Desconectado
|
<QrCode className="h-4 w-4 mr-2" />
|
||||||
</Badge>
|
Ver QR Code
|
||||||
)}
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="text-red-500 hover:text-red-700 hover:bg-red-50"
|
||||||
|
onClick={handleDelete}
|
||||||
|
disabled={loading !== null}
|
||||||
|
>
|
||||||
|
<X className="h-4 w-4 mr-2" />
|
||||||
|
Excluir
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="grid grid-cols-2 gap-2 w-full">
|
||||||
</CardContent>
|
<Button
|
||||||
<CardFooter className="flex justify-between">
|
variant="outline"
|
||||||
<Button
|
size="sm"
|
||||||
variant="outline"
|
onClick={handleRestart}
|
||||||
size="sm"
|
disabled={loading !== null}
|
||||||
onClick={() => onViewQrCode(instance)}
|
>
|
||||||
className="flex items-center"
|
<RefreshCw className="h-4 w-4 mr-1" />
|
||||||
>
|
Reiniciar
|
||||||
<QrCode className="h-4 w-4 mr-2" />
|
</Button>
|
||||||
Ver QR Code
|
<Button
|
||||||
</Button>
|
variant="outline"
|
||||||
<Button
|
size="sm"
|
||||||
variant="ghost"
|
onClick={handleLogout}
|
||||||
size="sm"
|
disabled={loading !== null}
|
||||||
className="text-red-500 hover:text-red-700 hover:bg-red-50"
|
>
|
||||||
onClick={() => onDelete(instance.instanceId)}
|
<PowerOff className="h-4 w-4 mr-1" />
|
||||||
>
|
Desconectar
|
||||||
Remover
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</CardFooter>
|
<div className="grid grid-cols-2 gap-2 w-full">
|
||||||
</Card>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleSetOnline}
|
||||||
|
disabled={loading !== null}
|
||||||
|
className="bg-green-50 hover:bg-green-100 text-green-700"
|
||||||
|
>
|
||||||
|
<CircleDot className="h-4 w-4 mr-1" />
|
||||||
|
Definir Online
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleSetOffline}
|
||||||
|
disabled={loading !== null}
|
||||||
|
className="bg-red-50 hover:bg-red-100 text-red-700"
|
||||||
|
>
|
||||||
|
<CircleOff className="h-4 w-4 mr-1" />
|
||||||
|
Definir Offline
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<AlertDialog open={confirmAction?.open} onOpenChange={(open) => !open && setConfirmAction(null)}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{confirmAction?.title}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{confirmAction?.description}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel disabled={loading !== null}>Cancelar</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={executeAction}
|
||||||
|
disabled={loading !== null}
|
||||||
|
className={loading === confirmAction?.title ? "opacity-50 cursor-not-allowed" : ""}
|
||||||
|
>
|
||||||
|
{loading === confirmAction?.title ? "Processando..." : confirmAction?.actionLabel}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,31 @@
|
|||||||
|
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import InstanceCard from './InstanceCard';
|
import InstanceCard from './InstanceCard';
|
||||||
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||||
|
import { RefreshCw } from 'lucide-react';
|
||||||
|
|
||||||
interface InstanceListProps {
|
interface InstanceListProps {
|
||||||
instances: WhatsAppInstance[];
|
instances: WhatsAppInstance[];
|
||||||
onViewQrCode: (instance: WhatsAppInstance) => void;
|
onViewQrCode: (instance: WhatsAppInstance) => void;
|
||||||
onDelete: (instanceId: string) => void;
|
onDelete: (instanceId: string) => void;
|
||||||
|
onRestart: (instance: WhatsAppInstance) => Promise<void>;
|
||||||
|
onLogout: (instance: WhatsAppInstance) => Promise<void>;
|
||||||
|
onSetPresence: (instance: WhatsAppInstance, presence: 'online' | 'offline') => Promise<void>;
|
||||||
|
onRefreshInstances: () => Promise<void>;
|
||||||
|
isRefreshing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InstanceList = ({ instances, onViewQrCode, onDelete }: InstanceListProps) => {
|
const InstanceList = ({
|
||||||
|
instances,
|
||||||
|
onViewQrCode,
|
||||||
|
onDelete,
|
||||||
|
onRestart,
|
||||||
|
onLogout,
|
||||||
|
onSetPresence,
|
||||||
|
onRefreshInstances,
|
||||||
|
isRefreshing
|
||||||
|
}: InstanceListProps) => {
|
||||||
if (instances.length === 0) {
|
if (instances.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@ -25,7 +41,18 @@ const InstanceList = ({ instances, onViewQrCode, onDelete }: InstanceListProps)
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h2 className="text-xl font-semibold">Instâncias Criadas</h2>
|
<div className="flex justify-between items-center">
|
||||||
|
<h2 className="text-xl font-semibold">Instâncias Criadas</h2>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={onRefreshInstances}
|
||||||
|
disabled={isRefreshing}
|
||||||
|
>
|
||||||
|
<RefreshCw className={`h-4 w-4 mr-2 ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||||
|
{isRefreshing ? 'Atualizando...' : 'Atualizar Lista'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{instances.map((instance) => (
|
{instances.map((instance) => (
|
||||||
<InstanceCard
|
<InstanceCard
|
||||||
@ -33,6 +60,9 @@ const InstanceList = ({ instances, onViewQrCode, onDelete }: InstanceListProps)
|
|||||||
instance={instance}
|
instance={instance}
|
||||||
onViewQrCode={onViewQrCode}
|
onViewQrCode={onViewQrCode}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
|
onRestart={onRestart}
|
||||||
|
onLogout={onLogout}
|
||||||
|
onSetPresence={onSetPresence}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,7 +7,12 @@ import {
|
|||||||
fetchQrCode,
|
fetchQrCode,
|
||||||
fetchConnectionState,
|
fetchConnectionState,
|
||||||
saveInstancesToLocalStorage,
|
saveInstancesToLocalStorage,
|
||||||
loadInstancesFromLocalStorage
|
loadInstancesFromLocalStorage,
|
||||||
|
fetchAllInstances,
|
||||||
|
restartInstance,
|
||||||
|
logoutInstance,
|
||||||
|
deleteInstance,
|
||||||
|
setInstancePresence
|
||||||
} from '@/services/whatsAppService';
|
} from '@/services/whatsAppService';
|
||||||
import CreateInstanceForm from '@/components/whatsapp/CreateInstanceForm';
|
import CreateInstanceForm from '@/components/whatsapp/CreateInstanceForm';
|
||||||
import InstanceList from '@/components/whatsapp/InstanceList';
|
import InstanceList from '@/components/whatsapp/InstanceList';
|
||||||
@ -20,6 +25,7 @@ const WhatsApp = () => {
|
|||||||
const [qrDialogOpen, setQrDialogOpen] = useState(false);
|
const [qrDialogOpen, setQrDialogOpen] = useState(false);
|
||||||
const [statusCheckInterval, setStatusCheckInterval] = useState<NodeJS.Timeout | null>(null);
|
const [statusCheckInterval, setStatusCheckInterval] = useState<NodeJS.Timeout | null>(null);
|
||||||
const [currentUserId, setCurrentUserId] = useState<string>('');
|
const [currentUserId, setCurrentUserId] = useState<string>('');
|
||||||
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
|
|
||||||
// Get current user ID and load instances on component mount
|
// Get current user ID and load instances on component mount
|
||||||
useEffect(() => {
|
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
|
// Handler for when a new instance is created
|
||||||
const handleInstanceCreated = async (newInstance: WhatsAppInstance) => {
|
const handleInstanceCreated = async (newInstance: WhatsAppInstance) => {
|
||||||
console.log("New instance created, adding to instances list:", newInstance);
|
console.log("New instance created, adding to instances list:", newInstance);
|
||||||
@ -152,23 +307,49 @@ const WhatsApp = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handler for when an instance is deleted
|
// Handler for when an instance is deleted
|
||||||
const handleDeleteInstance = (instanceId: string) => {
|
const handleDeleteInstance = async (instanceId: string) => {
|
||||||
console.log(`Deleting instance with ID: ${instanceId}`);
|
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
|
// Find the instance before deleting it
|
||||||
if (activeInstance?.instanceId === instanceId) {
|
const instanceToDelete = instances.find(i => i.instanceId === instanceId);
|
||||||
setQrDialogOpen(false);
|
|
||||||
|
if (!instanceToDelete) {
|
||||||
|
toast({
|
||||||
|
title: "Erro",
|
||||||
|
description: "Instância não encontrada",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toast({
|
try {
|
||||||
title: "Instância removida",
|
// Call API to delete instance
|
||||||
description: "A instância do WhatsApp foi removida com sucesso.",
|
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 (
|
return (
|
||||||
@ -186,6 +367,11 @@ const WhatsApp = () => {
|
|||||||
instances={instances}
|
instances={instances}
|
||||||
onViewQrCode={handleViewQrCode}
|
onViewQrCode={handleViewQrCode}
|
||||||
onDelete={handleDeleteInstance}
|
onDelete={handleDeleteInstance}
|
||||||
|
onRestart={handleRestartInstance}
|
||||||
|
onLogout={handleLogoutInstance}
|
||||||
|
onSetPresence={handleSetPresence}
|
||||||
|
onRefreshInstances={handleRefreshInstances}
|
||||||
|
isRefreshing={isRefreshing}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* QR Code Dialog */}
|
{/* QR Code Dialog */}
|
||||||
|
|||||||
@ -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<any> => {
|
||||||
|
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<any> => {
|
||||||
|
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<any> => {
|
||||||
|
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<any> => {
|
||||||
|
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<any> => {
|
||||||
|
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 = (
|
export const saveInstancesToLocalStorage = (
|
||||||
instances: WhatsAppInstance[],
|
instances: WhatsAppInstance[],
|
||||||
currentUserId: string
|
currentUserId: string
|
||||||
|
|||||||
@ -5,6 +5,7 @@ export interface WhatsAppInstance {
|
|||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
qrcode?: string;
|
qrcode?: string | null;
|
||||||
connectionState?: 'open' | 'closed' | 'connecting';
|
connectionState?: 'open' | 'closed' | 'connecting';
|
||||||
|
presence?: 'online' | 'offline';
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user