Reverted to commit b8d0db98bb
This commit is contained in:
parent
ba5d3c92a3
commit
90b3030ebb
8
package-lock.json
generated
8
package-lock.json
generated
@ -41,7 +41,7 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^3.6.0",
|
||||||
"embla-carousel-react": "^8.3.0",
|
"embla-carousel-react": "^8.3.0",
|
||||||
"input-otp": "^1.2.4",
|
"input-otp": "^1.2.4",
|
||||||
"lucide-react": "^0.462.0",
|
"lucide-react": "^0.462.0",
|
||||||
@ -4196,9 +4196,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/date-fns": {
|
"node_modules/date-fns": {
|
||||||
"version": "4.1.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
|||||||
@ -44,7 +44,7 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^3.6.0",
|
||||||
"embla-carousel-react": "^8.3.0",
|
"embla-carousel-react": "^8.3.0",
|
||||||
"input-otp": "^1.2.4",
|
"input-otp": "^1.2.4",
|
||||||
"lucide-react": "^0.462.0",
|
"lucide-react": "^0.462.0",
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import Index from '@/pages/Index';
|
import Index from '@/pages/Index';
|
||||||
import Transacoes from '@/pages/Transacoes';
|
import Transacoes from '@/pages/Transacoes';
|
||||||
@ -10,7 +11,6 @@ import WhatsApp from '@/pages/WhatsApp';
|
|||||||
import { Toaster } from '@/components/ui/toaster';
|
import { Toaster } from '@/components/ui/toaster';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import ProtectedRoute from '@/components/auth/ProtectedRoute';
|
import ProtectedRoute from '@/components/auth/ProtectedRoute';
|
||||||
import RodrigoAudio from './pages/RodrigoAudio';
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@ -49,7 +49,6 @@ function App() {
|
|||||||
} />
|
} />
|
||||||
<Route path="/login" element={<Auth />} />
|
<Route path="/login" element={<Auth />} />
|
||||||
<Route path="/auth" element={<Auth />} />
|
<Route path="/auth" element={<Auth />} />
|
||||||
<Route path="/rodrigo-audio" element={<RodrigoAudio />} />
|
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
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 Header from '@/components/layout/Header';
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { Menu } from 'lucide-react';
|
import { Menu } from 'lucide-react';
|
||||||
@ -39,11 +39,11 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</SheetTrigger>
|
</SheetTrigger>
|
||||||
<SheetContent side="left" className="p-0 w-[250px]">
|
<SheetContent side="left" className="p-0 w-[250px]">
|
||||||
<Sidebar items={defaultItems} className="w-full border-none" />
|
<Sidebar className="w-full border-none" />
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
) : (
|
) : (
|
||||||
<Sidebar items={defaultItems} />
|
<Sidebar />
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col flex-1 overflow-hidden">
|
<div className="flex flex-col flex-1 overflow-hidden">
|
||||||
<Header />
|
<Header />
|
||||||
|
|||||||
@ -1,74 +1,99 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from 'react';
|
||||||
import {
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
LayoutDashboard,
|
import { cn } from '@/lib/utils';
|
||||||
Users,
|
import { Button } from "@/components/ui/button";
|
||||||
Settings,
|
import { ChevronLeft, ChevronRight, Home, CalendarDays, DollarSign, PieChart, Flag, MessageCircle } from 'lucide-react';
|
||||||
MessageSquare,
|
|
||||||
Headphones
|
|
||||||
} from "lucide-react";
|
|
||||||
|
|
||||||
import { MainNavItem } from "@/types";
|
// Updated navItems with the new WhatsApp connection page
|
||||||
import { siteConfig } from "@/config/site";
|
const navItems = [
|
||||||
|
{
|
||||||
|
name: 'Dashboard',
|
||||||
|
path: '/dashboard',
|
||||||
|
icon: <Home className="mr-2 h-5 w-5" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Transações',
|
||||||
|
path: '/transacoes',
|
||||||
|
icon: <DollarSign className="mr-2 h-5 w-5" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Categorias',
|
||||||
|
path: '/categorias',
|
||||||
|
icon: <PieChart className="mr-2 h-5 w-5" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Metas',
|
||||||
|
path: '/metas',
|
||||||
|
icon: <Flag className="mr-2 h-5 w-5" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Calendário',
|
||||||
|
path: '/calendario',
|
||||||
|
icon: <CalendarDays className="mr-2 h-5 w-5" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Conectar WhatsApp',
|
||||||
|
path: '/whatsapp',
|
||||||
|
icon: <MessageCircle className="mr-2 h-5 w-5" />
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
items?: MainNavItem[];
|
|
||||||
className?: string;
|
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 (
|
return (
|
||||||
<div className={`flex flex-col space-y-6 w-full ${className}`}>
|
<div
|
||||||
<a href="/" className="flex items-center space-x-2">
|
className={cn(
|
||||||
<img
|
"flex flex-col h-full bg-sidebar border-r border-border transition-all duration-300 ease-in-out",
|
||||||
src="/logo.png"
|
collapsed ? "w-[60px]" : "w-[250px]",
|
||||||
alt="Logo"
|
className
|
||||||
className="h-8 w-8 rounded-md"
|
)}
|
||||||
/>
|
>
|
||||||
<span className="font-bold">{siteConfig.name}</span>
|
<div className="flex items-center justify-between p-4">
|
||||||
</a>
|
{!collapsed && (
|
||||||
<div className="flex flex-col space-y-1">
|
<h1 className="font-bold text-xl text-primary">FinDash</h1>
|
||||||
{items?.map((item) => (
|
)}
|
||||||
item.href ? (
|
<Button
|
||||||
<a
|
variant="ghost"
|
||||||
key={item.title}
|
size="icon"
|
||||||
href={item.href}
|
onClick={() => setCollapsed(!collapsed)}
|
||||||
className="flex items-center space-x-2 px-4 py-2 rounded-md hover:bg-secondary"
|
className="ml-auto"
|
||||||
>
|
>
|
||||||
{item.icon}
|
{collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
|
||||||
<span>{item.title}</span>
|
</Button>
|
||||||
</a>
|
|
||||||
) : null
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<nav className="flex-1 py-4 space-y-1">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<Link
|
||||||
|
key={item.name}
|
||||||
|
to={item.path}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center px-4 py-2 text-sm font-medium rounded-md transition-colors",
|
||||||
|
isItemActive(item.path)
|
||||||
|
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
||||||
|
: "text-sidebar-foreground hover:bg-sidebar-accent/50",
|
||||||
|
collapsed ? "justify-center" : "justify-start"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className={collapsed ? "mr-0" : "mr-2"}>{item.icon}</span>
|
||||||
|
{!collapsed && <span>{item.name}</span>}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const defaultItems: MainNavItem[] = [
|
export default Sidebar;
|
||||||
{
|
|
||||||
title: "Dashboard",
|
|
||||||
href: "/",
|
|
||||||
icon: <LayoutDashboard className="h-5 w-5" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Usuários",
|
|
||||||
href: "/usuarios",
|
|
||||||
icon: <Users className="h-5 w-5" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "WhatsApp",
|
|
||||||
href: "/whatsapp",
|
|
||||||
icon: <MessageSquare className="h-5 w-5" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Configurações",
|
|
||||||
href: "/configuracoes",
|
|
||||||
icon: <Settings className="h-5 w-5" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Áudios Rodrigo",
|
|
||||||
href: "/rodrigo-audio",
|
|
||||||
icon: <Headphones className="h-5 w-5" />
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|||||||
@ -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 (
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
{trigger || (
|
|
||||||
<Button variant="outline" size="sm" className="gap-2">
|
|
||||||
<UserRoundSearch className="h-4 w-4" />
|
|
||||||
<span>Usuários</span>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="sm:max-w-[700px] max-h-[80vh] overflow-y-auto">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Gerenciamento de Usuários</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Visualize e gerencie os usuários cadastrados na plataforma
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="py-4">
|
|
||||||
<UsersList />
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UsersDialog;
|
|
||||||
@ -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<UserInfo[]>([]);
|
|
||||||
const [allUsers, setAllUsers] = useState<UserInfo[]>([]);
|
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
|
||||||
const [activeTab, setActiveTab] = useState<string>("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 (
|
|
||||||
<Card className="w-full">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Usuários Cadastrados</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
{activeTab === "today"
|
|
||||||
? `${usersToday.length} usuários cadastrados hoje`
|
|
||||||
: `${allUsers.length} usuários cadastrados no total`}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Tabs defaultValue="today" onValueChange={handleTabChange}>
|
|
||||||
<TabsList className="mb-4">
|
|
||||||
<TabsTrigger value="today">Cadastrados Hoje</TabsTrigger>
|
|
||||||
<TabsTrigger value="all">Todos os Usuários</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsContent value="today" className="space-y-4">
|
|
||||||
{loading ? (
|
|
||||||
<div className="text-center py-4">Carregando...</div>
|
|
||||||
) : usersToday.length > 0 ? (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{usersToday.map((user) => (
|
|
||||||
<div key={user.id} className="border p-4 rounded-md">
|
|
||||||
<div className="font-semibold">{user.nome}</div>
|
|
||||||
<div className="text-sm text-muted-foreground">Email: {user.email}</div>
|
|
||||||
{user.empresa && (
|
|
||||||
<div className="text-sm text-muted-foreground">Empresa: {user.empresa}</div>
|
|
||||||
)}
|
|
||||||
<div className="text-xs text-muted-foreground mt-1">
|
|
||||||
Cadastrado em: {formatDate(user.created_at)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="text-center py-4">Nenhum usuário cadastrado hoje.</div>
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="all" className="space-y-4">
|
|
||||||
{loading ? (
|
|
||||||
<div className="text-center py-4">Carregando...</div>
|
|
||||||
) : allUsers.length > 0 ? (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{allUsers.map((user) => (
|
|
||||||
<div key={user.id} className="border p-4 rounded-md">
|
|
||||||
<div className="font-semibold">{user.nome}</div>
|
|
||||||
<div className="text-sm text-muted-foreground">Email: {user.email}</div>
|
|
||||||
{user.empresa && (
|
|
||||||
<div className="text-sm text-muted-foreground">Empresa: {user.empresa}</div>
|
|
||||||
)}
|
|
||||||
<div className="text-xs text-muted-foreground mt-1">
|
|
||||||
Cadastrado em: {formatDate(user.created_at)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="text-center py-4">Nenhum usuário cadastrado.</div>
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="flex justify-end">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => loadUsers(activeTab)}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{loading ? "Carregando..." : "Atualizar"}
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UsersList;
|
|
||||||
@ -8,20 +8,20 @@ import { RefreshCw } from 'lucide-react';
|
|||||||
interface InstanceListProps {
|
interface InstanceListProps {
|
||||||
instances: WhatsAppInstance[];
|
instances: WhatsAppInstance[];
|
||||||
onViewQrCode: (instance: WhatsAppInstance) => void;
|
onViewQrCode: (instance: WhatsAppInstance) => void;
|
||||||
onDeleteInstance: (instanceId: string) => void; // Only requires instanceId
|
onDelete: (instanceId: string) => void;
|
||||||
onRestartInstance: (instance: WhatsAppInstance) => Promise<void>;
|
onRestart: (instance: WhatsAppInstance) => Promise<void>;
|
||||||
onLogoutInstance: (instance: WhatsAppInstance) => Promise<void>;
|
onLogout: (instance: WhatsAppInstance) => Promise<void>;
|
||||||
onSetPresence: (instance: WhatsAppInstance, presence: 'online' | 'offline') => Promise<void>;
|
onSetPresence: (instance: WhatsAppInstance, presence: 'online' | 'offline') => Promise<void>;
|
||||||
onRefreshInstances?: () => Promise<void>;
|
onRefreshInstances: () => Promise<void>;
|
||||||
isRefreshing?: boolean;
|
isRefreshing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InstanceList = ({
|
const InstanceList = ({
|
||||||
instances,
|
instances,
|
||||||
onViewQrCode,
|
onViewQrCode,
|
||||||
onDeleteInstance,
|
onDelete,
|
||||||
onRestartInstance,
|
onRestart,
|
||||||
onLogoutInstance,
|
onLogout,
|
||||||
onSetPresence,
|
onSetPresence,
|
||||||
onRefreshInstances,
|
onRefreshInstances,
|
||||||
isRefreshing
|
isRefreshing
|
||||||
@ -43,17 +43,15 @@ const InstanceList = ({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h2 className="text-xl font-semibold">Instâncias Criadas</h2>
|
<h2 className="text-xl font-semibold">Instâncias Criadas</h2>
|
||||||
{onRefreshInstances && (
|
<Button
|
||||||
<Button
|
variant="outline"
|
||||||
variant="outline"
|
size="sm"
|
||||||
size="sm"
|
onClick={onRefreshInstances}
|
||||||
onClick={onRefreshInstances}
|
disabled={isRefreshing}
|
||||||
disabled={isRefreshing}
|
>
|
||||||
>
|
<RefreshCw className={`h-4 w-4 mr-2 ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||||
<RefreshCw className={`h-4 w-4 mr-2 ${isRefreshing ? 'animate-spin' : ''}`} />
|
{isRefreshing ? 'Atualizando...' : 'Atualizar Lista'}
|
||||||
{isRefreshing ? 'Atualizando...' : 'Atualizar Lista'}
|
</Button>
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</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) => (
|
||||||
@ -61,9 +59,9 @@ const InstanceList = ({
|
|||||||
key={instance.instanceId}
|
key={instance.instanceId}
|
||||||
instance={instance}
|
instance={instance}
|
||||||
onViewQrCode={onViewQrCode}
|
onViewQrCode={onViewQrCode}
|
||||||
onDelete={onDeleteInstance}
|
onDelete={onDelete}
|
||||||
onRestart={onRestartInstance}
|
onRestart={onRestart}
|
||||||
onLogout={onLogoutInstance}
|
onLogout={onLogout}
|
||||||
onSetPresence={onSetPresence}
|
onSetPresence={onSetPresence}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -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<MessageWebhookProps> = ({ instance }) => {
|
|
||||||
const { toast } = useToast();
|
|
||||||
const [webhookUrl, setWebhookUrl] = useState<string>('');
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Configurar Webhook para Mensagens</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Configure um webhook para receber mensagens da Evolution API.
|
|
||||||
Os áudios enviados para o grupo do Rodrigo serão armazenados automaticamente.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="webhook-url">URL do Webhook</Label>
|
|
||||||
<Input
|
|
||||||
id="webhook-url"
|
|
||||||
placeholder="https://seu-servidor.com/webhook"
|
|
||||||
value={webhookUrl}
|
|
||||||
onChange={(e) => setWebhookUrl(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{instance ? (
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Configurando para: <strong>{instance.instanceName}</strong>
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p className="text-sm text-yellow-600">
|
|
||||||
Selecione uma instância primeiro.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter>
|
|
||||||
<Button
|
|
||||||
onClick={handleSetWebhook}
|
|
||||||
disabled={!instance || !webhookUrl}
|
|
||||||
>
|
|
||||||
Configurar Webhook
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MessageWebhook;
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -10,22 +10,21 @@ import {
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import { RefreshCw } from 'lucide-react';
|
import { RefreshCw } from 'lucide-react';
|
||||||
|
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||||
import { fetchQrCode } from '@/services/whatsAppService';
|
import { fetchQrCode } from '@/services/whatsAppService';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
|
||||||
interface QrCodeDialogProps {
|
interface QrCodeDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
instanceName: string;
|
activeInstance: WhatsAppInstance | null;
|
||||||
phoneNumber: string;
|
onStatusCheck: () => void;
|
||||||
onStatusCheck?: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QrCodeDialog = ({
|
const QrCodeDialog = ({
|
||||||
open,
|
open,
|
||||||
setOpen,
|
onOpenChange,
|
||||||
instanceName,
|
activeInstance,
|
||||||
phoneNumber,
|
|
||||||
onStatusCheck
|
onStatusCheck
|
||||||
}: QrCodeDialogProps) => {
|
}: QrCodeDialogProps) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@ -33,30 +32,23 @@ const QrCodeDialog = ({
|
|||||||
const [qrCodeData, setQrCodeData] = useState<string | null>(null);
|
const [qrCodeData, setQrCodeData] = useState<string | null>(null);
|
||||||
const [qrError, setQrError] = useState<string | null>(null);
|
const [qrError, setQrError] = useState<string | null>(null);
|
||||||
|
|
||||||
// Load QR code when dialog opens
|
|
||||||
useEffect(() => {
|
|
||||||
if (open && instanceName) {
|
|
||||||
handleRefreshQrCode();
|
|
||||||
}
|
|
||||||
}, [open, instanceName]);
|
|
||||||
|
|
||||||
const handleOpenChange = (newOpen: boolean) => {
|
const handleOpenChange = (newOpen: boolean) => {
|
||||||
setOpen(newOpen);
|
onOpenChange(newOpen);
|
||||||
|
|
||||||
// After dialog closes, trigger status check to update connection state
|
// After dialog closes, trigger status check to update connection state
|
||||||
if (!newOpen && onStatusCheck) {
|
if (!newOpen) {
|
||||||
onStatusCheck();
|
onStatusCheck();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefreshQrCode = async () => {
|
const handleRefreshQrCode = async () => {
|
||||||
if (!instanceName) return;
|
if (!activeInstance) return;
|
||||||
|
|
||||||
setLoadingQR(true);
|
setLoadingQR(true);
|
||||||
setQrError(null);
|
setQrError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await fetchQrCode(instanceName);
|
const data = await fetchQrCode(activeInstance.instanceName);
|
||||||
console.log('QR Code API response:', data);
|
console.log('QR Code API response:', data);
|
||||||
|
|
||||||
// Using the "base64" field from the response as the QR code data
|
// Using the "base64" field from the response as the QR code data
|
||||||
@ -83,7 +75,7 @@ const QrCodeDialog = ({
|
|||||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
<DialogContent className="sm:max-w-md">
|
<DialogContent className="sm:max-w-md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Conectar WhatsApp - {instanceName}</DialogTitle>
|
<DialogTitle>Conectar WhatsApp - {activeInstance?.instanceName}</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Escaneie o QR Code com seu WhatsApp para finalizar a conexão
|
Escaneie o QR Code com seu WhatsApp para finalizar a conexão
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
@ -117,7 +109,7 @@ const QrCodeDialog = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
{instanceName && (
|
{activeInstance && (
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleRefreshQrCode}
|
onClick={handleRefreshQrCode}
|
||||||
@ -128,7 +120,7 @@ const QrCodeDialog = ({
|
|||||||
Atualizar QR Code
|
Atualizar QR Code
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button variant="ghost" onClick={() => setOpen(false)}>
|
<Button variant="ghost" onClick={() => onOpenChange(false)}>
|
||||||
Fechar
|
Fechar
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -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';
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
|
|
||||||
export const siteConfig = {
|
|
||||||
name: "WhatsApp Dashboard",
|
|
||||||
description: "Gerenciamento de instâncias de WhatsApp",
|
|
||||||
mainNav: [],
|
|
||||||
links: {
|
|
||||||
github: "https://github.com/",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -1,3 +1,186 @@
|
|||||||
|
|
||||||
// Re-export from the refactored location
|
import { useState } from 'react';
|
||||||
export { useWhatsAppActions } from './whatsapp/useWhatsAppActions';
|
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<void>
|
||||||
|
) => {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const [activeInstance, setActiveInstance] = useState<WhatsAppInstance | null>(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
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -1,3 +1,249 @@
|
|||||||
|
|
||||||
// Re-export from the refactored location
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
export { useWhatsAppInstances } from './whatsapp/useWhatsAppInstances';
|
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<WhatsAppInstance[]>([]);
|
||||||
|
const [currentUserId, setCurrentUserId] = useState<string>('');
|
||||||
|
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
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@ -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<WhatsAppInstance> => {
|
|
||||||
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'
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -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<void>;
|
|
||||||
checkAllInstancesStatus: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WhatsAppInstancesHookReturn = WhatsAppInstancesState & WhatsAppInstancesActions;
|
|
||||||
@ -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<void>
|
|
||||||
) => {
|
|
||||||
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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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<WhatsAppInstance | null>(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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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<void>
|
|
||||||
) => {
|
|
||||||
// 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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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<WhatsAppInstance[]>([]);
|
|
||||||
const [currentUserId, setCurrentUserId] = useState<string>('');
|
|
||||||
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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -147,42 +147,6 @@ export type Database = {
|
|||||||
}
|
}
|
||||||
Relationships: []
|
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: {
|
transacoes: {
|
||||||
Row: {
|
Row: {
|
||||||
categoria: string | null
|
categoria: string | null
|
||||||
@ -190,7 +154,6 @@ export type Database = {
|
|||||||
detalhes: string | null
|
detalhes: string | null
|
||||||
estabelecimento: string | null
|
estabelecimento: string | null
|
||||||
id: number
|
id: number
|
||||||
login: string | null
|
|
||||||
quando: string | null
|
quando: string | null
|
||||||
tipo: string | null
|
tipo: string | null
|
||||||
user: string | null
|
user: string | null
|
||||||
@ -202,7 +165,6 @@ export type Database = {
|
|||||||
detalhes?: string | null
|
detalhes?: string | null
|
||||||
estabelecimento?: string | null
|
estabelecimento?: string | null
|
||||||
id?: number
|
id?: number
|
||||||
login?: string | null
|
|
||||||
quando?: string | null
|
quando?: string | null
|
||||||
tipo?: string | null
|
tipo?: string | null
|
||||||
user?: string | null
|
user?: string | null
|
||||||
@ -214,7 +176,6 @@ export type Database = {
|
|||||||
detalhes?: string | null
|
detalhes?: string | null
|
||||||
estabelecimento?: string | null
|
estabelecimento?: string | null
|
||||||
id?: number
|
id?: number
|
||||||
login?: string | null
|
|
||||||
quando?: string | null
|
quando?: string | null
|
||||||
tipo?: string | null
|
tipo?: string | null
|
||||||
user?: string | null
|
user?: string | 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<AudioMessage[]>([]);
|
|
||||||
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 (
|
|
||||||
<Layout>
|
|
||||||
<div className="container mx-auto p-4">
|
|
||||||
<div className="flex justify-between items-center mb-6">
|
|
||||||
<h1 className="text-2xl font-bold">Áudios do Rodrigo</h1>
|
|
||||||
<Button
|
|
||||||
onClick={fetchAudioMessages}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
{isLoading ? 'Carregando...' : 'Atualizar'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{audioMessages.length === 0 ? (
|
|
||||||
<Card>
|
|
||||||
<CardContent className="py-6">
|
|
||||||
<div className="text-center text-muted-foreground">
|
|
||||||
<p>Nenhuma mensagem de áudio encontrada para Rodrigo.</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-4">
|
|
||||||
{audioMessages.map((message) => (
|
|
||||||
<Card key={message.id}>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-lg">
|
|
||||||
{message.sender_name || 'Usuário Desconhecido'}
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Data: {message.created_at ? format(new Date(message.created_at), 'dd/MM/yyyy HH:mm') : 'N/A'}
|
|
||||||
</p>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
Duração: {message.duration ? `${message.duration}s` : 'N/A'}
|
|
||||||
</p>
|
|
||||||
{message.audio_url && (
|
|
||||||
<audio controls className="w-full">
|
|
||||||
<source src={message.audio_url} type="audio/mpeg" />
|
|
||||||
Seu navegador não suporta áudio
|
|
||||||
</audio>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RodrigoAudio;
|
|
||||||
@ -1,27 +1,25 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import Layout from '@/components/layout/Layout';
|
import Layout from '@/components/layout/Layout';
|
||||||
import {
|
import { WhatsAppInstance } from '@/types/whatsAppTypes';
|
||||||
CreateInstanceForm,
|
import CreateInstanceForm from '@/components/whatsapp/CreateInstanceForm';
|
||||||
InstanceList,
|
import InstanceList from '@/components/whatsapp/InstanceList';
|
||||||
InstanceStats,
|
import InstanceStats from '@/components/whatsapp/InstanceStats';
|
||||||
QrCodeDialog
|
import QrCodeDialog from '@/components/whatsapp/QrCodeDialog';
|
||||||
} from '@/components/whatsapp';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import UsersDialog from '@/components/usuarios/UsersDialog';
|
|
||||||
import { useWhatsAppInstances } from '@/hooks/useWhatsAppInstances';
|
import { useWhatsAppInstances } from '@/hooks/useWhatsAppInstances';
|
||||||
import { useWhatsAppActions } from '@/hooks/useWhatsAppActions';
|
import { useWhatsAppActions } from '@/hooks/useWhatsAppActions';
|
||||||
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
|
||||||
const WhatsApp = () => {
|
const WhatsApp = () => {
|
||||||
|
const { toast } = useToast();
|
||||||
const {
|
const {
|
||||||
instances,
|
instances,
|
||||||
isRefreshing,
|
isRefreshing,
|
||||||
currentUserId,
|
|
||||||
addInstance,
|
addInstance,
|
||||||
removeInstance,
|
removeInstance,
|
||||||
updateInstance,
|
updateInstance,
|
||||||
refreshInstances,
|
refreshInstances,
|
||||||
checkAllInstancesStatus
|
checkAllInstancesStatus
|
||||||
} = useWhatsAppInstances();
|
} = useWhatsAppInstances();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -35,85 +33,119 @@ const WhatsApp = () => {
|
|||||||
handleViewQrCode
|
handleViewQrCode
|
||||||
} = useWhatsAppActions(updateInstance, removeInstance, checkAllInstancesStatus);
|
} = useWhatsAppActions(updateInstance, removeInstance, checkAllInstancesStatus);
|
||||||
|
|
||||||
// Poll for status updates every 30 seconds
|
// Set up periodic status checks
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let interval: ReturnType<typeof setInterval> | null = null;
|
console.log("Setting up periodic status checks, current instances:", instances.length);
|
||||||
|
|
||||||
const startPolling = () => {
|
// Check status initially
|
||||||
interval = setInterval(() => {
|
if (instances.length > 0) {
|
||||||
console.log("Polling for WhatsApp instance status updates");
|
checkAllInstancesStatus();
|
||||||
checkAllInstancesStatus();
|
}
|
||||||
}, 30000); // Check every 30 seconds
|
|
||||||
};
|
|
||||||
|
|
||||||
// Start polling
|
// Set up interval for periodic checks (every 30 seconds)
|
||||||
startPolling();
|
const interval = setInterval(() => {
|
||||||
|
if (instances.length > 0) {
|
||||||
// Cleanup on unmount
|
console.log("Running periodic status check");
|
||||||
|
checkAllInstancesStatus();
|
||||||
|
}
|
||||||
|
}, 30000); // 30 seconds
|
||||||
|
|
||||||
|
// Clean up interval when component unmounts
|
||||||
return () => {
|
return () => {
|
||||||
if (interval) {
|
if (interval) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [checkAllInstancesStatus]);
|
}, [instances.length, checkAllInstancesStatus]);
|
||||||
|
|
||||||
// Create a wrapper function that matches the expected signature
|
// Run refresh instances on initial load to get server instances
|
||||||
const handleDeleteInstanceWrapper = (instanceId: string) => {
|
useEffect(() => {
|
||||||
const instance = instances.find(inst => inst.instanceId === instanceId);
|
const initialLoad = async () => {
|
||||||
if (instance) {
|
if (instances.length === 0) {
|
||||||
handleDeleteInstance(instanceId, instance.instanceName);
|
try {
|
||||||
} else {
|
await refreshInstances();
|
||||||
console.error(`Instance with ID ${instanceId} not found`);
|
} 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 (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<div className="container mx-auto p-4">
|
<div className="space-y-6">
|
||||||
<div className="flex flex-col md:flex-row justify-between items-center mb-6">
|
<div className="flex justify-between items-center">
|
||||||
<h1 className="text-2xl font-bold mb-4 md:mb-0">WhatsApp Instances</h1>
|
<h1 className="text-2xl font-bold tracking-tight">Conectar WhatsApp</h1>
|
||||||
<div className="flex gap-2 items-center">
|
|
||||||
<UsersDialog />
|
|
||||||
<Button
|
|
||||||
onClick={refreshInstances}
|
|
||||||
disabled={isRefreshing}
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
{isRefreshing ? 'Atualizando...' : 'Atualizar Instâncias'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
{/* Form to create a new instance */}
|
||||||
<div className="md:col-span-2">
|
<CreateInstanceForm onInstanceCreated={handleInstanceCreated} />
|
||||||
<CreateInstanceForm onInstanceCreated={addInstance} />
|
|
||||||
<div className="mt-8">
|
{/* Stats component */}
|
||||||
<InstanceList
|
<InstanceStats instances={instances} />
|
||||||
instances={instances}
|
|
||||||
onRestartInstance={handleRestartInstance}
|
{/* List of created instances */}
|
||||||
onLogoutInstance={handleLogoutInstance}
|
<InstanceList
|
||||||
onSetPresence={handleSetPresence}
|
instances={instances}
|
||||||
onDeleteInstance={handleDeleteInstanceWrapper}
|
onViewQrCode={handleViewQrCode}
|
||||||
onViewQrCode={handleViewQrCode}
|
onDelete={handleDeleteInstanceWrapper}
|
||||||
onRefreshInstances={refreshInstances}
|
onRestart={handleRestartInstance}
|
||||||
isRefreshing={isRefreshing}
|
onLogout={handleLogoutInstance}
|
||||||
/>
|
onSetPresence={handleSetPresence}
|
||||||
</div>
|
onRefreshInstances={refreshInstances}
|
||||||
</div>
|
isRefreshing={isRefreshing}
|
||||||
<div>
|
/>
|
||||||
<InstanceStats instances={instances} />
|
|
||||||
</div>
|
{/* QR Code Dialog */}
|
||||||
</div>
|
<QrCodeDialog
|
||||||
|
open={qrDialogOpen}
|
||||||
{activeInstance && (
|
onOpenChange={setQrDialogOpen}
|
||||||
<QrCodeDialog
|
activeInstance={activeInstance}
|
||||||
open={qrDialogOpen}
|
onStatusCheck={checkAllInstancesStatus}
|
||||||
setOpen={setQrDialogOpen}
|
/>
|
||||||
instanceName={activeInstance.instanceName}
|
|
||||||
phoneNumber={activeInstance.phoneNumber}
|
|
||||||
onStatusCheck={checkAllInstancesStatus}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<boolean> => {
|
|
||||||
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<AudioMessage[]> => {
|
|
||||||
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<boolean> => {
|
|
||||||
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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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<UserInfo[]> => {
|
|
||||||
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<UserInfo[]> => {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -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<boolean> => {
|
|
||||||
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<boolean> => {
|
|
||||||
// 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<boolean> => {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
|
|
||||||
export interface MainNavItem {
|
|
||||||
title: string;
|
|
||||||
href?: string;
|
|
||||||
icon?: ReactNode;
|
|
||||||
disabled?: boolean;
|
|
||||||
external?: boolean;
|
|
||||||
}
|
|
||||||
@ -1,6 +1 @@
|
|||||||
|
project_id = "tnurlgbvfsxwqgwxamni"
|
||||||
project_id = "tnurlgbvfsxwqgwxamni"
|
|
||||||
|
|
||||||
# Enable webhook edge function without JWT verification
|
|
||||||
[functions.whatsapp-webhook]
|
|
||||||
verify_jwt = false
|
|
||||||
@ -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<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<boolean> {
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Loading…
Reference in New Issue
Block a user