diff --git a/package-lock.json b/package-lock.json index cdeff6e..6bdb41c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", - "date-fns": "^3.6.0", + "date-fns": "^4.1.0", "embla-carousel-react": "^8.3.0", "input-otp": "^1.2.4", "lucide-react": "^0.462.0", @@ -4196,9 +4196,9 @@ } }, "node_modules/date-fns": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", - "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", "license": "MIT", "funding": { "type": "github", diff --git a/package.json b/package.json index f8810fa..919c784 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", - "date-fns": "^3.6.0", + "date-fns": "^4.1.0", "embla-carousel-react": "^8.3.0", "input-otp": "^1.2.4", "lucide-react": "^0.462.0", diff --git a/src/components/usuarios/UsersDialog.tsx b/src/components/usuarios/UsersDialog.tsx new file mode 100644 index 0000000..99422a0 --- /dev/null +++ b/src/components/usuarios/UsersDialog.tsx @@ -0,0 +1,45 @@ + +import React from "react"; +import UsersList from "./UsersList"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { UserRoundSearch } from "lucide-react"; + +interface UsersDialogProps { + trigger?: React.ReactNode; +} + +export function UsersDialog({ trigger }: UsersDialogProps) { + return ( + + + {trigger || ( + + )} + + + + Gerenciamento de Usuários + + Visualize e gerencie os usuários cadastrados na plataforma + + +
+ +
+
+
+ ); +} + +export default UsersDialog; diff --git a/src/components/usuarios/UsersList.tsx b/src/components/usuarios/UsersList.tsx new file mode 100644 index 0000000..a091e3c --- /dev/null +++ b/src/components/usuarios/UsersList.tsx @@ -0,0 +1,143 @@ + +import React, { useState, useEffect } from 'react'; +import { format } from 'date-fns'; +import { ptBR } from 'date-fns/locale'; +import { UserInfo, getUsersRegisteredToday, getAllUsers } from '@/services/userService'; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { useToast } from "@/hooks/use-toast"; + +const UsersList: React.FC = () => { + const [usersToday, setUsersToday] = useState([]); + const [allUsers, setAllUsers] = useState([]); + const [loading, setLoading] = useState(false); + const [activeTab, setActiveTab] = useState("today"); + const { toast } = useToast(); + + const loadUsers = async (tab: string = activeTab) => { + setLoading(true); + try { + if (tab === "today") { + const users = await getUsersRegisteredToday(); + setUsersToday(users); + } else { + const users = await getAllUsers(); + setAllUsers(users); + } + } catch (error) { + console.error("Erro ao carregar usuários:", error); + toast({ + title: "Erro", + description: "Falha ao carregar dados de usuários.", + variant: "destructive", + }); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + loadUsers(); + }, []); + + const handleTabChange = (value: string) => { + setActiveTab(value); + loadUsers(value); + }; + + const formatDate = (dateString: string) => { + try { + return format(new Date(dateString), "dd/MM/yyyy HH:mm", { locale: ptBR }); + } catch (error) { + return "Data inválida"; + } + }; + + const displayUsers = activeTab === "today" ? usersToday : allUsers; + + return ( + + + Usuários Cadastrados + + {activeTab === "today" + ? `${usersToday.length} usuários cadastrados hoje` + : `${allUsers.length} usuários cadastrados no total`} + + + + + + Cadastrados Hoje + Todos os Usuários + + + + {loading ? ( +
Carregando...
+ ) : usersToday.length > 0 ? ( +
+ {usersToday.map((user) => ( +
+
{user.nome}
+
Email: {user.email}
+ {user.empresa && ( +
Empresa: {user.empresa}
+ )} +
+ Cadastrado em: {formatDate(user.created_at)} +
+
+ ))} +
+ ) : ( +
Nenhum usuário cadastrado hoje.
+ )} +
+ + + {loading ? ( +
Carregando...
+ ) : allUsers.length > 0 ? ( +
+ {allUsers.map((user) => ( +
+
{user.nome}
+
Email: {user.email}
+ {user.empresa && ( +
Empresa: {user.empresa}
+ )} +
+ Cadastrado em: {formatDate(user.created_at)} +
+
+ ))} +
+ ) : ( +
Nenhum usuário cadastrado.
+ )} +
+
+
+ + + +
+ ); +}; + +export default UsersList; diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index 69116ba..a7f0865 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -154,6 +154,7 @@ export type Database = { detalhes: string | null estabelecimento: string | null id: number + login: string | null quando: string | null tipo: string | null user: string | null @@ -165,6 +166,7 @@ export type Database = { detalhes?: string | null estabelecimento?: string | null id?: number + login?: string | null quando?: string | null tipo?: string | null user?: string | null @@ -176,6 +178,7 @@ export type Database = { detalhes?: string | null estabelecimento?: string | null id?: number + login?: string | null quando?: string | null tipo?: string | null user?: string | null diff --git a/src/pages/WhatsApp.tsx b/src/pages/WhatsApp.tsx index 383189f..68fd6a5 100644 --- a/src/pages/WhatsApp.tsx +++ b/src/pages/WhatsApp.tsx @@ -1,25 +1,27 @@ -import { useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import Layout from '@/components/layout/Layout'; -import { WhatsAppInstance } from '@/types/whatsAppTypes'; -import CreateInstanceForm from '@/components/whatsapp/CreateInstanceForm'; -import InstanceList from '@/components/whatsapp/InstanceList'; -import InstanceStats from '@/components/whatsapp/InstanceStats'; -import QrCodeDialog from '@/components/whatsapp/QrCodeDialog'; +import { + CreateInstanceForm, + InstanceList, + InstanceStats, + QrCodeDialog +} from '@/components/whatsapp'; +import { Button } from '@/components/ui/button'; +import UsersDialog from '@/components/usuarios/UsersDialog'; import { useWhatsAppInstances } from '@/hooks/useWhatsAppInstances'; import { useWhatsAppActions } from '@/hooks/useWhatsAppActions'; -import { useToast } from '@/hooks/use-toast'; const WhatsApp = () => { - const { toast } = useToast(); const { instances, - isRefreshing, + isRefreshing, + currentUserId, addInstance, removeInstance, updateInstance, - refreshInstances, - checkAllInstancesStatus + refreshInstances, + checkAllInstancesStatus } = useWhatsAppInstances(); const { @@ -33,119 +35,72 @@ const WhatsApp = () => { handleViewQrCode } = useWhatsAppActions(updateInstance, removeInstance, checkAllInstancesStatus); - // Set up periodic status checks + // Poll for status updates every 30 seconds useEffect(() => { - console.log("Setting up periodic status checks, current instances:", instances.length); + let interval: ReturnType | null = null; - // Check status initially - if (instances.length > 0) { - checkAllInstancesStatus(); - } - - // Set up interval for periodic checks (every 30 seconds) - const interval = setInterval(() => { - if (instances.length > 0) { - console.log("Running periodic status check"); + const startPolling = () => { + interval = setInterval(() => { + console.log("Polling for WhatsApp instance status updates"); checkAllInstancesStatus(); - } - }, 30000); // 30 seconds + }, 30000); // Check every 30 seconds + }; - // Clean up interval when component unmounts + // Start polling + startPolling(); + + // Cleanup on unmount return () => { if (interval) { clearInterval(interval); } }; - }, [instances.length, checkAllInstancesStatus]); - - // Run refresh instances on initial load to get server instances - useEffect(() => { - const initialLoad = async () => { - if (instances.length === 0) { - try { - await refreshInstances(); - } catch (error) { - console.error("Error on initial instance refresh:", error); - } - } - }; - - initialLoad(); - }, []); - - // Handler for when a new instance is created - const handleInstanceCreated = async (newInstance: WhatsAppInstance) => { - console.log('New instance to be added:', newInstance); - addInstance(newInstance); - - // If there's a QR code in the response, show it - if (newInstance.qrcode) { - handleViewQrCode(newInstance); - } - - // Trigger a status check for all instances - try { - await checkAllInstancesStatus(); - } catch (error) { - console.error("Error checking status after instance creation:", error); - toast({ - title: "Aviso", - description: "Instância criada, mas não foi possível verificar o status. Tente atualizar a lista manualmente.", - variant: "default", - }); - } - }; - - // Handler for when an instance is deleted - const handleDeleteInstanceWrapper = (instanceId: string) => { - console.log(`Instance deletion requested for ID: ${instanceId}`); - const instanceToDelete = instances.find(i => i.instanceId === instanceId); - if (instanceToDelete) { - handleDeleteInstance(instanceId, instanceToDelete.instanceName); - } else { - console.error(`Instance with ID ${instanceId} not found for deletion`); - toast({ - title: "Erro", - description: "Instância não encontrada para exclusão", - variant: "destructive", - }); - } - }; - - console.log('Current instances in WhatsApp component:', instances); + }, [checkAllInstancesStatus]); return ( -
-
-

Conectar WhatsApp

+
+
+

WhatsApp Instances

+
+ + +
- - {/* Form to create a new instance */} - - - {/* Stats component */} - - - {/* List of created instances */} - - - {/* QR Code Dialog */} - + +
+
+ +
+ +
+
+
+ +
+
+ + {activeInstance && ( + + )}
); diff --git a/src/services/userService.ts b/src/services/userService.ts new file mode 100644 index 0000000..986f228 --- /dev/null +++ b/src/services/userService.ts @@ -0,0 +1,62 @@ + +import { supabase } from "@/integrations/supabase/client"; +import { format } from "date-fns"; + +export interface UserInfo { + id: string; + nome: string; + email: string; + empresa: string | null; + created_at: string; +} + +export const getUsersRegisteredToday = async (): Promise => { + try { + console.log("Buscando usuários cadastrados hoje..."); + // Obtém a data atual no formato YYYY-MM-DD + const today = format(new Date(), 'yyyy-MM-dd'); + + // Busca usuários cadastrados hoje + const { data, error } = await supabase + .from("usuarios") + .select("*") + .gte('created_at', `${today}T00:00:00`) // Data início (hoje 00:00:00) + .lte('created_at', `${today}T23:59:59`) // Data fim (hoje 23:59:59) + .order('created_at', { ascending: false }); + + if (error) { + console.error("Erro ao buscar usuários:", error); + throw error; + } + + console.log(`Encontrados ${data?.length || 0} usuários cadastrados hoje:`, data); + + return data as UserInfo[]; + } catch (error) { + console.error("Erro ao buscar usuários cadastrados hoje:", error); + throw error; + } +}; + +export const getAllUsers = async (): Promise => { + try { + console.log("Buscando todos os usuários..."); + + const { data, error } = await supabase + .from("usuarios") + .select("*") + .order('created_at', { ascending: false }); + + if (error) { + console.error("Erro ao buscar usuários:", error); + throw error; + } + + console.log(`Encontrados ${data?.length || 0} usuários no total`); + + return data as UserInfo[]; + } catch (error) { + console.error("Erro ao buscar todos os usuários:", error); + throw error; + } +};