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:
parent
b8d0db98bb
commit
6100374e56
8
package-lock.json
generated
8
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
45
src/components/usuarios/UsersDialog.tsx
Normal file
45
src/components/usuarios/UsersDialog.tsx
Normal 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;
|
||||
143
src/components/usuarios/UsersList.tsx
Normal file
143
src/components/usuarios/UsersList.tsx
Normal 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;
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
62
src/services/userService.ts
Normal file
62
src/services/userService.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user