Add user filtering to database queries

Implement user-based filtering in database queries to retrieve data associated with specific users. Determine the users registered today.
This commit is contained in:
gpt-engineer-app[bot] 2025-05-19 18:22:18 +00:00
parent b8d0db98bb
commit 6100374e56
7 changed files with 324 additions and 116 deletions

8
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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 (
<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;

View File

@ -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<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;

View File

@ -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

View File

@ -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<typeof setInterval> | 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 (
<Layout>
<div className="space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold tracking-tight">Conectar WhatsApp</h1>
<div className="container mx-auto p-4">
<div className="flex flex-col md:flex-row justify-between items-center mb-6">
<h1 className="text-2xl font-bold mb-4 md:mb-0">WhatsApp Instances</h1>
<div className="flex gap-2 items-center">
<UsersDialog />
<Button
onClick={refreshInstances}
disabled={isRefreshing}
variant="outline"
>
{isRefreshing ? 'Atualizando...' : 'Atualizar Instâncias'}
</Button>
</div>
</div>
{/* Form to create a new instance */}
<CreateInstanceForm onInstanceCreated={handleInstanceCreated} />
{/* Stats component */}
<InstanceStats instances={instances} />
{/* List of created instances */}
<InstanceList
instances={instances}
onViewQrCode={handleViewQrCode}
onDelete={handleDeleteInstanceWrapper}
onRestart={handleRestartInstance}
onLogout={handleLogoutInstance}
onSetPresence={handleSetPresence}
onRefreshInstances={refreshInstances}
isRefreshing={isRefreshing}
/>
{/* QR Code Dialog */}
<QrCodeDialog
open={qrDialogOpen}
onOpenChange={setQrDialogOpen}
activeInstance={activeInstance}
onStatusCheck={checkAllInstancesStatus}
/>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="md:col-span-2">
<CreateInstanceForm userId={currentUserId} onInstanceCreated={addInstance} />
<div className="mt-8">
<InstanceList
instances={instances}
onRestartInstance={handleRestartInstance}
onLogoutInstance={handleLogoutInstance}
onSetPresence={handleSetPresence}
onDeleteInstance={handleDeleteInstance}
onViewQrCode={handleViewQrCode}
/>
</div>
</div>
<div>
<InstanceStats instances={instances} />
</div>
</div>
{activeInstance && (
<QrCodeDialog
open={qrDialogOpen}
setOpen={setQrDialogOpen}
instanceName={activeInstance.instanceName}
phoneNumber={activeInstance.phoneNumber}
/>
)}
</div>
</Layout>
);

View File

@ -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<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;
}
};