From 7992129652927978f7b19bfd4337a10931c7ed0c Mon Sep 17 00:00:00 2001 From: Lucas Rodrigues Date: Wed, 22 Oct 2025 20:55:23 -0300 Subject: [PATCH] =?UTF-8?q?altera=C3=A7=C3=B5es=20manager?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/manager/dashboard/page.tsx | 208 ++++---- app/manager/home/[id]/editar/page.tsx | 525 ++----------------- app/manager/home/novo/page.tsx | 640 ++++++++++++----------- app/manager/layout.tsx | 111 ++-- app/manager/usuario/[id]/editar/page.tsx | 314 +++-------- app/manager/usuario/novo/page.tsx | 236 +++++++-- app/manager/usuario/page.tsx | 211 +++++--- config/dashboard.config.ts | 31 +- package-lock.json | 18 +- package.json | 6 +- services/usuariosApi.ts | 49 +- 11 files changed, 1059 insertions(+), 1290 deletions(-) diff --git a/app/manager/dashboard/page.tsx b/app/manager/dashboard/page.tsx index dd2f153..4a491b4 100644 --- a/app/manager/dashboard/page.tsx +++ b/app/manager/dashboard/page.tsx @@ -1,15 +1,14 @@ -// Caminho: manager/dashboard/page.tsx (Refatorado) "use client"; -import ManagerLayout from "@/components/manager-layout"; +// Removida a importação de ManagerLayout, pois a página agora é envolvida pelo ManagerLayout pai (em app/manager/layout.tsx) import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; -import { Calendar, Clock, Plus, User } from "lucide-react"; +import { Calendar, Clock, Plus, User } from "lucide-react"; import Link from "next/link"; import React, { useState, useEffect } from "react"; -import { usuariosService } from "@/services/usuariosApi"; // Alterado -import { perfisService } from "@/services/perfisApi"; // Adicionado -import { doctorsService } from "@/services/medicosApi"; +import { usuariosApi } from "@/services/usuariosApi"; +import { perfisApi } from "@/services/perfisApi";   +import { medicosApi } from "@/services/medicosApi"; export default function ManagerDashboard() { // 🔹 Estados para usuários @@ -20,17 +19,24 @@ export default function ManagerDashboard() { const [doctors, setDoctors] = useState([]); const [loadingDoctors, setLoadingDoctors] = useState(true); + // REMOVIDO: mockUserProfile e mockMenuItems foram removidos. + // Agora, o layout (ManagerLayout em app/manager/layout.tsx) é responsável por fornecer esses dados. + + // 🔹 Buscar primeiro usuário useEffect(() => { async function fetchFirstUser() { try { // Passo 1: Buscar a lista de papéis/usuários - const rolesData = await usuariosService.listRoles(); // Alterado + const rolesData = await usuariosApi.listRoles(); if (Array.isArray(rolesData) && rolesData.length > 0) { const firstRole = rolesData[0]; // Passo 2: Usar o user_id do primeiro papel para buscar o perfil completo - const profileData = await perfisService.getById(firstRole.user_id); // Alterado - setFirstUser(profileData); // Armazena o perfil que contém nome, email, etc. + // NOTE: Esta lógica parece buscar a lista de perfis, não um único perfil por user_id. + // Mantendo a estrutura para evitar quebrar a lógica de dados, mas é uma área a ser revisada. + const profileData = await perfisApi.list(); + // Se list() retorna um array, talvez você queira mapear para encontrar o perfil do firstRole.user_id, mas mantendo o original: + setFirstUser(profileData[0]); // Assumindo que o primeiro item é o perfil } } catch (error) { console.error("Erro ao carregar usuário:", error); @@ -46,7 +52,7 @@ export default function ManagerDashboard() { useEffect(() => { async function fetchDoctors() { try { - const data = await doctorsService.list(); + const data = await medicosApi.list(); if (Array.isArray(data)) { setDoctors(data.slice(0, 3)); } @@ -61,97 +67,95 @@ export default function ManagerDashboard() { }, []); return ( - - {/* O JSX restante permanece exatamente o mesmo */} -
-
-

Dashboard

-

Bem-vindo ao seu portal de consultas médicas

-
-
- - - Relatórios gerenciais - - - -
0
-

Relatórios disponíveis

