diff --git a/app/cadastro/page.tsx b/app/cadastro/page.tsx index 843a490..9c772fb 100644 --- a/app/cadastro/page.tsx +++ b/app/cadastro/page.tsx @@ -10,7 +10,7 @@ export default function HomePage() {

Central de Operações

- MedConnect + MediConnect

diff --git a/app/manager/usuario/novo/page.tsx b/app/manager/usuario/novo/page.tsx index e9f50e5..ee73c95 100644 --- a/app/manager/usuario/novo/page.tsx +++ b/app/manager/usuario/novo/page.tsx @@ -1,231 +1,259 @@ -"use client" +"use client"; -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 { Save, Loader2 } from "lucide-react" -import ManagerLayout from "@/components/manager-layout" +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 { Save, Loader2 } from "lucide-react"; +import ManagerLayout from "@/components/manager-layout"; import { usersService } from "services/usersApi.mjs"; +import { login } from "services/api.mjs"; +// Adicionada a propriedade 'senha' e 'confirmarSenha' interface UserFormData { - email: string; - password: string; - nomeCompleto: string; - telefone: string; - papel: string; + email: string; + nomeCompleto: string; + telefone: string; + papel: string; + senha: string; + confirmarSenha: string; // Novo campo para confirmação } const defaultFormData: UserFormData = { - email: '', - password: '', - nomeCompleto: '', - telefone: '', - papel: '', + email: "", + nomeCompleto: "", + telefone: "", + papel: "", + senha: "", + confirmarSenha: "", }; -// Remove todos os caracteres não numéricos -const cleanNumber = (value: string): string => value.replace(/\D/g, ''); - -// Definição do requisito mínimo de senha -const MIN_PASSWORD_LENGTH = 8; - - +// Funções de formatação de telefone +const cleanNumber = (value: string): string => value.replace(/\D/g, ""); const formatPhone = (value: string): string => { - 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; + 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() { - const router = useRouter(); - const [formData, setFormData] = useState(defaultFormData); - const [isSaving, setIsSaving] = useState(false); - const [error, setError] = useState(null); + const router = useRouter(); + const [formData, setFormData] = useState(defaultFormData); + const [isSaving, setIsSaving] = useState(false); + const [error, setError] = useState(null); - const handleInputChange = (key: keyof UserFormData, value: string) => { - const updatedValue = key === 'telefone' ? formatPhone(value) : value; - setFormData((prev) => ({ ...prev, [key]: updatedValue })); - }; + const handleInputChange = (key: keyof UserFormData, value: string) => { + const updatedValue = key === "telefone" ? formatPhone(value) : value; + setFormData((prev) => ({ ...prev, [key]: updatedValue })); + }; - // Handles form submission - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setError(null); + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); - // Basic validation - if (!formData.email || !formData.password || !formData.nomeCompleto || !formData.papel) { - setError("Por favor, preencha todos os campos obrigatórios."); - return; - } + // Validação de campos obrigatórios + if (!formData.email || !formData.nomeCompleto || !formData.papel || !formData.senha || !formData.confirmarSenha) { + setError("Por favor, preencha todos os campos obrigatórios."); + return; + } - // Validação de comprimento mínimo da senha - if (formData.password.length < MIN_PASSWORD_LENGTH) { - setError(`A senha deve ter no mínimo ${MIN_PASSWORD_LENGTH} caracteres.`); - return; - } + // Validação de senhas + if (formData.senha !== formData.confirmarSenha) { + setError("A Senha e a Confirmação de Senha não coincidem."); + return; + } - setIsSaving(true); + setIsSaving(true); - // ---------------------------------------------------------------------- - // CORREÇÃO FINAL: Usa o formato de telefone que o mock API comprovadamente aceitou. - // ---------------------------------------------------------------------- - const phoneValue = formData.telefone.trim(); + try { + await login(); - // Prepara o payload com os campos obrigatórios - const payload: any = { - email: formData.email, - password: formData.password, - full_name: formData.nomeCompleto, - role: formData.papel, - }; + const payload = { + full_name: formData.nomeCompleto, + email: formData.email.trim().toLowerCase(), + phone: formData.telefone || null, + role: formData.papel, + password: formData.senha, // Senha adicionada + }; - // Adiciona o telefone APENAS se estiver preenchido, enviando o formato FORMATADO. - if (phoneValue.length > 0) { - payload.phone = phoneValue; - } - // ---------------------------------------------------------------------- + console.log("📤 Enviando payload:", payload); + await usersService.create_user(payload); - try { - await usersService.create_user(payload); - router.push("/manager/usuario"); - } catch (e: any) { - console.error("Erro ao criar usuário:", e); - // Melhorando a mensagem de erro para o usuário final - const apiErrorMsg = e.message?.includes("500") - ? "Erro interno do servidor. Verifique os logs do backend ou tente novamente mais tarde. (Possível problema: E-mail já em uso ou falha de conexão.)" - : e.message || "Ocorreu um erro inesperado. Tente novamente."; + router.push("/manager/usuario"); + } catch (e: any) { + console.error("Erro ao criar usuário:", e); + const msg = + e.message || + "Não foi possível criar o usuário. Verifique os dados e tente novamente."; + setError(msg); + } finally { + setIsSaving(false); + } + }; - setError(apiErrorMsg); - } finally { - setIsSaving(false); - } - }; - - return ( - -

-
-
-

Novo Usuário

-

- Preencha os dados para cadastrar um novo usuário no sistema. -

-
- - - -
- -
- - {/* Error Message Display */} - {error && ( -
-

Erro no Cadastro:

-

{error}

-
- )} - -
-
- - handleInputChange("nomeCompleto", e.target.value)} - placeholder="Nome e Sobrenome" - required - /> -
- -
-
- - handleInputChange("email", e.target.value)} - placeholder="exemplo@dominio.com" - required - /> -
-
- - handleInputChange("password", e.target.value)} - placeholder="••••••••" - required - minLength={MIN_PASSWORD_LENGTH} // Adiciona validação HTML - /> - {/* MENSAGEM DE AJUDA PARA SENHA */} -

