diff --git a/app/doctor/consultas/page.tsx b/app/doctor/consultas/page.tsx index 8eca4d2..0bc765f 100644 --- a/app/doctor/consultas/page.tsx +++ b/app/doctor/consultas/page.tsx @@ -56,7 +56,7 @@ export default function DoctorAppointmentsPage() { const patientsMap = new Map( patientsList.map((p: any) => [p.id, { name: p.full_name, phone: p.phone_mobile }]) ); - + const enrichedAppointments = appointmentsList.map((apt: any) => ({ id: apt.id, patientName: patientsMap.get(apt.patient_id)?.name || "Paciente Desconhecido", @@ -85,10 +85,10 @@ export default function DoctorAppointmentsPage() { const appointmentsToDisplay = selectedDate ? allAppointments.filter(app => app.scheduled_at && app.scheduled_at.startsWith(format(selectedDate, "yyyy-MM-dd"))) : allAppointments.filter(app => { - if (!app.scheduled_at) return false; - const dateObj = parseISO(app.scheduled_at); - return isValid(dateObj) && isFuture(dateObj); - }); + if (!app.scheduled_at) return false; + const dateObj = parseISO(app.scheduled_at); + return isValid(dateObj) && isFuture(dateObj); + }); return appointmentsToDisplay.reduce((acc, appointment) => { const dateKey = format(parseISO(appointment.scheduled_at), "yyyy-MM-dd"); @@ -162,7 +162,7 @@ export default function DoctorAppointmentsPage() { Filtrar por DataSelecione um dia para ver os detalhes. - + @@ -197,7 +197,7 @@ export default function DoctorAppointmentsPage() { {format(scheduledAtDate, "HH:mm")} - + {/* Coluna 2: Status e Telefone */}
{statusPT[appointment.status].replace('_', ' ')} diff --git a/app/doctor/dashboard/page.tsx b/app/doctor/dashboard/page.tsx index aa382d4..45fd3d7 100644 --- a/app/doctor/dashboard/page.tsx +++ b/app/doctor/dashboard/page.tsx @@ -24,6 +24,15 @@ import Link from "next/link"; import { useEffect, useState } from "react"; import { toast } from "@/hooks/use-toast"; +// --- IMPORTS ADICIONADOS PARA A CORREÇÃO --- +import { useAuthLayout } from "@/hooks/useAuthLayout"; +import { patientsService } from "@/services/patientsApi.mjs"; +// --- FIM DOS IMPORTS ADICIONADOS --- + +import { appointmentsService } from "@/services/appointmentsApi.mjs"; +import { format, parseISO, isAfter, isSameMonth, startOfToday } from "date-fns"; +import { ptBR } from "date-fns/locale"; + import { AvailabilityService } from "@/services/availabilityApi.mjs"; import { exceptionsService } from "@/services/exceptionApi.mjs"; import { doctorsService } from "@/services/doctorsApi.mjs"; @@ -122,127 +131,120 @@ interface Exception { } export default function PatientDashboard() { - const [loggedDoctor, setLoggedDoctor] = useState(); - const [userData, setUserData] = useState(); - const [availability, setAvailability] = useState(null); - const [exceptions, setExceptions] = useState([]); - const [schedule, setSchedule] = useState< - Record - >({}); - const formatTime = (time?: string | null) => - time?.split(":")?.slice(0, 2).join(":") ?? ""; - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [exceptionToDelete, setExceptionToDelete] = useState( - null - ); - const [error, setError] = useState(null); + // --- USA O HOOK DE AUTENTICAÇÃO PARA PEGAR O USUÁRIO LOGADO --- + const { user } = useAuthLayout({ requiredRole: ['medico'] }); - // Mapa de tradução - const weekdaysPT: Record = { - sunday: "Domingo", - monday: "Segunda", - tuesday: "Terça", - wednesday: "Quarta", - thursday: "Quinta", - friday: "Sexta", - saturday: "Sábado", - }; + const [loggedDoctor, setLoggedDoctor] = useState(null); + const [userData, setUserData] = useState(); + const [availability, setAvailability] = useState(null); + const [exceptions, setExceptions] = useState([]); + const [schedule, setSchedule] = useState>({}); + const formatTime = (time?: string | null) => time?.split(":")?.slice(0, 2).join(":") ?? ""; + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [exceptionToDelete, setExceptionToDelete] = useState(null); + const [error, setError] = useState(null); - useEffect(() => { - const fetchData = async () => { - try { - const doctorsList: Doctor[] = await doctorsService.list(); - const doctor = doctorsList[0]; + // --- ESTADOS PARA OS CARDS ATUALIZADOS --- + const [nextAppointment, setNextAppointment] = useState(null); + const [monthlyCount, setMonthlyCount] = useState(0); - // Salva no estado - setLoggedDoctor(doctor); + const weekdaysPT: Record = { sunday: "Domingo", monday: "Segunda", tuesday: "Terça", wednesday: "Quarta", thursday: "Quinta", friday: "Sexta", saturday: "Sábado" }; - // Busca disponibilidade - const availabilityList = await AvailabilityService.list(); + // ▼▼▼ LÓGICA DE BUSCA CORRIGIDA E ATUALIZADA ▼▼▼ + useEffect(() => { + const fetchData = async () => { + if (!user?.id) return; // Aguarda o usuário ser carregado - // Filtra já com a variável local - const filteredAvail = availabilityList.filter( - (disp: { doctor_id: string }) => disp.doctor_id === doctor?.id - ); - setAvailability(filteredAvail); + try { + // Encontra o perfil de médico correspondente ao usuário logado + const doctorsList: Doctor[] = await doctorsService.list(); + const currentDoctor = doctorsList.find(doc => doc.user_id === user.id); + + if (!currentDoctor) { + setError("Perfil de médico não encontrado para este usuário."); + return; + } + setLoggedDoctor(currentDoctor); + + // Busca todos os dados necessários em paralelo + const [appointmentsList, patientsList, availabilityList, exceptionsList] = await Promise.all([ + appointmentsService.list(), + patientsService.list(), + AvailabilityService.list(), + exceptionsService.list() + ]); + + // Mapeia pacientes por ID para consulta rápida + const patientsMap = new Map(patientsList.map((p: any) => [p.id, p.full_name])); + + // Filtra e enriquece as consultas APENAS do médico logado + const doctorAppointments = appointmentsList + .filter((apt: any) => apt.doctor_id === currentDoctor.id) + .map((apt: any): EnrichedAppointment => ({ + ...apt, + patientName: patientsMap.get(apt.patient_id) || "Paciente Desconhecido", + })); + + // 1. Lógica para "Próxima Consulta" + const today = startOfToday(); + const upcomingAppointments = doctorAppointments + .filter(apt => isAfter(parseISO(apt.scheduled_at), today)) + .sort((a, b) => new Date(a.scheduled_at).getTime() - new Date(b.scheduled_at).getTime()); + setNextAppointment(upcomingAppointments[0] || null); + + // 2. Lógica para "Consultas Este Mês" (apenas ativas) + const activeStatuses = ['confirmed', 'requested', 'checked_in']; + const currentMonthAppointments = doctorAppointments.filter(apt => + isSameMonth(parseISO(apt.scheduled_at), new Date()) && activeStatuses.includes(apt.status) + ); + setMonthlyCount(currentMonthAppointments.length); + + // Busca e filtra o restante dos dados + setAvailability(availabilityList.filter((d: any) => d.doctor_id === currentDoctor.id)); + setExceptions(exceptionsList.filter((e: any) => e.doctor_id === currentDoctor.id)); - // Busca exceções - const exceptionsList = await exceptionsService.list(); - const filteredExc = exceptionsList.filter((exc: { doctor_id: string }) => exc.doctor_id === doctor?.id); - setExceptions(filteredExc); } catch (e: any) { - alert(`${e?.error} ${e?.message}`); + setError(e?.message || "Erro ao buscar dados do dashboard"); + console.error("Erro no dashboard:", e); } }; - fetchData(); - }, []); + fetchData(); + }, [user]); // A busca de dados agora depende do usuário logado + // ▲▲▲ FIM DA LÓGICA DE BUSCA ATUALIZADA ▲▲▲ - // Função auxiliar para filtrar o id do doctor correspondente ao user logado - function findDoctorById(id: string, doctors: Doctor[]) { - return doctors.find((doctor) => doctor.user_id === id); - } - - const openDeleteDialog = (exceptionId: string) => { - setExceptionToDelete(exceptionId); - setDeleteDialogOpen(true); - }; - - const handleDeleteException = async (ExceptionId: string) => { - try { - alert(ExceptionId); - const res = await exceptionsService.delete(ExceptionId); - - let message = "Exceção deletada com sucesso"; - try { - if (res) { - throw new Error( - `${res.error} ${res.message}` || "A API retornou erro" - ); - } else { - console.log(message); - } - } catch {} - - toast({ - title: "Sucesso", - description: message, - }); - - setExceptions((prev: Exception[]) => - prev.filter((p) => String(p.id) !== String(ExceptionId)) - ); - } catch (e: any) { - toast({ - title: "Erro", - description: e?.message || "Não foi possível deletar a exceção", - }); + function findDoctorById(id: string, doctors: Doctor[]) { + return doctors.find((doctor) => doctor.user_id === id); } - setDeleteDialogOpen(false); - setExceptionToDelete(null); - }; - function formatAvailability(data: Availability[]) { - // Agrupar os horários por dia da semana - const schedule = data.reduce((acc: any, item) => { - const { weekday, start_time, end_time } = item; + const openDeleteDialog = (exceptionId: string) => { + setExceptionToDelete(exceptionId); + setDeleteDialogOpen(true); + }; - // Se o dia ainda não existe, cria o array - if (!acc[weekday]) { - acc[weekday] = []; - } + const handleDeleteException = async (ExceptionId: string) => { + try { + const res = await exceptionsService.delete(ExceptionId); + if (res && res.error) { throw new Error(res.message || "A API retornou um erro"); } + toast({ title: "Sucesso", description: "Exceção deletada com sucesso" }); + setExceptions((prev: Exception[]) => prev.filter((p) => String(p.id) !== String(ExceptionId))); + } catch (e: any) { + toast({ title: "Erro", description: e?.message || "Não foi possível deletar a exceção" }); + } + setDeleteDialogOpen(false); + setExceptionToDelete(null); + }; - // Adiciona o horário do dia - acc[weekday].push({ - start: start_time, - end: end_time, - }); - - return acc; - }, {} as Record); - - return schedule; - } + function formatAvailability(data: Availability[]) { + if (!data) return {}; + const schedule = data.reduce((acc: any, item) => { + const { weekday, start_time, end_time } = item; + if (!acc[weekday]) acc[weekday] = []; + acc[weekday].push({ start: start_time, end: end_time }); + return acc; + }, {} as Record); + return schedule; + } useEffect(() => { if (availability) { @@ -261,32 +263,45 @@ export default function PatientDashboard() {

-
- - - - Próxima Consulta - - - - -
02 out
-

Dr. Silva - 14:30

-
-
+
+ {/* ▼▼▼ CARD "PRÓXIMA CONSULTA" CORRIGIDO PARA MOSTRAR NOME DO PACIENTE ▼▼▼ */} + + + Próxima Consulta + + + + {nextAppointment ? ( + <> +
+ {format(parseISO(nextAppointment.scheduled_at), "dd MMM", { locale: ptBR })} +
+

+ {nextAppointment.patientName} - {format(parseISO(nextAppointment.scheduled_at), "HH:mm")} +

+ + ) : ( + <> +
Nenhuma
+

Sem próximas consultas

+ + )} +
+
+ {/* ▲▲▲ FIM DO CARD ATUALIZADO ▲▲▲ */} - - - - Consultas Este Mês - - - - -
4
-

4 agendadas

-
-
+ {/* ▼▼▼ CARD "CONSULTAS ESTE MÊS" CORRIGIDO PARA CONTAGEM CORRETA ▼▼▼ */} + + + Consultas Este Mês + + + +
{monthlyCount}
+

{monthlyCount === 1 ? '1 agendada' : `${monthlyCount} agendadas`}

+
+
+ {/* ▲▲▲ FIM DO CARD ATUALIZADO ▲▲▲ */} @@ -300,23 +315,22 @@ export default function PatientDashboard() {
-
- - - Ações Rápidas - - Acesse rapidamente as principais funcionalidades - - - - - - - - + {/* O restante do código permanece o mesmo */} +
+ + + Ações Rápidas + Acesse rapidamente as principais funcionalidades + + + + + + + @@ -355,16 +369,15 @@ export default function PatientDashboard() { Bloqueios e liberações eventuais de agenda - - {exceptions && exceptions.length > 0 ? ( - exceptions.map((ex: Exception) => { - // Formata data e hora - const date = new Date(ex.date).toLocaleDateString("pt-BR", { - weekday: "long", - day: "2-digit", - month: "long", - timeZone: "UTC", - }); + + {exceptions && exceptions.length > 0 ? ( + exceptions.map((ex: Exception) => { + const date = new Date(ex.date).toLocaleDateString("pt-BR", { + weekday: "long", + day: "2-digit", + month: "long", + timeZone: "UTC" + }); const startTime = formatTime(ex.start_time); const endTime = formatTime(ex.end_time); @@ -374,7 +387,11 @@ export default function PatientDashboard() {

{date}

-

{startTime && endTime ? `${startTime} - ${endTime}` : "Dia todo"}

+

+ {startTime && endTime + ? `${startTime} - ${endTime}` + : "Dia todo"} +

{ex.kind === "bloqueio" ? "Bloqueio" : "Liberação"}

@@ -412,4 +429,4 @@ export default function PatientDashboard() {
); -} +} \ No newline at end of file diff --git a/app/doctor/medicos/[id]/laudos/[laudoId]/editar/page.tsx b/app/doctor/medicos/[id]/laudos/[laudoId]/editar/page.tsx index 52525fd..acce902 100644 --- a/app/doctor/medicos/[id]/laudos/[laudoId]/editar/page.tsx +++ b/app/doctor/medicos/[id]/laudos/[laudoId]/editar/page.tsx @@ -144,10 +144,6 @@ export default function EditarLaudoPage() {
-
- - -
diff --git a/app/doctor/medicos/[id]/laudos/novo/page.tsx b/app/doctor/medicos/[id]/laudos/novo/page.tsx index 9c9b8a9..1bac56b 100644 --- a/app/doctor/medicos/[id]/laudos/novo/page.tsx +++ b/app/doctor/medicos/[id]/laudos/novo/page.tsx @@ -1,5 +1,4 @@ - -"use client"; + "use client"; import { useParams, useRouter } from "next/navigation"; import { useState } from "react"; @@ -106,10 +105,6 @@ export default function NovoLaudoPage() {
-
- - -
diff --git a/app/doctor/medicos/page.tsx b/app/doctor/medicos/page.tsx index 1502fab..7973a65 100644 --- a/app/doctor/medicos/page.tsx +++ b/app/doctor/medicos/page.tsx @@ -8,7 +8,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { Eye, Edit, Calendar, Trash2, Loader2 } from "lucide-react"; +import { Eye, Edit, Calendar, Trash2, Loader2, MoreVertical } from "lucide-react"; import { api } from "@/services/api.mjs"; import { PatientDetailsModal } from "@/components/ui/patient-details-modal"; import { @@ -50,7 +50,7 @@ export default function PacientesPage() { const [isModalOpen, setIsModalOpen] = useState(false); // --- Lógica de Paginação INÍCIO --- - const [itemsPerPage, setItemsPerPage] = useState(5); + const [itemsPerPage, setItemsPerPage] = useState(10); const [currentPage, setCurrentPage] = useState(1); const totalPages = Math.ceil(pacientes.length / itemsPerPage); @@ -197,14 +197,6 @@ export default function PacientesPage() { 20 por página - - -
@@ -291,9 +283,10 @@ export default function PacientesPage() { - + - + Laudos - - alert(`Agenda para paciente ID: ${p.id}`) - } - > - - Ver agenda - { const newPacientes = pacientes.filter( diff --git a/app/globals.css b/app/globals.css index d0b1400..95ff1b4 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,8 +1,6 @@ -@import 'tailwindcss'; -@import 'tw-animate-css'; - +@import "tailwindcss"; +@import "tw-animate-css"; @custom-variant dark (&:is(.dark *)); - :root { --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); @@ -75,33 +73,33 @@ } .high-contrast { - --background: oklch(0 0 0); - --foreground: oklch(1 0.5 100); - --card: oklch(0 0 0); - --card-foreground: oklch(1 0.5 100); - --popover: oklch(0 0 0); - --popover-foreground: oklch(1 0.5 100); - --primary: oklch(1 0.5 100); - --primary-foreground: oklch(0 0 0); - --secondary: oklch(0 0 0); - --secondary-foreground: oklch(1 0.5 100); - --muted: oklch(0 0 0); - --muted-foreground: oklch(1 0.5 100); - --accent: oklch(0 0 0); - --accent-foreground: oklch(1 0.5 100); - --destructive: oklch(0.5 0.3 30); - --destructive-foreground: oklch(0 0 0); - --border: oklch(1 0.5 100); - --input: oklch(0 0 0); - --ring: oklch(1 0.5 100); - --sidebar: oklch(0 0 0); - --sidebar-foreground: oklch(1 0.5 100); - --sidebar-primary: oklch(1 0.5 100); - --sidebar-primary-foreground: oklch(0 0 0); - --sidebar-accent: oklch(0 0 0); - --sidebar-accent-foreground: oklch(1 0.5 100); - --sidebar-border: oklch(1 0.5 100); - --sidebar-ring: oklch(1 0.5 100); + --background: oklch(0 0 0); + --foreground: oklch(1 0.5 100); + --card: oklch(0 0 0); + --card-foreground: oklch(1 0.5 100); + --popover: oklch(0 0 0); + --popover-foreground: oklch(1 0.5 100); + --primary: oklch(1 0.5 100); + --primary-foreground: oklch(0 0 0); + --secondary: oklch(0 0 0); + --secondary-foreground: oklch(1 0.5 100); + --muted: oklch(0 0 0); + --muted-foreground: oklch(1 0.5 100); + --accent: oklch(0 0 0); + --accent-foreground: oklch(1 0.5 100); + --destructive: oklch(0.5 0.3 30); + --destructive-foreground: oklch(0 0 0); + --border: oklch(1 0.5 100); + --input: oklch(0 0 0); + --ring: oklch(1 0.5 100); + --sidebar: oklch(0 0 0); + --sidebar-foreground: oklch(1 0.5 100); + --sidebar-primary: oklch(1 0.5 100); + --sidebar-primary-foreground: oklch(0 0 0); + --sidebar-accent: oklch(0 0 0); + --sidebar-accent-foreground: oklch(1 0.5 100); + --sidebar-border: oklch(1 0.5 100); + --sidebar-ring: oklch(1 0.5 100); } @theme inline { @@ -153,4 +151,4 @@ @apply bg-background text-foreground; transition: background-color 0.3s, color 0.3s; } -} \ No newline at end of file +} diff --git a/app/manager/dashboard/page.tsx b/app/manager/dashboard/page.tsx index 6562eef..11f5c19 100644 --- a/app/manager/dashboard/page.tsx +++ b/app/manager/dashboard/page.tsx @@ -8,12 +8,13 @@ import { CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; -import { Calendar, Clock, Plus, User } from "lucide-react"; +import { Clock, Plus, User } from "lucide-react"; // Removi 'Calendar' que não estava sendo usado import Link from "next/link"; import React, { useState, useEffect } from "react"; import { usersService } from "services/usersApi.mjs"; import { doctorsService } from "services/doctorsApi.mjs"; import Sidebar from "@/components/Sidebar"; +import { api } from "services/api.mjs"; // <-- ADICIONEI ESTE IMPORT export default function ManagerDashboard() { // 🔹 Estados para usuários @@ -24,20 +25,48 @@ export default function ManagerDashboard() { const [doctors, setDoctors] = useState([]); const [loadingDoctors, setLoadingDoctors] = useState(true); - // 🔹 Buscar primeiro usuário - useEffect(() => { - async function fetchFirstUser() { - try { - const data = await usersService.list_roles(); - if (Array.isArray(data) && data.length > 0) { - setFirstUser(data[0]); + // 🔹 Buscar primeiro usuário (LÓGICA ATUALIZADA) + useEffect(() => { + async function fetchFirstUser() { + setLoadingUser(true); // Garante que o estado de loading inicie como true + try { + // 1. Busca a lista de usuários com seus cargos (roles) + const rolesData = await usersService.list_roles(); + + // 2. Verifica se a lista não está vazia + if (Array.isArray(rolesData) && rolesData.length > 0) { + const firstUserRole = rolesData[0]; + const firstUserId = firstUserRole.user_id; + + if (!firstUserId) { + throw new Error("O primeiro usuário da lista não possui um ID válido."); + } + + // 3. Usa o ID para buscar o perfil (com nome e email) do usuário + const profileData = await api.get( + `/rest/v1/profiles?select=full_name,email&id=eq.${firstUserId}` + ); + + // 4. Verifica se o perfil foi encontrado + if (Array.isArray(profileData) && profileData.length > 0) { + const userProfile = profileData[0]; + // 5. Combina os dados do cargo e do perfil e atualiza o estado + setFirstUser({ + ...firstUserRole, + ...userProfile + }); + } else { + // Se não encontrar o perfil, exibe os dados que temos + setFirstUser(firstUserRole); + } + } + } catch (error) { + console.error("Erro ao carregar usuário:", error); + setFirstUser(null); // Limpa o usuário em caso de erro + } finally { + setLoadingUser(false); + } } - } catch (error) { - console.error("Erro ao carregar usuário:", error); - } finally { - setLoadingUser(false); - } - } fetchFirstUser(); }, []); @@ -71,23 +100,9 @@ export default function ManagerDashboard() {

- {/* Cards principais */} -
- {/* Card 1 */} - - - - Relatórios gerenciais - - - - -
0
-

- Relatórios disponíveis -

-
-
+ {/* Cards principais */} +
+ {/* Card 2 — Gestão de usuários */} @@ -224,4 +239,4 @@ export default function ManagerDashboard() {
); -} +} \ 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 89a5afb..b3bfeea 100644 --- a/app/manager/home/[id]/editar/page.tsx +++ b/app/manager/home/[id]/editar/page.tsx @@ -9,47 +9,47 @@ 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 { Save, Loader2, ArrowLeft } from "lucide-react" import Sidebar from "@/components/Sidebar" -import { doctorsService } from "services/doctorsApi.mjs"; +import { doctorsService } from "services/doctorsApi.mjs"; 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; + 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; } 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, + 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: '', + 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, ''); @@ -73,420 +73,420 @@ const formatPhoneMobile = (value: string): string => { }; export default function EditarMedicoPage() { - const router = useRouter(); - 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 [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 () => { - 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."); - } finally { - setLoading(false); - } + const router = useRouter(); + 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 [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' }; - 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 })); - } - }; + useEffect(() => { + if (!id) return; - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setError(null); - setIsSaving(true); - - if (!id) { - setError("ID do médico ausente."); - setIsSaving(false); - return; - } + const fetchDoctor = async () => { + try { + const data = await doctorsService.getById(id); - const finalPayload: { [key: string]: any } = {}; - const formKeys = Object.keys(formData) as Array; + if (!data) { + setError("Médico não encontrado."); + setLoading(false); + return; + } - - formKeys.forEach((key) => { - const apiFieldName = apiMap[key]; - - if (!apiFieldName) 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."); + } finally { + setLoading(false); + } + }; + fetchDoctor(); + }, [id]); + + const handleInputChange = (key: keyof DoctorFormData, value: string | boolean) => { - 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; - }); + let maskedValue = value; + if (key === 'cpf') maskedValue = formatCPF(value); + if (key === 'cep') maskedValue = formatCEP(value); + if (key === 'telefoneCelular' || key === 'telefone2') maskedValue = formatPhoneMobile(value); - delete finalPayload.user_id; - 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}`); - } finally { - setIsSaving(false); + setFormData((prev) => ({ ...prev, [key]: maskedValue })); + } else { + setFormData((prev) => ({ ...prev, [key]: value })); + } + }; + + + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + 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; + 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}`); + } finally { + setIsSaving(false); + } + }; + if (loading) { + return ( + +
+ +

Carregando dados do médico...

+
+
+ ); } - }; - if (loading) { + return ( -
- -

Carregando dados do médico...

+
+
+
+

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

+

+ Atualize as informações do médico +

+
+ + + +
+ + + + {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) +

+