diff --git a/app/doctor/consultas/page.tsx b/app/doctor/consultas/page.tsx index 9332a8b..0bc765f 100644 --- a/app/doctor/consultas/page.tsx +++ b/app/doctor/consultas/page.tsx @@ -31,7 +31,7 @@ interface EnrichedAppointment { } export default function DoctorAppointmentsPage() { - const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole: 'medico' }); + const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole: "medico" }); const [allAppointments, setAllAppointments] = useState([]); const [isLoading, setIsLoading] = useState(true); @@ -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"); @@ -111,13 +111,22 @@ export default function DoctorAppointmentsPage() { return format(date, "EEEE, dd 'de' MMMM", { locale: ptBR }); }; + const statusPT: Record = { + confirmed: "Confirmada", + completed: "Concluída", + cancelled: "Cancelada", + requested: "Solicitada", + no_show: "oculta", + checked_in: "Aguardando", + }; + const getStatusVariant = (status: EnrichedAppointment['status']) => { switch (status) { - case "confirmed": case "checked_in": return "default"; - case "completed": return "secondary"; - case "cancelled": case "no_show": return "destructive"; - case "requested": return "outline"; - default: return "outline"; + case "confirmed": case "checked_in": return "text-foreground bg-blue-100 hover:bg-blue-150"; + case "completed": return "text-foreground bg-green-100 hover:bg-green-150"; + case "cancelled": case "no_show": return "text-foreground bg-red-200 hover:bg-red-250"; + case "requested": return "text-foreground bg-yellow-100 hover:bg-yellow-150"; + default: return "border-gray bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90"; } }; @@ -153,7 +162,7 @@ export default function DoctorAppointmentsPage() { Filtrar por DataSelecione um dia para ver os detalhes. - + @@ -188,10 +197,10 @@ export default function DoctorAppointmentsPage() { {format(scheduledAtDate, "HH:mm")} - + {/* Coluna 2: Status e Telefone */}
- {appointment.status.replace('_', ' ')} + {statusPT[appointment.status].replace('_', ' ')}
{appointment.patientPhone} diff --git a/app/doctor/dashboard/page.tsx b/app/doctor/dashboard/page.tsx index 060d8e4..45fd3d7 100644 --- a/app/doctor/dashboard/page.tsx +++ b/app/doctor/dashboard/page.tsx @@ -1,37 +1,62 @@ "use client"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Calendar, Clock, User, Trash2 } from "lucide-react"; -import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; 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"; import { usersService } from "@/services/usersApi.mjs"; import Sidebar from "@/components/Sidebar"; +import WeeklyScheduleCard from "@/components/ui/WeeklyScheduleCard"; type Availability = { - id: string; - doctor_id: string; - weekday: string; - start_time: string; - end_time: string; - slot_minutes: number; - appointment_type: string; - active: boolean; - created_at: string; - updated_at: string; - created_by: string; - updated_by: string | null; + id: string; + doctor_id: string; + weekday: string; + start_time: string; + end_time: string; + slot_minutes: number; + appointment_type: string; + active: boolean; + created_at: string; + updated_at: string; + created_by: string; + updated_by: string | null; }; type Schedule = { - weekday: object; + weekday: object; }; type Doctor = { @@ -61,36 +86,36 @@ type Doctor = { updated_by: string | null; max_days_in_advance: number; rating: number | null; -} +}; interface UserPermissions { - isAdmin: boolean; - isManager: boolean; - isDoctor: boolean; - isSecretary: boolean; - isAdminOrManager: boolean; + isAdmin: boolean; + isManager: boolean; + isDoctor: boolean; + isSecretary: boolean; + isAdminOrManager: boolean; } interface UserData { - user: { - id: string; - email: string; - email_confirmed_at: string | null; - created_at: string | null; - last_sign_in_at: string | null; - }; - profile: { - id: string; - full_name: string; - email: string; - phone: string; - avatar_url: string | null; - disabled: boolean; - created_at: string | null; - updated_at: string | null; - }; - roles: string[]; - permissions: UserPermissions; + user: { + id: string; + email: string; + email_confirmed_at: string | null; + created_at: string | null; + last_sign_in_at: string | null; + }; + profile: { + id: string; + full_name: string; + email: string; + phone: string; + avatar_url: string | null; + disabled: boolean; + created_at: string | null; + updated_at: string | null; + }; + roles: string[]; + permissions: UserPermissions; } interface Exception { @@ -98,7 +123,7 @@ interface Exception { doctor_id: string; date: string; // formato YYYY-MM-DD start_time: string | null; // null = dia inteiro - end_time: string | null; // null = dia inteiro + end_time: string | null; // null = dia inteiro kind: "bloqueio" | "disponibilidade"; // tipos conhecidos reason: string | null; // pode ser null created_at: string; // timestamp ISO @@ -106,7 +131,10 @@ interface Exception { } export default function PatientDashboard() { - const [loggedDoctor, setLoggedDoctor] = useState(); + // --- USA O HOOK DE AUTENTICAÇÃO PARA PEGAR O USUÁRIO LOGADO --- + const { user } = useAuthLayout({ requiredRole: ['medico'] }); + + const [loggedDoctor, setLoggedDoctor] = useState(null); const [userData, setUserData] = useState(); const [availability, setAvailability] = useState(null); const [exceptions, setExceptions] = useState([]); @@ -116,56 +144,79 @@ export default function PatientDashboard() { const [exceptionToDelete, setExceptionToDelete] = useState(null); const [error, setError] = useState(null); - // Mapa de tradução - const weekdaysPT: Record = { - sunday: "Domingo", - monday: "Segunda", - tuesday: "Terça", - wednesday: "Quarta", - thursday: "Quinta", - friday: "Sexta", - saturday: "Sábado", - }; + // --- ESTADOS PARA OS CARDS ATUALIZADOS --- + const [nextAppointment, setNextAppointment] = useState(null); + const [monthlyCount, setMonthlyCount] = useState(0); + const weekdaysPT: Record = { sunday: "Domingo", monday: "Segunda", tuesday: "Terça", wednesday: "Quarta", thursday: "Quinta", friday: "Sexta", saturday: "Sábado" }; + + // ▼▼▼ LÓGICA DE BUSCA CORRIGIDA E ATUALIZADA ▼▼▼ useEffect(() => { - const fetchData = async () => { - try { - const doctorsList: Doctor[] = await doctorsService.list(); - const doctor = doctorsList[0]; + const fetchData = async () => { + if (!user?.id) return; // Aguarda o usuário ser carregado - // Salva no estado - setLoggedDoctor(doctor); + 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); - // Busca disponibilidade - const availabilityList = await AvailabilityService.list(); - - // Filtra já com a variável local - const filteredAvail = availabilityList.filter( - (disp: { doctor_id: string }) => disp.doctor_id === doctor?.id - ); - setAvailability(filteredAvail); + if (!currentDoctor) { + setError("Perfil de médico não encontrado para este usuário."); + return; + } + setLoggedDoctor(currentDoctor); - // Busca exceções - const exceptionsList = await exceptionsService.list(); - const filteredExc = exceptionsList.filter( - (exc: { doctor_id: string }) => exc.doctor_id === doctor?.id - ); - console.log(exceptionsList) - setExceptions(filteredExc); + // Busca todos os dados necessários em paralelo + const [appointmentsList, patientsList, availabilityList, exceptionsList] = await Promise.all([ + appointmentsService.list(), + patientsService.list(), + AvailabilityService.list(), + exceptionsService.list() + ]); - } catch (e: any) { - alert(`${e?.error} ${e?.message}`); - } - }; + // Mapeia pacientes por ID para consulta rápida + const patientsMap = new Map(patientsList.map((p: any) => [p.id, p.full_name])); - fetchData(); -}, []); + // 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)); + + } catch (e: any) { + setError(e?.message || "Erro ao buscar dados do dashboard"); + console.error("Erro no dashboard:", e); + } + }; + + 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); @@ -173,106 +224,98 @@ export default function PatientDashboard() { 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, - }); - + 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", - }); + toast({ title: "Erro", description: e?.message || "Não foi possível deletar a exceção" }); } setDeleteDialogOpen(false); setExceptionToDelete(null); }; function formatAvailability(data: Availability[]) { - // Agrupar os horários por dia da semana + if (!data) return {}; const schedule = data.reduce((acc: any, item) => { const { weekday, start_time, end_time } = item; - - // Se o dia ainda não existe, cria o array - if (!acc[weekday]) { - acc[weekday] = []; - } - - // Adiciona o horário do dia - acc[weekday].push({ - start: start_time, - end: end_time, - }); - + if (!acc[weekday]) acc[weekday] = []; + acc[weekday].push({ start: start_time, end: end_time }); return acc; }, {} as Record); - return schedule; } - useEffect(() => { - if (availability) { - const formatted = formatAvailability(availability); - setSchedule(formatted); - } - }, [availability]); + useEffect(() => { + if (availability) { + const formatted = formatAvailability(availability); + setSchedule(formatted); + } + }, [availability]); - return ( - -
-
-

Dashboard

-

Bem-vindo ao seu portal de consultas médicas

-
+ return ( + +
+
+

Dashboard

+

+ Bem-vindo ao seu portal de consultas médicas +

+
+ {/* ▼▼▼ CARD "PRÓXIMA CONSULTA" CORRIGIDO PARA MOSTRAR NOME DO PACIENTE ▼▼▼ */} Próxima Consulta -
02 out
-

Dr. Silva - 14:30

+ {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 ▲▲▲ */} + {/* ▼▼▼ CARD "CONSULTAS ESTE MÊS" CORRIGIDO PARA CONTAGEM CORRETA ▼▼▼ */} Consultas Este Mês -
4
-

4 agendadas

+
{monthlyCount}
+

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

+ {/* ▲▲▲ FIM DO CARD ATUALIZADO ▲▲▲ */} - - - Perfil - - - -
100%
-

Dados completos

-
-
-
+ + + Perfil + + + +
100%
+

Dados completos

+
+
+
+ {/* O restante do código permanece o mesmo */}
@@ -316,31 +359,7 @@ export default function PatientDashboard() { Horário Semanal Confira rapidamente a sua disponibilidade da semana - - {["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"].map((day) => { - const times = schedule[day] || []; - return ( -
-
-
-

{weekdaysPT[day]}

-
-
- {times.length > 0 ? ( - times.map((t, i) => ( -

- {formatTime(t.start)}
{formatTime(t.end)} -

- )) - ) : ( -

Sem horário

- )} -
-
-
- ); - })} -
+ {loggedDoctor && }
@@ -353,7 +372,6 @@ export default function PatientDashboard() { {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", @@ -361,18 +379,18 @@ export default function PatientDashboard() { timeZone: "UTC" }); - const startTime = formatTime(ex.start_time); - const endTime = formatTime(ex.end_time); + const startTime = formatTime(ex.start_time); + const endTime = formatTime(ex.end_time); return (

{date}

-

+

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

@@ -411,4 +429,4 @@ export default function PatientDashboard() {
); -} +} \ No newline at end of file diff --git a/app/doctor/disponibilidade/page.tsx b/app/doctor/disponibilidade/page.tsx index 80b7816..7fd25fd 100644 --- a/app/doctor/disponibilidade/page.tsx +++ b/app/doctor/disponibilidade/page.tsx @@ -6,7 +6,13 @@ 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 { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { AvailabilityService } from "@/services/availabilityApi.mjs"; import { usersService } from "@/services/usersApi.mjs"; @@ -14,163 +20,203 @@ import { doctorsService } from "@/services/doctorsApi.mjs"; import { toast } from "@/hooks/use-toast"; import { useRouter } from "next/navigation"; -import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card"; -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, +} from "@/components/ui/card"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { Edit, Trash2 } from "lucide-react"; import { AvailabilityEditModal } from "@/components/ui/availability-edit-modal"; -import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; import Sidebar from "@/components/Sidebar"; // ... (Interfaces de tipo omitidas para brevidade, pois não foram alteradas) interface UserPermissions { - isAdmin: boolean; - isManager: boolean; - isDoctor: boolean; - isSecretary: boolean; - isAdminOrManager: boolean; + isAdmin: boolean; + isManager: boolean; + isDoctor: boolean; + isSecretary: boolean; + isAdminOrManager: boolean; } interface UserData { - user: { - id: string; - email: string; - email_confirmed_at: string | null; - created_at: string | null; - last_sign_in_at: string | null; - }; - profile: { - id: string; - full_name: string; - email: string; - phone: string; - avatar_url: string | null; - disabled: boolean; - created_at: string | null; - updated_at: string | null; - }; - roles: string[]; - permissions: UserPermissions; + user: { + id: string; + email: string; + email_confirmed_at: string | null; + created_at: string | null; + last_sign_in_at: string | null; + }; + profile: { + id: string; + full_name: string; + email: string; + phone: string; + avatar_url: string | null; + disabled: boolean; + created_at: string | null; + updated_at: string | null; + }; + roles: string[]; + permissions: UserPermissions; } type Doctor = { - id: string; - user_id: string | null; - crm: string; - crm_uf: string; - specialty: string; - full_name: string; - cpf: string; - email: string; - phone_mobile: string | null; - phone2: string | null; - cep: string | null; - street: string | null; - number: string | null; - complement: string | null; - neighborhood: string | null; - city: string | null; - state: string | null; - birth_date: string | null; - rg: string | null; - active: boolean; - created_at: string; - updated_at: string; - created_by: string; - updated_by: string | null; - max_days_in_advance: number; - rating: number | null; -} + id: string; + user_id: string | null; + crm: string; + crm_uf: string; + specialty: string; + full_name: string; + cpf: string; + email: string; + phone_mobile: string | null; + phone2: string | null; + cep: string | null; + street: string | null; + number: string | null; + complement: string | null; + neighborhood: string | null; + city: string | null; + state: string | null; + birth_date: string | null; + rg: string | null; + active: boolean; + created_at: string; + updated_at: string; + created_by: string; + updated_by: string | null; + max_days_in_advance: number; + rating: number | null; +}; type Availability = { - id: string; - doctor_id: string; - weekday: string; - start_time: string; - end_time: string; - slot_minutes: number; - appointment_type: string; - active: boolean; - created_at: string; - updated_at: string; - created_by: string; - updated_by: string | null; + id: string; + doctor_id: string; + weekday: string; + start_time: string; + end_time: string; + slot_minutes: number; + appointment_type: string; + active: boolean; + created_at: string; + updated_at: string; + created_by: string; + updated_by: string | null; }; export default function AvailabilityPage() { - const [error, setError] = useState(null); - const router = useRouter(); - const [isLoading, setIsLoading] = useState(false); - const [schedule, setSchedule] = useState>({}); - const formatTime = (time?: string | null) => time?.split(":")?.slice(0, 2).join(":") ?? ""; - const [userData, setUserData] = useState(); - const [availability, setAvailability] = useState(null); - const [doctorId, setDoctorId] = useState(); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [modalidadeConsulta, setModalidadeConsulta] = useState(""); - const [selectedAvailability, setSelectedAvailability] = useState(null); - const [isModalOpen, setIsModalOpen] = useState(false); - - const selectAvailability = (schedule: { start: string; end: string;}, day: string) => { - const selected = availability.filter((a: Availability) => - a.start_time === schedule.start && - a.end_time === schedule.end && - a.weekday === day - ); - setSelectedAvailability(selected[0]); - } + const [error, setError] = useState(null); + const router = useRouter(); + const [isLoading, setIsLoading] = useState(false); + const [schedule, setSchedule] = useState< + Record + >({}); + const formatTime = (time?: string | null) => + time?.split(":")?.slice(0, 2).join(":") ?? ""; + const [userData, setUserData] = useState(); + const [availability, setAvailability] = useState(null); + const [doctorId, setDoctorId] = useState(); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [modalidadeConsulta, setModalidadeConsulta] = useState(""); + const [selectedAvailability, setSelectedAvailability] = + useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); - const handleOpenModal = (schedule: { start: string; end: string;}, day: string) => { - selectAvailability(schedule, day) - setIsModalOpen(true); - }; - - const handleCloseModal = () => { - setSelectedAvailability(null); - setIsModalOpen(false); + const selectAvailability = ( + schedule: { start: string; end: string }, + day: string + ) => { + const selected = availability.filter( + (a: Availability) => + a.start_time === schedule.start && + a.end_time === schedule.end && + a.weekday === day + ); + setSelectedAvailability(selected[0]); + }; + + const handleOpenModal = ( + schedule: { start: string; end: string }, + day: string + ) => { + selectAvailability(schedule, day); + setIsModalOpen(true); + }; + + const handleCloseModal = () => { + setSelectedAvailability(null); + setIsModalOpen(false); + }; + + const handleEdit = async (formData: { + start_time: ""; + end_time: ""; + slot_minutes: ""; + appointment_type: ""; + id: ""; + }) => { + if (isLoading) return; + setIsLoading(true); + + const apiPayload = { + start_time: formData.start_time, + end_time: formData.end_time, + slot_minutes: formData.slot_minutes, + appointment_type: formData.appointment_type, }; + console.log(apiPayload); - const handleEdit = async (formData:{ start_time: "", end_time: "", slot_minutes: "", appointment_type: "", id:""}) => { - if (isLoading) return; - setIsLoading(true); + try { + const res = await AvailabilityService.update(formData.id, apiPayload); + console.log(res); - const apiPayload = { - start_time: formData.start_time, - end_time: formData.end_time, - slot_minutes: formData.slot_minutes, - appointment_type: formData.appointment_type, - }; - console.log(apiPayload); - - try { - const res = await AvailabilityService.update(formData.id, apiPayload); - console.log(res); - - let message = "disponibilidade editada com sucesso"; - try { - if (!res[0].id) { - throw new Error(`${res.error} ${res.message}` || "A API retornou erro"); - } else { - console.log(message); - } - } catch {} - - toast({ - title: "Sucesso", - description: message, - }); - router.push("#") - } catch (err: any) { - toast({ - title: "Erro", - description: err?.message || "Não foi possível editar a disponibilidade", - }); - } finally { - setIsLoading(false); - handleCloseModal(); - fetchData() + let message = "disponibilidade editada com sucesso"; + try { + if (!res[0].id) { + throw new Error( + `${res.error} ${res.message}` || "A API retornou erro" + ); + } else { + console.log(message); } - }; + } catch {} + + toast({ + title: "Sucesso", + description: message, + }); + router.push("#"); + } catch (err: any) { + toast({ + title: "Erro", + description: + err?.message || "Não foi possível editar a disponibilidade", + }); + } finally { + setIsLoading(false); + handleCloseModal(); + fetchData(); + } + }; // Mapa de tradução const weekdaysPT: Record = { @@ -183,95 +229,96 @@ export default function AvailabilityPage() { saturday: "Sábado", }; const fetchData = async () => { - try { - const loggedUser = await usersService.getMe(); - const doctorList = await doctorsService.list(); - setUserData(loggedUser); - const doctor = findDoctorById(loggedUser.user.id, doctorList); - setDoctorId(doctor?.id); - console.log(doctor); - // Busca disponibilidade - const availabilityList = await AvailabilityService.list(); - - // Filtra já com a variável local - const filteredAvail = availabilityList.filter( - (disp: { doctor_id: string }) => disp.doctor_id === doctor?.id - ); - setAvailability(filteredAvail); - } catch (e: any) { - alert(`${e?.error} ${e?.message}`); - } - }; - - useEffect(() => { - fetchData(); - }, []); - - // 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); - } - - - 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; - - // Se o dia ainda não existe, cria o array - if (!acc[weekday]) { - acc[weekday] = []; - } - - // Adiciona o horário do dia - acc[weekday].push({ - start: start_time, - end: end_time, - }); - - return acc; - }, {} as Record); - - return schedule; - } - - useEffect(() => { - if (availability) { - const formatted = formatAvailability(availability); - setSchedule(formatted); - } - }, [availability]); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - if (isLoading) return; - setIsLoading(true); - const form = e.currentTarget; - const formData = new FormData(form); - - const apiPayload = { - doctor_id: doctorId, - weekday: (formData.get("weekday") as string) || undefined, - start_time: (formData.get("horarioEntrada") as string) || undefined, - end_time: (formData.get("horarioSaida") as string) || undefined, - slot_minutes: Number(formData.get("duracaoConsulta")) || undefined, - appointment_type: modalidadeConsulta || undefined, - active: true, - }; - console.log(apiPayload); - try { - const res = await AvailabilityService.create(apiPayload); - console.log(res); + const loggedUser = await usersService.getMe(); + const doctorList = await doctorsService.list(); + setUserData(loggedUser); + const doctor = findDoctorById(loggedUser.user.id, doctorList); + setDoctorId(doctor?.id); + console.log(doctor); + // Busca disponibilidade + const availabilityList = await AvailabilityService.list(); + + // Filtra já com a variável local + const filteredAvail = availabilityList.filter( + (disp: { doctor_id: string }) => disp.doctor_id === doctor?.id + ); + setAvailability(filteredAvail); + } catch (e: any) { + alert(`${e?.error} ${e?.message}`); + } + }; - let message = "disponibilidade cadastrada com sucesso"; - try { - if (!res[0].id) { - throw new Error(`${res.error} ${res.message}` || "A API retornou erro"); - } else { - console.log(message); - } - } catch {} + useEffect(() => { + fetchData(); + }, []); + + // 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); + } + + 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; + + // Se o dia ainda não existe, cria o array + if (!acc[weekday]) { + acc[weekday] = []; + } + + // Adiciona o horário do dia + acc[weekday].push({ + start: start_time, + end: end_time, + }); + + return acc; + }, {} as Record); + + return schedule; + } + + useEffect(() => { + if (availability) { + const formatted = formatAvailability(availability); + setSchedule(formatted); + } + }, [availability]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (isLoading) return; + setIsLoading(true); + const form = e.currentTarget; + const formData = new FormData(form); + + const apiPayload = { + doctor_id: doctorId, + weekday: (formData.get("weekday") as string) || undefined, + start_time: (formData.get("horarioEntrada") as string) || undefined, + end_time: (formData.get("horarioSaida") as string) || undefined, + slot_minutes: Number(formData.get("duracaoConsulta")) || undefined, + appointment_type: modalidadeConsulta || undefined, + active: true, + }; + console.log(apiPayload); + + try { + const res = await AvailabilityService.create(apiPayload); + console.log(res); + + let message = "disponibilidade cadastrada com sucesso"; + try { + if (!res[0].id) { + throw new Error( + `${res.error} ${res.message}` || "A API retornou erro" + ); + } else { + console.log(message); + } + } catch {} toast({ title: "Sucesso", @@ -284,14 +331,18 @@ export default function AvailabilityPage() { description: err?.message || "Não foi possível criar a disponibilidade", }); } finally { + fetchData() setIsLoading(false); } }; - const openDeleteDialog = (schedule: { start: string; end: string;}, day: string) => { - selectAvailability(schedule, day) - setDeleteDialogOpen(true); - }; + const openDeleteDialog = ( + schedule: { start: string; end: string }, + day: string + ) => { + selectAvailability(schedule, day); + setDeleteDialogOpen(true); + }; const handleDeleteAvailability = async (AvailabilityId: string) => { try { @@ -318,101 +369,176 @@ export default function AvailabilityPage() { description: e?.message || "Não foi possível deletar a disponibilidade", }); } + fetchData() setDeleteDialogOpen(false); setSelectedAvailability(null); }; - return ( - -
-
-
-

Definir Disponibilidade

-

Defina sua disponibilidade para consultas

-
+ return ( + +
+
+
+

+ Definir Disponibilidade +

+

+ Defina sua disponibilidade para consultas{" "} +

+
+
+ +
+
+

Dados

+ +
+ {/* **AJUSTE DE RESPONSIVIDADE: DIAS DA SEMANA** */} +
+ + {/* O antigo 'flex gap-4 mt-2 flex-nowrap' foi substituído por um grid responsivo: */} +
+ + + + + + +
+
- -
-

Dados

+ {/* **AJUSTE DE RESPONSIVIDADE: HORÁRIO E DURAÇÃO** */} + {/* Ajustado para 1 coluna em móvel, 2 em tablet e 5 em desktop (mantendo o que já existia com ajustes) */} +
+
+ + +
+
+ + +
+
+ + +
+ {/* O Select de modalidade fica fora deste grid para ocupar uma linha inteira em telas menores, como no original, garantindo clareza */} +
-
- {/* **AJUSTE DE RESPONSIVIDADE: DIAS DA SEMANA** */} -
- - {/* O antigo 'flex gap-4 mt-2 flex-nowrap' foi substituído por um grid responsivo: */} -
- - - - - - - -
-
- - {/* **AJUSTE DE RESPONSIVIDADE: HORÁRIO E DURAÇÃO** */} - {/* Ajustado para 1 coluna em móvel, 2 em tablet e 5 em desktop (mantendo o que já existia com ajustes) */} -
-
- - -
-
- - -
-
- - -
- {/* O Select de modalidade fica fora deste grid para ocupar uma linha inteira em telas menores, como no original, garantindo clareza */} -
- -
- - -
-
-
+
+ + +
+
+
{/* **AJUSTE DE RESPONSIVIDADE: BOTÕES DE AÇÃO** */} {/* Alinha à direita em telas maiores e empilha (com o botão primário no final) em telas menores */} @@ -453,7 +579,7 @@ export default function AvailabilityPage() {
-

+

{formatTime(t.start)} - {formatTime(t.end)}

@@ -509,4 +635,4 @@ export default function AvailabilityPage() { ); -} \ 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 4dd6c87..2d4e45a 100644 --- a/app/doctor/medicos/page.tsx +++ b/app/doctor/medicos/page.tsx @@ -2,415 +2,439 @@ import { useEffect, useState, useCallback } from "react"; import Link from "next/link"; -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; -import { Eye, Edit, Calendar, Trash2, Loader2 } from "lucide-react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Eye, Edit, Calendar, Trash2, Loader2, MoreVertical, Filter } from "lucide-react"; import { api } from "@/services/api.mjs"; import { PatientDetailsModal } from "@/components/ui/patient-details-modal"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import Sidebar from "@/components/Sidebar"; interface Paciente { - id: string; - nome: string; - telefone: string; - cidade: string; - estado: string; - ultimoAtendimento?: string; - proximoAtendimento?: string; - email?: string; - birth_date?: string; - cpf?: string; - blood_type?: string; - weight_kg?: number; - height_m?: number; - street?: string; - number?: string; - complement?: string; - neighborhood?: string; - cep?: string; + id: string; + nome: string; + telefone: string; + cidade: string; + estado: string; + ultimoAtendimento?: string; + proximoAtendimento?: string; + email?: string; + birth_date?: string; + cpf?: string; + blood_type?: string; + weight_kg?: number; + height_m?: number; + street?: string; + number?: string; + complement?: string; + neighborhood?: string; + cep?: string; + // NOVOS CAMPOS PARA O FILTRO + convenio?: string; + vip?: string; } export default function PacientesPage() { - const [pacientes, setPacientes] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [selectedPatient, setSelectedPatient] = useState(null); - const [isModalOpen, setIsModalOpen] = useState(false); + const [pacientes, setPacientes] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [selectedPatient, setSelectedPatient] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); - // --- Lógica de Paginação INÍCIO --- - const [itemsPerPage, setItemsPerPage] = useState(5); - const [currentPage, setCurrentPage] = useState(1); + // --- ESTADOS DOS FILTROS --- + const [searchTerm, setSearchTerm] = useState(""); + const [convenioFilter, setConvenioFilter] = useState("todos"); + const [vipFilter, setVipFilter] = useState("todos"); - const totalPages = Math.ceil(pacientes.length / itemsPerPage); + // --- Lógica de Filtragem --- + const filteredPacientes = pacientes.filter((p) => { + // 1. Filtro de Texto (Nome ou Telefone) + const searchLower = searchTerm.toLowerCase(); + const matchesSearch = p.nome?.toLowerCase().includes(searchLower) || p.telefone?.includes(searchLower); - const indexOfLastItem = currentPage * itemsPerPage; - const indexOfFirstItem = indexOfLastItem - itemsPerPage; - const currentItems = pacientes.slice(indexOfFirstItem, indexOfLastItem); + // 2. Filtro de Convênio + // Se for "todos", passa. Se não, verifica se o convênio do paciente é igual ao selecionado. + const matchesConvenio = convenioFilter === "todos" || (p.convenio?.toLowerCase() === convenioFilter); - const paginate = (pageNumber: number) => setCurrentPage(pageNumber); + // 3. Filtro VIP + // Se for "todos", passa. Se não, verifica se o status VIP é igual ao selecionado. + const matchesVip = vipFilter === "todos" || (p.vip?.toLowerCase() === vipFilter); - // Funções de Navegação - const goToPrevPage = () => { - setCurrentPage((prev) => Math.max(1, prev - 1)); - }; + return matchesSearch && matchesConvenio && matchesVip; + }); - const goToNextPage = () => { - setCurrentPage((prev) => Math.min(totalPages, prev + 1)); - }; + // --- Lógica de Paginação --- + const [itemsPerPage, setItemsPerPage] = useState(10); + const [currentPage, setCurrentPage] = useState(1); - // Lógica para gerar os números das páginas visíveis (máximo de 5) - const getVisiblePageNumbers = (totalPages: number, currentPage: number) => { - const pages: number[] = []; - const maxVisiblePages = 5; - const halfRange = Math.floor(maxVisiblePages / 2); - let startPage = Math.max(1, currentPage - halfRange); - let endPage = Math.min(totalPages, currentPage + halfRange); + // Resetar página quando qualquer filtro mudar + useEffect(() => { + setCurrentPage(1); + }, [searchTerm, convenioFilter, vipFilter, itemsPerPage]); - if (endPage - startPage + 1 < maxVisiblePages) { - if (endPage === totalPages) { - startPage = Math.max(1, totalPages - maxVisiblePages + 1); - } - if (startPage === 1) { - endPage = Math.min(totalPages, maxVisiblePages); - } - } + const totalPages = Math.ceil(filteredPacientes.length / itemsPerPage); + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = filteredPacientes.slice(indexOfFirstItem, indexOfLastItem); - for (let i = startPage; i <= endPage; i++) { - pages.push(i); - } - return pages; - }; + const paginate = (pageNumber: number) => setCurrentPage(pageNumber); - const visiblePageNumbers = getVisiblePageNumbers(totalPages, currentPage); + const goToPrevPage = () => { + setCurrentPage((prev) => Math.max(1, prev - 1)); + }; - // Lógica para mudar itens por página, resetando para a página 1 - const handleItemsPerPageChange = (value: string) => { - setItemsPerPage(Number(value)); - setCurrentPage(1); - }; - // --- Lógica de Paginação FIM --- + const goToNextPage = () => { + setCurrentPage((prev) => Math.min(totalPages, prev + 1)); + }; + const getVisiblePageNumbers = (totalPages: number, currentPage: number) => { + const pages: number[] = []; + const maxVisiblePages = 5; + const halfRange = Math.floor(maxVisiblePages / 2); + let startPage = Math.max(1, currentPage - halfRange); + let endPage = Math.min(totalPages, currentPage + halfRange); - const handleOpenModal = (patient: Paciente) => { - setSelectedPatient(patient); - setIsModalOpen(true); - }; + if (endPage - startPage + 1 < maxVisiblePages) { + if (endPage === totalPages) { + startPage = Math.max(1, totalPages - maxVisiblePages + 1); + } + if (startPage === 1) { + endPage = Math.min(totalPages, maxVisiblePages); + } + } - const handleCloseModal = () => { - setSelectedPatient(null); - setIsModalOpen(false); - }; + for (let i = startPage; i <= endPage; i++) { + pages.push(i); + } + return pages; + }; - const formatDate = (dateString: string | null | undefined) => { - if (!dateString) return "N/A"; - try { - const date = new Date(dateString); - return new Intl.DateTimeFormat("pt-BR").format(date); - } catch (e) { - return dateString; // Retorna o string original se o formato for inválido - } - }; + const visiblePageNumbers = getVisiblePageNumbers(totalPages, currentPage); - const fetchPacientes = useCallback(async () => { - try { - setLoading(true); - setError(null); - const json = await api.get("/rest/v1/patients"); - const items = Array.isArray(json) - ? json - : Array.isArray(json?.data) - ? json.data - : []; + const handleItemsPerPageChange = (value: string) => { + setItemsPerPage(Number(value)); + setCurrentPage(1); + }; - const mapped: Paciente[] = items.map((p: any) => ({ - id: String(p.id ?? ""), - nome: p.full_name ?? "—", - telefone: p.phone_mobile ?? "N/A", - cidade: p.city ?? "N/A", - estado: p.state ?? "N/A", - ultimoAtendimento: formatDate(p.created_at), - proximoAtendimento: "N/A", // Necessita de lógica de agendamento real - email: p.email ?? "N/A", - birth_date: p.birth_date ?? "N/A", - cpf: p.cpf ?? "N/A", - blood_type: p.blood_type ?? "N/A", - weight_kg: p.weight_kg ?? 0, - height_m: p.height_m ?? 0, - street: p.street ?? "N/A", - number: p.number ?? "N/A", - complement: p.complement ?? "N/A", - neighborhood: p.neighborhood ?? "N/A", - cep: p.cep ?? "N/A", - })); + const handleOpenModal = (patient: Paciente) => { + setSelectedPatient(patient); + setIsModalOpen(true); + }; - setPacientes(mapped); - setCurrentPage(1); // Resetar a página ao carregar novos dados - } catch (e: any) { - console.error("Erro ao carregar pacientes:", e); - setError(e?.message || "Erro ao carregar pacientes"); - } finally { - setLoading(false); - } - }, []); + const handleCloseModal = () => { + setSelectedPatient(null); + setIsModalOpen(false); + }; - useEffect(() => { - fetchPacientes(); - }, [fetchPacientes]); + const formatDate = (dateString: string | null | undefined) => { + if (!dateString) return "N/A"; + try { + const date = new Date(dateString); + return new Intl.DateTimeFormat("pt-BR").format(date); + } catch (e) { + return dateString; + } + }; - return ( - -
- {/* Cabeçalho */} -
{/* Ajustado para flex-col em telas pequenas */} -
-

Pacientes

-

- Lista de pacientes vinculados -

-
- {/* Controles de filtro e novo paciente */} - {/* Alterado para que o Select e o Link ocupem a largura total em telas pequenas e fiquem lado a lado em telas maiores */} -
- - - - -
-
+ const fetchPacientes = useCallback(async () => { + try { + setLoading(true); + setError(null); + const json = await api.get("/rest/v1/patients"); + const items = Array.isArray(json) + ? json + : Array.isArray(json?.data) + ? json.data + : []; + const mapped: Paciente[] = items.map((p: any) => ({ + id: String(p.id ?? ""), + nome: p.full_name ?? "—", + telefone: p.phone_mobile ?? "N/A", + cidade: p.city ?? "N/A", + estado: p.state ?? "N/A", + ultimoAtendimento: formatDate(p.created_at), + proximoAtendimento: "N/A", + email: p.email ?? "N/A", + birth_date: p.birth_date ?? "N/A", + cpf: p.cpf ?? "N/A", + blood_type: p.blood_type ?? "N/A", + weight_kg: p.weight_kg ?? 0, + height_m: p.height_m ?? 0, + street: p.street ?? "N/A", + number: p.number ?? "N/A", + complement: p.complement ?? "N/A", + neighborhood: p.neighborhood ?? "N/A", + cep: p.cep ?? "N/A", + + // ⚠️ ATENÇÃO: Verifique o nome real desses campos na sua API + // Se a API não retorna, estou colocando valores padrão para teste + convenio: p.insurance_plan || p.convenio || "Unimed", // Exemplo: mapeie o campo correto + vip: p.is_vip ? "Sim" : "Não", // Exemplo: se for booleano converta para string + })); -
- {/* Tabela para Telas Médias e Grandes */} -
{/* Esconde em telas pequenas */} - - - - - - - - - - - - - - {loading ? ( - - - - ) : error ? ( - - - - ) : pacientes.length === 0 ? ( - - - - ) : ( - currentItems.map((p) => ( - - - - - - - - - - )) - )} - -
Nome - Telefone - - Cidade - - Estado - - Último atendimento - - Próximo atendimento - Ações
- - Carregando pacientes... -
{`Erro: ${error}`}
- Nenhum paciente encontrado -
{p.nome} - {p.telefone} - - {p.cidade} - - {p.estado} - - {p.ultimoAtendimento} - - {p.proximoAtendimento} - - - - - - - handleOpenModal(p)}> - - Ver detalhes - - - - - Laudos - - - alert(`Agenda para paciente ID: ${p.id}`)}> - - Ver agenda - - { - const newPacientes = pacientes.filter((pac) => pac.id !== p.id); - setPacientes(newPacientes); - alert(`Paciente ID: ${p.id} excluído`); - }} - className="text-red-600 focus:bg-red-50 focus:text-red-600" - > - - Excluir - - - -
-
+ setPacientes(mapped); + } catch (e: any) { + console.error("Erro ao carregar pacientes:", e); + setError(e?.message || "Erro ao carregar pacientes"); + } finally { + setLoading(false); + } + }, []); - {/* Layout em Cards/Lista para Telas Pequenas */} -
{/* Visível apenas em telas pequenas */} - {loading ? ( -
- - Carregando pacientes... -
- ) : error ? ( -
{`Erro: ${error}`}
- ) : pacientes.length === 0 ? ( -
- Nenhum paciente encontrado -
- ) : ( - currentItems.map((p) => ( -
-
{/* Adicionado padding à direita */} -
{/* Aumentado a fonte e break-words para evitar corte do nome */} - {p.nome || "—"} -
- {/* Removido o 'truncate' e adicionado 'break-words' no telefone */} -
- Telefone: **{p.telefone || "N/A"}** -
-
-
- - - - - - handleOpenModal(p)}> - - Ver detalhes - - - - - Laudos - - - alert(`Agenda para paciente ID: ${p.id}`)}> - - Ver agenda - - { - const newPacientes = pacientes.filter((pac) => pac.id !== p.id); - setPacientes(newPacientes); - alert(`Paciente ID: ${p.id} excluído`); - }} - className="text-red-600 focus:bg-red-50 focus:text-red-600" - > - - Excluir - - - -
-
- )) - )} -
+ useEffect(() => { + fetchPacientes(); + }, [fetchPacientes]); + return ( + +
+
+
+

Pacientes

+

+ Lista de pacientes vinculados +

+
+
- {/* Paginação */} - {totalPages > 1 && ( -
- - {/* Botão Anterior */} - - - {/* Números das Páginas */} - {visiblePageNumbers.map((number) => ( - - ))} - - {/* Botão Próximo */} - - -
- )} -
+ {/* --- BARRA DE PESQUISA COM FILTROS ATIVOS --- */} +
+ + {/* Input de Busca */} +
+ + setSearchTerm(e.target.value)} + className="border-0 focus-visible:ring-0 shadow-none bg-transparent px-0 h-auto text-base placeholder:text-muted-foreground" + />
+ {/* Filtros e Paginação */} +
+ + {/* FILTRO CONVÊNIO */} +
+ Convênio + +
+ + {/* FILTRO VIP */} +
+ VIP + +
+ + {/* PAGINAÇÃO */} +
+ +
+ +
+
+ + {/* Tabela de Dados */} +
+
+ + + + + + {/* Coluna Convênio visível para teste */} + + + + + + + + {loading ? ( + + + + ) : error ? ( + + + + ) : filteredPacientes.length === 0 ? ( + + + + ) : ( + currentItems.map((p) => ( + + + + + + + + + )) + )} + +
NomeTelefoneConvênioVIPÚltimo atendimentoAções
+ + Carregando pacientes... +
{`Erro: ${error}`}
+ Nenhum paciente encontrado com esses filtros. +
{p.nome}{p.telefone}{p.convenio}{p.vip}{p.ultimoAtendimento} + + + + + + handleOpenModal(p)}> + + Ver detalhes + + + + + Laudos + + + + +
+
+ + {/* Cards para Mobile */} +
+ {loading ? ( +
+ + Carregando... +
+ ) : filteredPacientes.length === 0 ? ( +
+ Nenhum paciente encontrado. +
+ ) : ( + currentItems.map((p) => ( +
+
+
+ {p.nome || "—"} +
+
+ {p.telefone} | {p.convenio} | VIP: {p.vip} +
+
+
+ + + + + + handleOpenModal(p)}> + + Ver detalhes + + + + + Laudos + + + + +
+
+ )) + )} +
+ + {/* Paginação */} + {totalPages > 1 && ( +
+ + + {visiblePageNumbers.map((number) => ( + + ))} + + +
+ )} +
+
+ - + Crie uma agora @@ -232,18 +232,21 @@ export default function LoginPage() { {/* Botões */}
+ {/* Botão Cancelar – Azul contornado */} + + {/* Botão Resetar Senha – Azul sólido */} diff --git a/app/manager/dashboard/page.tsx b/app/manager/dashboard/page.tsx index 6558bd0..11f5c19 100644 --- a/app/manager/dashboard/page.tsx +++ b/app/manager/dashboard/page.tsx @@ -1,190 +1,242 @@ "use client"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +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 { 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 - const [firstUser, setFirstUser] = useState(null); - const [loadingUser, setLoadingUser] = useState(true); + // 🔹 Estados para usuários + const [firstUser, setFirstUser] = useState(null); + const [loadingUser, setLoadingUser] = useState(true); - // 🔹 Estados para médicos - const [doctors, setDoctors] = useState([]); - const [loadingDoctors, setLoadingDoctors] = useState(true); + // 🔹 Estados para médicos + const [doctors, setDoctors] = useState([]); + const [loadingDoctors, setLoadingDoctors] = useState(true); - // 🔹 Buscar primeiro usuário + // 🔹 Buscar primeiro usuário (LÓGICA ATUALIZADA) useEffect(() => { async function fetchFirstUser() { + setLoadingUser(true); // Garante que o estado de loading inicie como true try { - const data = await usersService.list_roles(); - if (Array.isArray(data) && data.length > 0) { - setFirstUser(data[0]); + // 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); } } - fetchFirstUser(); - }, []); + fetchFirstUser(); + }, []); - // 🔹 Buscar 3 primeiros médicos - useEffect(() => { - async function fetchDoctors() { - try { - const data = await doctorsService.list(); // ajuste se seu service tiver outro método - if (Array.isArray(data)) { - setDoctors(data.slice(0, 3)); // pega os 3 primeiros - } - } catch (error) { - console.error("Erro ao carregar médicos:", error); - } finally { - setLoadingDoctors(false); - } + // 🔹 Buscar 3 primeiros médicos + useEffect(() => { + async function fetchDoctors() { + try { + const data = await doctorsService.list(); // ajuste se seu service tiver outro método + if (Array.isArray(data)) { + setDoctors(data.slice(0, 3)); // pega os 3 primeiros } + } catch (error) { + console.error("Erro ao carregar médicos:", error); + } finally { + setLoadingDoctors(false); + } + } - fetchDoctors(); - }, []); + fetchDoctors(); + }, []); - return ( - -
- {/* Cabeçalho */} -
-

Dashboard

-

Bem-vindo ao seu portal de consultas médicas

-
+ return ( + +
+ {/* Cabeçalho */} +
+

Dashboard

+

+ Bem-vindo ao seu portal de consultas médicas +

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

Relatórios disponíveis

-
-
- {/* Card 2 — Gestão de usuários */} - - - 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
- )} -
-
- {/* Card 3 — Perfil */} - - - Perfil - - - -
100%
-

Dados completos

-
-
+ {/* Card 2 — Gestão de usuários */} + + + + Gestão de usuários + + + + + {loadingUser ? ( +
+ Carregando usuário...
- - {/* Cards secundários */} -
- {/* Card — Ações rápidas */} - - - Ações Rápidas - Acesse rapidamente as principais funcionalidades - - - - - - - - - - - - - - - - - - {/* Card — Gestão de Médicos */} - - - 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"} -

-
-
- ))} -
- )} -
-
+ ) : firstUser ? ( + <> +
+ {firstUser.full_name || "Sem nome"} +
+

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

+ + ) : ( +
+ Nenhum usuário encontrado
-
- - ); -} + )} +
+
+ + {/* Card 3 — Perfil */} + + + Perfil + + + +
100%
+