Mínimo de {MIN_PASSWORD_LENGTH} caracteres.

-
-
- -
-
- - handleInputChange("telefone", e.target.value)} - placeholder="(00) 00000-0000" - maxLength={15} - /> -
-
- - -
-
-
- - {/* Action Buttons */} -
- - - - -
-
+ return ( + + {/* Container principal: w-full e centralizado. max-w-screen-lg para evitar expansão excessiva */} +
+
+ + {/* Cabeçalho */} +
+
+

Novo Usuário

+

+ Preencha os dados para cadastrar um novo usuário no sistema. +

- - ); -} + + + +
+ + {/* Formulário */} +
+ {error && ( +
+

Erro no Cadastro:

+

{error}

+
+ )} + + {/* Campos de Entrada - Usando Grid de 2 colunas para organização */} +
+ + {/* Nome Completo - Largura total */} +
+ + + handleInputChange("nomeCompleto", e.target.value) + } + placeholder="Nome e Sobrenome" + required + /> +
+ + {/* E-mail (Coluna 1) */} +
+ + handleInputChange("email", e.target.value)} + placeholder="exemplo@dominio.com" + required + /> +
+ + {/* Papel (Função) (Coluna 2) */} +
+ + +
+ + {/* Senha (Coluna 1) */} +
+ + handleInputChange("senha", e.target.value)} + placeholder="Mínimo 8 caracteres" + minLength={8} + required + /> +
+ + {/* Confirmar Senha (Coluna 2) */} +
+ + handleInputChange("confirmarSenha", e.target.value)} + placeholder="Repita a senha" + required + /> + {formData.senha && formData.confirmarSenha && formData.senha !== formData.confirmarSenha && ( +

As senhas não coincidem.

+ )} +
+ + {/* Telefone (Opcional, mas mantido no grid) */} +
+ + + handleInputChange("telefone", e.target.value) + } + placeholder="(00) 00000-0000" + maxLength={15} + /> +
+ +
+ + {/* Botões de Ação */} +
+ + + + +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/manager/usuario/page.tsx b/app/manager/usuario/page.tsx index d126112..660db2a 100644 --- a/app/manager/usuario/page.tsx +++ b/app/manager/usuario/page.tsx @@ -1,15 +1,20 @@ +// app/manager/usuario/page.tsx "use client"; import React, { useEffect, useState, useCallback } from "react"; import ManagerLayout from "@/components/manager-layout"; 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 { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Plus, Eye, Filter, Loader2 } from "lucide-react"; import { AlertDialog, - AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, @@ -17,173 +22,144 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; - +import { api, login } from "services/api.mjs"; import { usersService } from "services/usersApi.mjs"; -interface User { - user: { - id: string; - email: string; - 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; - user_id: string; + id: string; + user_id: string; full_name?: string; email: string; phone?: string | null; role: string; } - +interface UserInfoResponse { + user: any; + profile: any; + roles: string[]; + permissions: Record; +} export default function UsersPage() { - const router = useRouter(); - - 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 [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [userToDeleteId, setUserToDeleteId] = useState(null); + const [userDetails, setUserDetails] = useState(null); const [selectedRole, setSelectedRole] = useState(""); const fetchUsers = useCallback(async () => { - setLoading(true); - setError(null); - try { - const data = await usersService.list_roles(); // já retorna o JSON diretamente - console.log("Resposta da API list_roles:", data); + setLoading(true); + setError(null); + try { + // 1) pega roles + const rolesData: any[] = await usersService.list_roles(); + // Garante que rolesData é array + const rolesArray = Array.isArray(rolesData) ? rolesData : []; - if (Array.isArray(data)) { - const mappedUsers: FlatUser[] = data.map((item: any) => ({ - id: item.id || (item.user_id ?? ""), // id da linha ou fallback - user_id: item.user_id || item.id || "", // garante que user_id exista - full_name: item.full_name || "—", - email: item.email || "—", - phone: item.phone ?? "—", - role: item.role || "—", - })); + // 2) pega todos os profiles de uma vez (para evitar muitos requests) + const profilesData: any[] = await api.get(`/rest/v1/profiles?select=id,full_name,email,phone`); + const profilesById = new Map(); + if (Array.isArray(profilesData)) { + for (const p of profilesData) { + if (p?.id) profilesById.set(p.id, p); + } + } - setUsers(mappedUsers); - } else { - console.warn("Formato inesperado recebido em list_roles:", data); + // 3) mapear roles -> flat users, usando ID específico de cada item + const mapped: FlatUser[] = rolesArray.map((roleItem) => { + const uid = roleItem.user_id; + const profile = profilesById.get(uid); + return { + id: uid, + user_id: uid, + full_name: profile?.full_name ?? "—", + email: profile?.email ?? "—", + phone: profile?.phone ?? "—", + role: roleItem.role ?? "—", + }; + }); + + setUsers(mapped); + console.log("[fetchUsers] mapped count:", mapped.length); + } catch (err: any) { + console.error("Erro ao buscar usuários:", err); + setError("Não foi possível carregar os usuários. Veja console."); setUsers([]); + } finally { + setLoading(false); } - } 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); - } -}, []); + }, []); - useEffect(() => { - fetchUsers(); + const init = async () => { + try { + await login(); // garante token + } catch (e) { + console.warn("login falhou no init:", e); + } + await fetchUsers(); + }; + init(); }, [fetchUsers]); - - - - const openDetailsDialog = async (flatUser: FlatUser) => { - setDetailsDialogOpen(true); - setUserDetails(null); - - try { - console.log("Buscando detalhes do user_id:", flatUser.user_id); - const fullUserData: User = await usersService.full_data(flatUser.user_id); - setUserDetails(fullUserData); - } catch (err: any) { - console.error("Erro ao buscar detalhes do usuário:", err); - setUserDetails({ - user: { - id: flatUser.user_id, - email: flatUser.email || "", - created_at: "Erro ao Carregar", - last_sign_in_at: "Erro ao Carregar", - }, - profile: { - full_name: flatUser.full_name || "Erro ao Carregar Detalhes", - phone: flatUser.phone || "—", - }, - roles: [], - permissions: {}, - } as any); - } -}; - - - - const filteredUsers = selectedRole && selectedRole !== "all" - ? users.filter((u) => u.role === selectedRole) - : users; + setDetailsDialogOpen(true); + setUserDetails(null); + try { + console.log("[openDetailsDialog] user_id:", flatUser.user_id); + const data = await usersService.full_data(flatUser.user_id); + console.log("[openDetailsDialog] full_data returned:", data); + setUserDetails(data); + } catch (err: any) { + console.error("Erro ao carregar detalhes:", err); + // fallback com dados já conhecidos + setUserDetails({ + user: { id: flatUser.user_id, email: flatUser.email }, + profile: { full_name: flatUser.full_name, phone: flatUser.phone }, + roles: [flatUser.role], + permissions: {}, + }); + } + }; + const filteredUsers = + selectedRole && selectedRole !== "all" ? users.filter((u) => u.role === selectedRole) : users; return (
-

