diff --git a/app/manager/usuario/novo/page.tsx b/app/manager/usuario/novo/page.tsx index 781f35d..1eb7b28 100644 --- a/app/manager/usuario/novo/page.tsx +++ b/app/manager/usuario/novo/page.tsx @@ -3,54 +3,51 @@ import { useState } from "react" import { useRouter } from "next/navigation" import Link from "next/link" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Button } from "components/ui/button" +import { Input } from "components/ui/input" +import { Label } from "components/ui/label" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "components/ui/select" import { Save, Loader2 } from "lucide-react" -import ManagerLayout from "@/components/manager-layout" +import ManagerLayout from "components/manager-layout" +import { usersService } from "services/usersApi.mjs"; -// Mock user service for demonstration. Replace with your actual API service. -const usersService = { - create: async (payload: any) => { - console.log("API Call: Creating user with payload:", payload); - // Simulate network delay - await new Promise(resolve => setTimeout(resolve, 1000)); - // Simulate a success response - return { id: Date.now(), ...payload }; - // To simulate an error, you could throw an error here: - // throw new Error("O e-mail informado já está em uso."); - } -}; -// Define the structure for our form data interface UserFormData { email: string; password: string; nomeCompleto: string; telefone: string; - papel: string; // e.g., 'admin', 'gestor', 'medico', etc. + cargo: string; } -// Define the initial state for the form + const defaultFormData: UserFormData = { email: '', password: '', nomeCompleto: '', telefone: '', - papel: '', + cargo: '', }; -// Helper function to remove non-digit characters const cleanNumber = (value: string): string => value.replace(/\D/g, ''); -// Helper function to format a phone number + + const formatPhone = (value: string): string => { - const cleaned = cleanNumber(value).substring(0, 11); - if (cleaned.length > 10) { - return cleaned.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3'); - } - return cleaned.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3'); + const cleaned = cleanNumber(value).substring(0, 11); + + + if (cleaned.length === 11) { + return cleaned.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3'); + } + + + if (cleaned.length === 10) { + return cleaned.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3'); + } + + + return cleaned; }; export default function NovoUsuarioPage() { @@ -59,7 +56,7 @@ export default function NovoUsuarioPage() { const [isSaving, setIsSaving] = useState(false); const [error, setError] = useState(null); - // Handles changes in form inputs + const handleInputChange = (key: keyof UserFormData, value: string) => { const updatedValue = key === 'telefone' ? formatPhone(value) : value; setFormData((prev) => ({ ...prev, [key]: updatedValue })); @@ -71,7 +68,7 @@ export default function NovoUsuarioPage() { setError(null); // Basic validation - if (!formData.email || !formData.password || !formData.nomeCompleto || !formData.papel) { + if (!formData.email || !formData.password || !formData.nomeCompleto || !formData.cargo) { setError("Por favor, preencha todos os campos obrigatórios."); return; } @@ -83,13 +80,12 @@ export default function NovoUsuarioPage() { email: formData.email, password: formData.password, full_name: formData.nomeCompleto, - phone: formData.telefone.trim() || null, // Send null if empty - role: formData.papel, + phone: formData.telefone.trim() || null, + role: formData.cargo, }; try { - await usersService.create(payload); - // On success, redirect to the main user list page + await usersService.create_user(payload); router.push("/manager/usuario"); } catch (e: any) { console.error("Erro ao criar usuário:", e); @@ -174,7 +170,7 @@ export default function NovoUsuarioPage() {
- handleInputChange("cargo", v)} required> diff --git a/app/manager/usuario/page.tsx b/app/manager/usuario/page.tsx index 00c4836..12a4cc4 100644 --- a/app/manager/usuario/page.tsx +++ b/app/manager/usuario/page.tsx @@ -1,12 +1,12 @@ "use client"; -import React, { useEffect, useState, useCallback } from "react" +import React, { useEffect, useState, useCallback } from "react"; import ManagerLayout from "@/components/manager-layout"; -import Link from "next/link" +import Link from "next/link"; import { useRouter } from "next/navigation"; -import { Button } from "@/components/ui/button" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Plus, Edit, Trash2, Eye, Filter, Loader2 } from "lucide-react" +import { Button } from "@/components/ui/button"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Plus, Edit, Trash2, Eye, Filter, Loader2 } from "lucide-react"; import { AlertDialog, AlertDialogAction, @@ -16,57 +16,79 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog" +} from "@/components/ui/alert-dialog"; -// Mock user service for demonstration. Replace with your actual API service. -const usersService = { - list: async (): Promise => { - console.log("API Call: Fetching users..."); - await new Promise(resolve => setTimeout(resolve, 500)); // Simulate network delay - return [ - { id: 1, full_name: 'Alice Admin', email: 'alice.admin@example.com', phone: '(11) 98765-4321', role: 'user' }, +import { usersService } from "services/usersApi.mjs"; - ]; - }, - delete: async (id: number): Promise => { - console.log(`API Call: Deleting user with ID ${id}`); - await new Promise(resolve => setTimeout(resolve, 700)); - // In a real app, you'd handle potential errors here - } -}; - -// Interface for a User object interface User { - id: number; - full_name: string; + user: { + id: string; email: string; - phone: string | null; - role: 'admin' | 'gestor' | 'medico' | 'secretaria' | 'user'; + email_confirmed_at?: string; + created_at?: string; + last_sign_in_at?: string; + }; + profile: { + id?: string; + full_name?: string; + email?: string; + phone?: string | null; + avatar_url?: string; + disabled?: boolean; + created_at?: string; + updated_at?: string; + }; + roles: string[]; + permissions: { + isAdmin?: boolean; + isManager?: boolean; + isDoctor?: boolean; + isSecretary?: boolean; + isAdminOrManager?: boolean; + [key: string]: boolean | undefined; + }; +} + + +interface FlatUser { + id: string; + full_name?: string; + email: string; + phone?: string | null; + role: string; } -// Interface for User Details (can be the same as User for this case) -interface UserDetails extends User {} export default function UsersPage() { const router = useRouter(); - const [users, setUsers] = useState([]); + + const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); - const [userDetails, setUserDetails] = useState(null); + const [userDetails, setUserDetails] = useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [userToDeleteId, setUserToDeleteId] = useState(null); - + const [selectedRole, setSelectedRole] = useState(""); + const fetchUsers = useCallback(async () => { setLoading(true); setError(null); try { - const data: User[] = await usersService.list(); - setUsers(data || []); - } catch (e: any) { - console.error("Erro ao carregar lista de usuários:", e); - setError("Não foi possível carregar a lista de usuários. Tente novamente."); + + const { data, error } = await usersService.list_roles(); + if (error) throw error; + + if (Array.isArray(data)) { + setUsers(data as FlatUser[]); + } else { + console.warn("Formato inesperado recebido:", data); + setUsers([]); + } + } catch (err: any) { + console.error("Erro ao buscar usuários:", err); + setError("Não foi possível carregar os usuários. Tente novamente."); setUsers([]); } finally { setLoading(false); @@ -77,169 +99,197 @@ export default function UsersPage() { fetchUsers(); }, [fetchUsers]); - const openDetailsDialog = (user: User) => { - setUserDetails(user); + + const openDetailsDialog = async (flatUser: FlatUser) => { setDetailsDialogOpen(true); - }; - - const handleDelete = async () => { - if (userToDeleteId === null) return; - - setLoading(true); + setUserDetails(null); + try { - await usersService.delete(userToDeleteId); - console.log(`Usuário com ID ${userToDeleteId} excluído com sucesso!`); - setDeleteDialogOpen(false); - setUserToDeleteId(null); - await fetchUsers(); // Refresh the list after deletion - } catch (e) { - console.error("Erro ao excluir:", e); - alert("Erro ao excluir usuário."); - } finally { - setLoading(false); + + const fullUserData: User = await usersService.full_data(flatUser.id); + + + setUserDetails(fullUserData); + + } catch (err: any) { + console.error("Erro ao buscar detalhes do usuário:", err); + + setUserDetails({ + user: { + id: flatUser.id, + email: flatUser.email || "", + created_at: "Erro ao Carregar", + last_sign_in_at: "Erro ao Carregar", + }, + profile: { + full_name: "Erro ao Carregar Detalhes", + phone: "—", + }, + roles: [], + permissions: {}, + } as any); } }; - const openDeleteDialog = (userId: number) => { - setUserToDeleteId(userId); - setDeleteDialogOpen(true); - }; - - const handleEdit = (userId: number) => { - // Assuming the edit page is at a similar path - router.push(`/manager/usuario/${userId}/editar`); - }; + + + const filteredUsers = selectedRole + ? users.filter((u) => u.role === selectedRole) + : users; return ( -
-
-
-

Usuários Cadastrados

-

Gerencie todos os usuários do sistema.

-
- - - -
- - {/* Filters Section */} -
- - -
- - {/* Users Table */} -
- {loading ? ( -
- - Carregando usuários... -
- ) : error ? ( -
- {error} -
- ) : users.length === 0 ? ( -
- Nenhum usuário cadastrado. Adicione um novo. -
- ) : ( -
- - - - - - - - - - - - {users.map((user) => ( - - - - - - - - ))} - -
Nome CompletoE-mailTelefonePapelAções
{user.full_name}{user.email}{user.phone || "N/A"}{user.role} -
- - - -
-
+
+
+
+

Usuários Cadastrados

+

Gerencie todos os usuários e seus papéis no sistema.

- )} -
- - {/* Delete Confirmation Dialog */} - - - - Confirma a exclusão? - - Esta ação é irreversível e excluirá permanentemente o registro deste usuário. - - - - Cancelar - - {loading ? : null} - Excluir - - - - + + + +
- {/* User Details Dialog */} - - - - {userDetails?.full_name} - - {userDetails && ( + +
+ + +
+ + +
+ {loading ? ( +
+ + Carregando usuários... +
+ ) : error ? ( +
{error}
+ ) : filteredUsers.length === 0 ? ( +
+ Nenhum usuário encontrado.{" "} + + Adicione um novo + + . +
+ ) : ( +
+ + + + + + + + + + + + + {filteredUsers.map((user) => ( + + + + + + + + + + ))} + +
IDNomeE-mailTelefonePapelAções
{user.id}{user.full_name || "—"}{user.email || "—"}{user.phone || "—"}{user.role || "—"} +
+ +
+
+
+ )} +
+ + + + + + + {userDetails?.profile?.full_name || userDetails?.user?.email || "Detalhes do Usuário"} + + + + {!userDetails ? ( +
+ + Buscando dados completos... +
+ ) : (
-
-
E-mail: {userDetails.email}
-
Telefone: {userDetails.phone || 'Não informado'}
-
Papel: {userDetails.role}
+ +
ID: {userDetails.user.id}
+
E-mail: {userDetails.user.email}
+
Email confirmado em: {userDetails.user.email_confirmed_at || "—"}
+
Último login: {userDetails.user.last_sign_in_at || "—"}
+
Criado em: {userDetails.user.created_at || "—"}
+ + +
Nome completo: {userDetails.profile.full_name || "—"}
+
Telefone: {userDetails.profile.phone || "—"}
+ {userDetails.profile.avatar_url && ( +
Avatar:
+ )} +
Conta desativada: {userDetails.profile.disabled ? "Sim" : "Não"}
+
Profile criado em: {userDetails.profile.created_at || "—"}
+
Profile atualizado em: {userDetails.profile.updated_at || "—"}
+ + +
+ Roles: +
    + {userDetails.roles.map((role, idx) =>
  • {role}
  • )} +
+
+ +
+ Permissões: +
    + {Object.entries(userDetails.permissions).map(([key, value]) => ( +
  • {key}: {value ? "Sim" : "Não"}
  • + ))} +
)} - - - - Fechar - - - -
+ + +
+
+ + Fechar + +
+
+
); } \ No newline at end of file diff --git a/components/LoginForm.tsx b/components/LoginForm.tsx index 71a6ab4..77037a7 100644 --- a/components/LoginForm.tsx +++ b/components/LoginForm.tsx @@ -9,7 +9,7 @@ import Cookies from "js-cookie"; import { jwtDecode } from "jwt-decode"; import { cn } from "@/lib/utils"; -// Componentes Shadcn UI + import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -17,10 +17,10 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com import { Separator } from "@/components/ui/separator"; import { apikey } from "@/services/api.mjs"; -// Hook customizado + import { useToast } from "@/hooks/use-toast"; -// Ícones + import { Eye, EyeOff, Mail, Lock, Loader2, UserCheck, Stethoscope, IdCard, Receipt } from "lucide-react"; interface LoginFormProps { diff --git a/services/usersApi.mjs b/services/usersApi.mjs index d4aa18f..f37bc14 100644 --- a/services/usersApi.mjs +++ b/services/usersApi.mjs @@ -1,8 +1,8 @@ import { api } from "./api.mjs"; export const usersService = { - create_user: (data) => api.post('/functions/v1/create-user'), - list_roles: () => api.get("/rest/v1/user_roles"), - full_data: () => api.get(`/functions/v1/user-info`), - summary_data: () => api.get('/auth/v1/user') + create_user: (data) => api.post(`/functions/v1/create-user`), + list_roles: () => api.get(`/rest/v1/user_roles`), + full_data: (id) => api.get(`/functions/v1/user-info?user_id=${id}`), + summary_data: () => api.get(`/auth/v1/user`) } \ No newline at end of file