Dados completos

+
+
+
+ + {/* Cards secundários */} +
+ {/* Card — Ações rápidas */} + + + Ações Rápidas + + Acesse rapidamente as principais funcionalidades + + + + + + + + + + + + + + + + + + + {/* Card — Gestão de Médicos */} + + + 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/disponibilidade/page.tsx b/app/manager/disponibilidade/page.tsx new file mode 100644 index 0000000..adfcd80 --- /dev/null +++ b/app/manager/disponibilidade/page.tsx @@ -0,0 +1,185 @@ +"use client"; + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import Sidebar from "@/components/Sidebar"; +import WeeklyScheduleCard from "@/components/ui/WeeklyScheduleCard"; + +import { useEffect, useState, useMemo } from "react"; + +import { AvailabilityService } from "@/services/availabilityApi.mjs"; +import { doctorsService } from "@/services/doctorsApi.mjs"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Button } from "@/components/ui/button"; +import { Filter } from "lucide-react"; + +type Doctor = { + id: string; + full_name: string; + specialty: string; + active: boolean; +}; + +type Availability = { + id: string; + doctor_id: string; + weekday: string; + start_time: string; + end_time: string; +}; + +export default function AllAvailabilities() { + const [availabilities, setAvailabilities] = useState(null); + const [doctors, setDoctors] = useState(null); + + // 🔎 Filtros + const [search, setSearch] = useState(""); + const [specialty, setSpecialty] = useState("all"); + + // 🔄 Paginação + const ITEMS_PER_PAGE = 6; + const [page, setPage] = useState(1); + + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchData = async () => { + try { + const doctorsList = await doctorsService.list(); + setDoctors(doctorsList); + + const availabilityList = await AvailabilityService.list(); + setAvailabilities(availabilityList); + } catch (e: any) { + alert(`${e?.error} ${e?.message}`); + } finally { + setLoading(false); + } + }; + + fetchData(); + }, []); + + // 🎯 Obter todas as especialidades existentes + const specialties = useMemo(() => { + if (!doctors) return []; + const unique = Array.from(new Set(doctors.map((d) => d.specialty))); + return unique; + }, [doctors]); + + // 🔍 Filtrar médicos por especialidade + nome + const filteredDoctors = useMemo(() => { + if (!doctors) return []; + + return doctors.filter((doctor) => (specialty === "all" ? true : doctor.specialty === specialty)).filter((doctor) => doctor.full_name.toLowerCase().includes(search.toLowerCase())); + }, [doctors, search, specialty]); + + // 📄 Paginação (após filtros!) + const totalPages = Math.ceil(filteredDoctors.length / ITEMS_PER_PAGE); + const paginatedDoctors = filteredDoctors.slice((page - 1) * ITEMS_PER_PAGE, page * ITEMS_PER_PAGE); + + const goNext = () => setPage((p) => Math.min(p + 1, totalPages)); + const goPrev = () => setPage((p) => Math.max(p - 1, 1)); + + if (loading) { + return ( + +
Carregando dados...
+
+ ); + } + + if (!doctors || !availabilities) { + return ( + +
Não foi possível carregar médicos ou disponibilidades.
+
+ ); + } + + return ( + +
+
+

Disponibilidade dos Médicos

+

Visualize a agenda semanal individual de cada médico.

+
+ + + {/* 🔎 Filtros */} +
+ {/* Filtro por nome */} + + { + setSearch(e.target.value); + setPage(1); + }} + className="w-full md:w-1/3" + /> + + {/* Filtro por especialidade */} + +
+
+
+ {/* GRID de cards */} +
+ {paginatedDoctors.map((doctor) => { + const doctorAvailabilities = availabilities.filter((a) => a.doctor_id === doctor.id); + + return ( + + + {doctor.full_name} + + + + + + + ); + })} +
+ + {/* 📄 Paginação */} + {totalPages > 1 && ( +
+ + + + Página {page} de {totalPages} + + + +
+ )} +
+
+ ); +} 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) +

+