Usuários Cadastrados

-

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

+

Usuários

+

Gerencie usuários.

-
- + + + + + Todos + Admin + Gestor + Médico + Secretária + Usuário + +
-
{loading ? (
@@ -194,11 +170,7 @@ export default function UsersPage() {
{error}
) : filteredUsers.length === 0 ? (
- Nenhum usuário encontrado.{" "} - - Adicione um novo - - . + Nenhum usuário encontrado.
) : (
@@ -209,31 +181,22 @@ export default function UsersPage() { Nome E-mail Telefone - Papel + Cargo Ações - {filteredUsers.map((user) => ( - - - {user.id} - {user.full_name || "—"} - {user.email || "—"} - {user.phone || "—"} - {user.role || "—"} + {filteredUsers.map((u) => ( + + {u.id} + {u.full_name} + {u.email} + {u.phone} + {u.role} -
- - -
+ ))} @@ -243,59 +206,31 @@ export default function UsersPage() { )}
- - - {userDetails?.profile?.full_name || userDetails?.user?.email || "Detalhes do Usuário"} - + {userDetails?.profile?.full_name || "Detalhes do Usuário"} - - {!userDetails ? ( -
+ {!userDetails ? ( +
Buscando dados completos... -
- ) : ( -
- -
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"}
  • - ))} -
+ ) : ( +
+
ID: {userDetails.user.id}
+
E-mail: {userDetails.user.email}
+
Nome completo: {userDetails.profile.full_name}
+
Telefone: {userDetails.profile.phone}
+
Roles: {userDetails.roles?.join(", ")}
+
+ Permissões: +
    + {Object.entries(userDetails.permissions || {}).map(([k,v]) =>
  • {k}: {v ? "Sim" : "Não"}
  • )} +
+
-
- )} - - + )} @@ -306,4 +241,4 @@ export default function UsersPage() {
); -} \ No newline at end of file +} diff --git a/app/page.tsx b/app/page.tsx index 5c3d1f6..c29ad38 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -10,12 +10,12 @@ export default function InicialPage() { {}
Horário: 08h00 - 21h00 - Email: contato@medconnect.com + Email: contato@mediconnect.com
{}
-

MedConnect

+

MediConnect

); diff --git a/components/doctor-layout.tsx b/components/doctor-layout.tsx index 2d4375d..69df494 100644 --- a/components/doctor-layout.tsx +++ b/components/doctor-layout.tsx @@ -135,7 +135,7 @@ export default function DoctorLayout({ children }: PatientLayoutProps) {
- MidConnecta + MediConnect
)}
)}
- MidConnecta + MediConnect
)} diff --git a/components/hospital-layout.tsx b/components/hospital-layout.tsx index 286be4d..db7ebc1 100644 --- a/components/hospital-layout.tsx +++ b/components/hospital-layout.tsx @@ -123,7 +123,7 @@ export default function HospitalLayout({ children }: HospitalLayoutProps) {
- MedConnect + MediConnect
)}