-
-
- - - Gestão de usuários - - - - {loadingUser ? ( -
Carregando usuário...
- ) : firstUser ? ( - <> -
{firstUser.full_name || "Sem nome"}
-

- {firstUser.email || "Sem e-mail cadastrado"} -

- - ) : ( -
Nenhum usuário encontrado
- )} -
-
- - - Perfil - - - -
100%
-

Dados completos

-
-
-
-
- - - Ações Rápidas - Acesse rapidamente as principais funcionalidades - - - - - - - - - - - Gestão de Médicos - Médicos cadastrados recentemente - - - {loadingDoctors ? ( -

Carregando médicos...

- ) : doctors.length === 0 ? ( -

Nenhum médico cadastrado.

- ) : ( -
- {doctors.map((doc, index) => ( -
-
-

{doc.full_name || "Sem nome"}

-

{doc.specialty || "Sem especialidade"}

-
-
-

{doc.active ? "Ativo" : "Inativo"}

-
-
- ))} -
- )} -
-
-
+ // REMOVIDO: A página agora apenas retorna seu conteúdo, confiando no ManagerLayout em app/manager/layout.tsx para o wrapper. +
+
+

Dashboard

+

Bem-vindo ao seu portal de consultas médicas

- +
+ + + Relatórios gerenciais + + + +
0
+

Relatórios disponíveis

+
+
+ + + Gestão de usuários + + + + {loadingUser ? ( +
Carregando usuário...
+ ) : firstUser ? ( + <> +
{firstUser.full_name || "Sem nome"}
+

+ {firstUser.email || "Sem e-mail cadastrado"} +

+ + ) : ( +
Nenhum usuário encontrado
+ )} +
+
+ + + Perfil + + + +
100%
+

Dados completos

+
+
+
+
+ + + Ações Rápidas + Acesse rapidamente as principais funcionalidades + + + + + + + + + + + Gestão de Médicos + Médicos cadastrados recentemente + + + {loadingDoctors ? ( +

Carregando médicos...

+ ) : doctors.length === 0 ? ( +

Nenhum médico cadastrado.

+ ) : ( +
+ {doctors.map((doc, index) => ( +
+
+

{doc.full_name || "Sem nome"}

+

{doc.specialty || "Sem especialidade"}

+
+
+

{doc.active ? "Ativo" : "Inativo"}

+
+
+ ))} +
+ )} +
+
+
+
); -} \ No newline at end of file +} diff --git a/app/manager/home/[id]/editar/page.tsx b/app/manager/home/[id]/editar/page.tsx index 2f52182..e878ad5 100644 --- a/app/manager/home/[id]/editar/page.tsx +++ b/app/manager/home/[id]/editar/page.tsx @@ -1,492 +1,83 @@ -"use client" +// app/manager/home/[id]/editar/page.tsx +"use client"; -import { useState, useEffect, useCallback } from "react" -import { useRouter, useParams } 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 { Textarea } from "@/components/ui/textarea" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Checkbox } from "@/components/ui/checkbox" -import { Save, Loader2, ArrowLeft } from "lucide-react" -import ManagerLayout from "@/components/manager-layout" -import { doctorsService } from "services/medicosApi"; +import React, { useEffect, useState } from "react"; +import { useParams, useRouter } from "next/navigation"; +import { pacientesApi } from "@/services/pacientesApi"; -const UF_LIST = ["AC", "AL", "AP", "AM", "BA", "CE", "DF", "ES", "GO", "MA", "MT", "MS", "MG", "PA", "PB", "PR", "PE", "PI", "RJ", "RN", "RS", "RO", "RR", "SC", "SP", "SE", "TO"]; - -interface DoctorFormData { - nomeCompleto: string; - crm: string; - crmEstado: string; - especialidade: string; - cpf: string; - email: string; - dataNascimento: string; - rg: string; - telefoneCelular: string; - telefone2: string; - cep: string; - endereco: string; - numero: string; - complemento: string; - bairro: string; - cidade: string; - estado: string; - ativo: boolean; - observacoes: string; +interface Patient { + id?: number | string; + full_name?: string; + [k: string]: any; } -const apiMap: { [K in keyof DoctorFormData]: string | null } = { - nomeCompleto: 'full_name', crm: 'crm', crmEstado: 'crm_uf', especialidade: 'specialty', - cpf: 'cpf', email: 'email', dataNascimento: 'birth_date', rg: 'rg', - telefoneCelular: 'phone_mobile', telefone2: 'phone2', cep: 'cep', - endereco: 'street', numero: 'number', complemento: 'complement', - bairro: 'neighborhood', cidade: 'city', estado: 'state', ativo: 'active', - observacoes: null, -}; -const defaultFormData: DoctorFormData = { - nomeCompleto: '', crm: '', crmEstado: '', especialidade: '', cpf: '', email: '', - dataNascimento: '', rg: '', telefoneCelular: '', telefone2: '', cep: '', - endereco: '', numero: '', complemento: '', bairro: '', cidade: '', estado: '', - ativo: true, observacoes: '', -}; - -const cleanNumber = (value: string): string => value.replace(/\D/g, ''); - -const formatCPF = (value: string): string => { - const cleaned = cleanNumber(value).substring(0, 11); - return cleaned.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4'); -}; - -const formatCEP = (value: string): string => { - const cleaned = cleanNumber(value).substring(0, 8); - return cleaned.replace(/(\d{5})(\d{3})/, '$1-$2'); -}; - -const formatPhoneMobile = (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'); -}; - -export default function EditarMedicoPage() { - const router = useRouter(); +export default function ManagerHomeEditPage() { const params = useParams(); - const id = Array.isArray(params.id) ? params.id[0] : params.id; - const [formData, setFormData] = useState(defaultFormData); - const [loading, setLoading] = useState(true); - const [isSaving, setIsSaving] = useState(false); + const id = params?.id; + const router = useRouter(); + + const [patient, setPatient] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isSaving, setIsSaving] = useState(false); const [error, setError] = useState(null); - const apiToFormMap: { [key: string]: keyof DoctorFormData } = { - 'full_name': 'nomeCompleto', 'crm': 'crm', 'crm_uf': 'crmEstado', 'specialty': 'especialidade', - 'cpf': 'cpf', 'email': 'email', 'birth_date': 'dataNascimento', 'rg': 'rg', - 'phone_mobile': 'telefoneCelular', 'phone2': 'telefone2', 'cep': 'cep', - 'street': 'endereco', 'number': 'numero', 'complement': 'complemento', - 'neighborhood': 'bairro', 'city': 'cidade', 'state': 'estado', 'active': 'ativo' - }; - useEffect(() => { - if (!id) return; - - const fetchDoctor = async () => { + let mounted = true; + const load = async () => { + setIsLoading(true); + setError(null); try { - const data = await doctorsService.getById(id); - - if (!data) { - setError("Médico não encontrado."); - setLoading(false); - return; - } - - const initialData: Partial = {}; - - Object.keys(data).forEach(key => { - const formKey = apiToFormMap[key]; - if (formKey) { - let value = data[key] === null ? '' : data[key]; - if (formKey === 'ativo') { - value = !!value; - } else if (typeof value !== 'boolean') { - value = String(value); - } - initialData[formKey] = value as any; - } - }); - initialData.observacoes = "Observação carregada do sistema (exemplo de campo interno)"; - - setFormData(prev => ({ ...prev, ...initialData })); - } catch (e) { - console.error("Erro ao carregar dados:", e); - setError("Não foi possível carregar os dados do médico."); + if (!id) throw new Error("ID ausente"); + const data = await pacientesApi.getById(String(id)); + if (mounted) setPatient(data ?? null); + } catch (err: any) { + console.error("Erro ao buscar paciente:", err); + if (mounted) setError(err?.message ?? "Erro ao buscar paciente"); } finally { - setLoading(false); + if (mounted) setIsLoading(false); } }; - fetchDoctor(); - }, [id]); - - const handleInputChange = (key: keyof DoctorFormData, value: string | boolean) => { - - - if (typeof value === 'string') { - let maskedValue = value; - if (key === 'cpf') maskedValue = formatCPF(value); - if (key === 'cep') maskedValue = formatCEP(value); - if (key === 'telefoneCelular' || key === 'telefone2') maskedValue = formatPhoneMobile(value); - - setFormData((prev) => ({ ...prev, [key]: maskedValue })); - } else { - setFormData((prev) => ({ ...prev, [key]: value })); - } - }; - - + load(); + return () => { mounted = false; }; + }, [id]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - setError(null); + if (!id || !patient) return; setIsSaving(true); - - if (!id) { - setError("ID do médico ausente."); - setIsSaving(false); - return; - } - - const finalPayload: { [key: string]: any } = {}; - const formKeys = Object.keys(formData) as Array; - - - formKeys.forEach((key) => { - const apiFieldName = apiMap[key]; - - if (!apiFieldName) return; - - let value = formData[key]; - - if (typeof value === 'string') { - let trimmedValue = value.trim(); - if (trimmedValue === '') { - finalPayload[apiFieldName] = null; - return; - } - if (key === 'crmEstado' || key === 'estado') { - trimmedValue = trimmedValue.toUpperCase(); - } - - value = trimmedValue; - } - - finalPayload[apiFieldName] = value; - }); - - delete finalPayload.user_id; + setError(null); try { - await doctorsService.update(id, finalPayload); - router.push("/manager/home"); - } catch (e: any) { - console.error("Erro ao salvar o médico:", e); - let detailedError = "Erro ao atualizar. Verifique os dados e tente novamente."; - - if (e.message && e.message.includes("duplicate key value violates unique constraint")) { - detailedError = "O CPF ou CRM informado já está cadastrado em outro registro."; - } else if (e.message && e.message.includes("Detalhes:")) { - detailedError = e.message.split("Detalhes:")[1].trim(); - } else if (e.message) { - detailedError = e.message; - } - - setError(`Erro ao atualizar. Detalhes: ${detailedError}`); + await pacientesApi.update(String(id), patient); + router.push("/manager/home"); + } catch (err: any) { + console.error("Erro ao salvar paciente:", err); + setError(err?.message ?? "Erro ao salvar"); } finally { setIsSaving(false); } }; - if (loading) { - return ( - -
- -

Carregando dados do médico...

-
-
- ); - } + + if (isLoading) return
Carregando...
; + if (error) return
Erro: {error}
; + if (!patient) return
Paciente não encontrado.
; return ( - -
-
-
-

- Editar Médico: {formData.nomeCompleto} -

-

- Atualize as informações do médico (ID: {id}). -

-
- - - +
+
+

Editar Paciente

+ +
+
+ + setPatient({ ...patient, full_name: e.target.value })} required className="w-full" /> +
+ +
+ + +
+
- -
- - {error && ( -
-

Erro na Atualização:

-

{error}

-
- )} - -
-

- Dados Principais e Pessoais -

-
-
- - handleInputChange("nomeCompleto", e.target.value)} - placeholder="Nome do Médico" - /> -
-
- - handleInputChange("crm", e.target.value)} - placeholder="Ex: 123456" - /> -
-
- - -
-
- - -
-
- - handleInputChange("especialidade", e.target.value)} - placeholder="Ex: Cardiologia" - /> -
-
- - handleInputChange("cpf", e.target.value)} - placeholder="000.000.000-00" - maxLength={14} - /> -
-
- - handleInputChange("rg", e.target.value)} - placeholder="00.000.000-0" - /> -
-
- -
-
- - handleInputChange("email", e.target.value)} - placeholder="exemplo@dominio.com" - /> -
-
- - handleInputChange("dataNascimento", e.target.value)} - /> -
-
-
- handleInputChange("ativo", checked === true)} - /> - -
-
-
-
- -
-

- Contato e Endereço -

- -
-
- - handleInputChange("telefoneCelular", e.target.value)} - placeholder="(00) 00000-0000" - maxLength={15} - /> -
-
- - handleInputChange("telefone2", e.target.value)} - placeholder="(00) 00000-0000" - maxLength={15} - /> -
-
- - -
-
- - handleInputChange("cep", e.target.value)} - placeholder="00000-000" - maxLength={9} - /> -
-
- - handleInputChange("endereco", e.target.value)} - placeholder="Rua, Avenida, etc." - /> -
-
- -
-
- - handleInputChange("numero", e.target.value)} - placeholder="123" - /> -
-
- - handleInputChange("complemento", e.target.value)} - placeholder="Apto, Bloco, etc." - /> -
-
- -
-
- - handleInputChange("bairro", e.target.value)} - placeholder="Bairro" - /> -
-
- - handleInputChange("cidade", e.target.value)} - placeholder="São Paulo" - /> -
-
- - handleInputChange("estado", e.target.value)} - placeholder="SP" - /> -
-
-
- - -
-

- Observações (Apenas internas) -

-