diff --git a/1para.ajudar.na.refatoracao/.txt b/1para.ajudar.na.refatoracao/.txt new file mode 100644 index 0000000..5a4913e --- /dev/null +++ b/1para.ajudar.na.refatoracao/.txt @@ -0,0 +1,270 @@ +Olá! Vamos iniciar uma sessão de refatoração de componentes React/Next.js. +Seu Papel: A partir de agora, você atuará como um Desenvolvedor Sênior realizando uma revisão de código e refatoração. Seu objetivo não é apenas corrigir os erros óbvios, mas garantir que cada componente seja robusto, legível e siga as melhores práticas. +Contexto do Projeto: +A aplicação foi recentemente refatorada para usar uma camada de serviço (services/) para todas as chamadas de API e uma estrutura de layout automática do Next.js App Router. As páginas (page.tsx) estão desatualizadas e precisam ser corrigidas. +As 6 Regras de Ouro da Refatoração (Checklist Obrigatório): +Para CADA arquivo de página que eu fornecer, você deve aplicar TODAS as seguintes regras, sem exceção: +[UI] Limpeza do Layout Antigo: +REMOVER qualquer import de componentes de layout antigos (ex: import ManagerLayout from '...'). +REMOVER o componente wrapper do JSX (ex: as tags ...). A página deve retornar apenas seu próprio conteúdo. +[API] Substituição da Chamada de API: +LOCALIZAR a lógica de busca de dados (geralmente em useEffect). +SUBSTITUIR a chamada fetch antiga pela função correspondente da nossa camada de serviço (ex: fetch('/rest/v1/doctors') se torna medicosApi.list()). Use a documentação da API abaixo como referência. +[ESTADO] Gerenciamento de Estado Robusto: +IMPLEMENTAR estados explícitos para isLoading e error. +O estado principal de dados deve ser inicializado como um array ou objeto vazio. +Exemplo: +code +TypeScript +const [doctors, setDoctors] = useState([]); +const [isLoading, setIsLoading] = useState(true); +const [error, setError] = useState(null); +[TIPAGEM] Garantir a Segurança de Tipos (Type Safety): +IMPORTAR as interfaces de tipo (Doctor, Patient, etc.) do arquivo de serviço correspondente. +APLICAR essa interface ao estado do useState (ex: useState([])). Chega de any! +[UI] Feedback Visual para o Usuário: +ADICIONAR renderização condicional no JSX para os estados de carregamento e erro. +Se isLoading for true, exiba um componente de "Carregando..." (pode ser um simples texto ou um spinner). +Se error existir, exiba uma mensagem de erro para o usuário. +Se os dados estiverem vazios após o carregamento, exiba uma mensagem como "Nenhum médico encontrado". +[LIMPEZA] Limpeza Final do Código: +REMOVER quaisquer variáveis, estados ou imports que se tornaram inúteis após a refatoração. +Formato da Resposta (Obrigatório): +Para cada arquivo que eu enviar, sua resposta deve SEMPRE seguir este formato: +[CÓDIGO REATORADO] +Um único bloco de código contendo o arquivo page.tsx completo e corrigido, aplicando TODAS as 6 regras. +[RESUMO DAS ALTERAÇÕES] +Uma lista (bullet points) explicando as principais mudanças que você fez, justificando-as com base nas "Regras de Ouro". Ex: +[API & Estado]: Substituí o fetch por medicosApi.list() e adicionei os estados isLoading e error. +[Tipagem]: Importei a interface Doctor e a apliquei ao estado com useState([]). +[UI]: Adicionei renderização condicional para exibir mensagens de carregamento e erro. +[Limpeza]: Removi o import ManagerLayout e o wrapper do JSX. +Referência Essencial: Documentação da Camada de Serviço +(Use esta documentação para saber qual função de serviço chamar) +code +Code +// services/medicosApi.ts -> Funções: list, getById, create, update, delete. Tipos: Doctor. +// services/pacientesApi.ts -> Funções: list, getById, create, update, delete. Tipos: Patient. +// services/agendamentosApi.ts -> Funções: list, getById, create, update, delete, searchAvailableSlots. Tipos: Appointment. +// services/usuariosApi.ts -> Funções: listRoles, createUser, getCurrentUser, getFullData. Tipos: User, UserRole. +// ... (e assim por diante para todos os outros arquivos de serviço) +Estou pronto. Por favor, me envie o código do primeiro arquivo page.tsx para ser refatorado. + +====================================================================== +DOCUMENTAÇÃO DA CAMADA DE SERVIÇO (SERVICES) + +Este documento descreve a arquitetura e o funcionamento da camada de serviço, +responsável por toda a comunicação com o backend (Supabase API). + +ARQUITETURA GERAL + +A camada de serviço é composta por 12 arquivos, organizados por módulos +de funcionalidade da API. A arquitetura é centralizada em um arquivo +principal api.ts que configura o Axios, enquanto os outros arquivos +consomem essa configuração para realizar as chamadas específicas. + +ARQUIVO PRINCIPAL: api.ts + +Propósito: Este é o coração da camada de serviço. Ele cria e exporta +uma instância centralizada do Axios pré-configurada para interagir com +a API do Supabase. + +Configurações Principais: + +baseURL: Aponta para https://yuanqfswhberkoevtmfr.supabase.co. + +apikey: A chave pública (anon key) do Supabase é adicionada como +um cabeçalho padrão em TODAS as requisições. + +Interceptor de Requisição (Request Interceptor): + +Antes de qualquer requisição ser enviada, o interceptor busca por um +cookie chamado supabase-token. + +Se o token for encontrado, ele é adicionado ao cabeçalho Authorization +como um Bearer Token. + +Isso automatiza o processo de autenticação para todas as rotas protegidas, +evitando a necessidade de adicionar o token manualmente em cada chamada. + +Importante: Este arquivo NÃO contém nenhuma função de endpoint (como +login ou listagem de médicos). Sua única responsabilidade é a configuração +do cliente HTTP. + +MÓDULOS DE SERVIÇO + +Cada arquivo a seguir representa um módulo da API e exporta um objeto +com funções assíncronas para interagir com os endpoints. + +2.1. autenticacaoApi.ts + +Propósito: Gerencia todas as operações de autenticação. + +Observação: Este módulo utiliza fetch diretamente em vez da instância +api do Axios. Isso é necessário porque as funções de login são as que +OBTÊM o token, que o interceptor do Axios precisa para funcionar. Ele também +gerencia a gravação e remoção do supabase-token nos cookies do navegador. + +Funções Exportadas: + +loginWithEmailAndPassword(email, password): Envia credenciais para POST /auth/v1/token?grant_type=password, recebe o token de acesso e o armazena nos cookies. + +logout(): Envia uma requisição para POST /auth/v1/logout para invalidar a sessão no Supabase e remove o token dos cookies. + +sendMagicLink(email, redirectTo): Envia um email para POST /auth/v1/otp para login sem senha. + +renewToken(refreshToken): Usa um refresh token para obter um novo token de acesso via POST /auth/v1/token?grant_type=refresh_token. + +2.2. atribuicoesApi.ts + +Propósito: Gerencia as atribuições de pacientes a profissionais. + +Tabela Alvo: patient_assignments + +Funções Exportadas: + +list(): Busca a lista de todas as atribuições (GET /rest/v1/patient_assignments). + +create(data): Cria uma nova atribuição (POST /rest/v1/patient_assignments). + +2.3. avatarsApi.ts + +Propósito: Gerencia o upload e a remoção de avatares no Supabase Storage. + +Observação: As URLs e o método de envio (multipart/form-data) são +específicos para o serviço de Storage do Supabase. + +Funções Exportadas: + +upload(userId, file): Envia um arquivo de imagem para POST /storage/v1/object/avatars/{userId}/avatar. + +remove(userId): Deleta o avatar de um usuário (DELETE /storage/v1/object/avatars/{userId}/avatar). + +getPublicUrl(userId, ext): Monta e retorna a URL pública para acessar a imagem do avatar, não faz uma chamada de API. + +2.4. medicosApi.ts + +Propósito: Gerencia o CRUD (Create, Read, Update, Delete) completo para o recurso de médicos. + +Tabela Alvo: doctors + +Funções Exportadas: + +list(): GET /rest/v1/doctors + +getById(id): GET /rest/v1/doctors?id=eq.{id} + +create(data): POST /rest/v1/doctors + +update(id, data): PATCH /rest/v1/doctors?id=eq.{id} + +delete(id): DELETE /rest/v1/doctors?id=eq.{id} + +2.5. pacientesApi.ts + +Propósito: Gerencia o CRUD completo para o recurso de pacientes. + +Tabela Alvo: patients + +Funções Exportadas: CRUD padrão (list, getById, create, update, delete). + +2.6. perfisApi.ts + +Propósito: Gerencia a listagem e atualização de perfis de usuários. + +Tabela Alvo: profiles + +Funções Exportadas: + +list(): GET /rest/v1/profiles + +update(userId, data): PATCH /rest/v1/profiles?id=eq.{userId} + +2.7. relatoriosApi.ts + +Propósito: Gerencia o CRUD completo para o recurso de relatórios. + +Tabela Alvo: reports + +Funções Exportadas: CRUD padrão (list, getById, create, update, delete). + +2.8. usuariosApi.ts + +Propósito: Agrupa endpoints relacionados a usuários que não são CRUD direto da tabela profiles. + +Funções Exportadas: + +listRoles(): Busca as funções (roles) dos usuários (GET /rest/v1/user_roles). + +createUser(data): Chama uma Supabase Function para criar um novo usuário (POST /functions/v1/create-user). + +getCurrentUser(): Obtém os dados do usuário atualmente autenticado (GET /auth/v1/user). + +getFullData(userId): Chama uma Supabase Function para obter dados consolidados de um usuário (GET /functions/v1/user-info). + +2.9. smsApi.ts + +Propósito: Responsável pelo envio de mensagens SMS. + +Funções Exportadas: + +send(data): Chama a Supabase Function para enviar um SMS (POST /functions/v1/send-sms). + +2.10. agendamentosApi.ts + +Propósito: Gerencia o CRUD de agendamentos e a busca por horários. + +Tabela Alvo: appointments + +Funções Exportadas: + +CRUD padrão (list, getById, create, update, delete). + +searchAvailableSlots(data): Chama a Supabase Function para buscar horários disponíveis (POST /functions/v1/get-available-slots). + +2.11. disponibilidadeApi.ts + +Propósito: Gerencia o CRUD completo para a disponibilidade dos médicos. + +Tabela Alvo: doctor_availability + +Funções Exportadas: CRUD padrão (list, getById, create, update, delete). + +2.12. excecoesApi.ts + +Propósito: Gerencia as exceções (bloqueios/liberações) na agenda dos médicos. + +Tabela Alvo: doctor_exceptions + +Funções Exportadas: + +list(): GET /rest/v1/doctor_exceptions + +create(data): POST /rest/v1/doctor_exceptions + +delete(id): DELETE /rest/v1/doctor_exceptions?id=eq.{id} + +COMO UTILIZAR + +Para usar qualquer uma dessas funções em um componente ou página do Next.js, +basta importar o módulo desejado e chamar a função. O tratamento de erros +(com try/catch) e o gerenciamento de estado (loading, data, error) devem +ser feitos no local onde a função é chamada. + +Exemplo: + +code +TypeScript +download +content_copy +expand_less +import { medicosApi } from './services/medicosApi'; + +async function fetchDoctors() { +try { +const doctors = await medicosApi.list(); +console.log(doctors); +} catch (error) { +console.error("Erro ao buscar médicos:", error); +} +} \ No newline at end of file diff --git a/1para.ajudar.na.refatoracao/Default module.html b/1para.ajudar.na.refatoracao/Default module.html new file mode 100644 index 0000000..05b8154 --- /dev/null +++ b/1para.ajudar.na.refatoracao/Default module.html @@ -0,0 +1,65 @@ + + + + + + API Documentation - Apidog + + + + + + +
+ + + + + diff --git a/a.txt b/a.txt new file mode 100644 index 0000000..a0bed19 --- /dev/null +++ b/a.txt @@ -0,0 +1,6 @@ +Gabriel ok +Lucas +Danilo +Miguel +Pedro +Jhony \ No newline at end of file diff --git a/app/context/AppointmentsContext.tsx b/app/context/AppointmentsContext.tsx index f5ce2db..b64d349 100644 --- a/app/context/AppointmentsContext.tsx +++ b/app/context/AppointmentsContext.tsx @@ -1,99 +1,82 @@ "use client"; -import React, { createContext, useContext, useState, ReactNode } from 'react'; - -// A interface Appointment permanece a mesma -export interface Appointment { - id: string; - doctorName: string; - specialty: string; - date: string; - time: string; - location: string; - phone: string; - status: 'Agendada' | 'Realizada' | 'Cancelada'; - observations?: string; -} +import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback } from 'react'; +import { agendamentosApi, Appointment } from '@/services/agendamentosApi'; export interface AppointmentsContextType { appointments: Appointment[]; - addAppointment: (appointmentData: Omit) => void; - updateAppointment: (appointmentId: string, updatedData: Partial>) => void; - // [NOVA FUNÇÃO] Adicionando a assinatura da função de exclusão ao nosso contrato - deleteAppointment: (appointmentId: string) => void; + isLoading: boolean; + error: string | null; + fetchAppointments: () => Promise; + addAppointment: (appointmentData: Omit) => Promise; + updateAppointment: (appointmentId: string, updatedData: Partial>) => Promise; + deleteAppointment: (appointmentId: string) => Promise; } const AppointmentsContext = createContext(undefined); -// Os dados iniciais permanecem os mesmos -const initialAppointments: Appointment[] = [ - { - id: '1', - doctorName: "Dr. João Silva", - specialty: "Cardiologia", - date: "2024-08-15", - time: "14:30", - status: "Agendada", - location: "Consultório A - 2º andar", - phone: "(11) 3333-4444", - observations: "Paciente relata dor no peito.", - }, - { - id: '2', - doctorName: "Dra. Maria Santos", - specialty: "Dermatologia", - date: "2024-09-10", - time: "10:00", - status: "Agendada", - location: "Consultório B - 1º andar", - phone: "(11) 3333-5555", - }, - { - id: '3', - doctorName: "Dr. Pedro Costa", - specialty: "Ortopedia", - date: "2024-07-08", - time: "16:00", - status: "Realizada", - location: "Consultório C - 3º andar", - phone: "(11) 3333-6666", - }, -]; - export function AppointmentsProvider({ children }: { children: ReactNode }) { - const [appointments, setAppointments] = useState(initialAppointments); + const [appointments, setAppointments] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); - const addAppointment = (appointmentData: Omit) => { - const newAppointment: Appointment = { - id: Date.now().toString(), - status: 'Agendada', - ...appointmentData, - }; - setAppointments((prev) => [...prev, newAppointment]); + const fetchAppointments = useCallback(async () => { + setIsLoading(true); + setError(null); + try { + const data = await agendamentosApi.list(); + setAppointments(data || []); + } catch (err) { + console.error("Erro ao buscar agendamentos:", err); + setError("Não foi possível carregar os agendamentos."); + setAppointments([]); + } finally { + setIsLoading(false); + } + }, []); + + useEffect(() => { + fetchAppointments(); + }, [fetchAppointments]); + + const addAppointment = async (appointmentData: Omit) => { + try { + await agendamentosApi.create(appointmentData); + await fetchAppointments(); // Recarrega a lista para incluir o novo agendamento + } catch (err) { + console.error("Erro ao adicionar agendamento:", err); + setError("Falha ao criar o novo agendamento. Tente novamente."); + } }; - const updateAppointment = (appointmentId: string, updatedData: Partial>) => { - setAppointments((prev) => - prev.map((apt) => - apt.id === appointmentId ? { ...apt, ...updatedData } : apt - ) - ); + const updateAppointment = async (appointmentId: string, updatedData: Partial>) => { + try { + await agendamentosApi.update(appointmentId, updatedData); + await fetchAppointments(); // Recarrega a lista para refletir as alterações + } catch (err) { + console.error("Erro ao atualizar agendamento:", err); + setError("Falha ao atualizar o agendamento. Tente novamente."); + } }; - // [NOVA FUNÇÃO] Implementando a lógica de exclusão real - const deleteAppointment = (appointmentId: string) => { - setAppointments((prev) => - // O método 'filter' cria um novo array com todos os itens - // EXCETO aquele cujo ID corresponde ao que queremos excluir. - prev.filter((apt) => apt.id !== appointmentId) - ); + const deleteAppointment = async (appointmentId: string) => { + try { + await agendamentosApi.delete(appointmentId); + await fetchAppointments(); // Recarrega a lista para remover o item excluído + } catch (err) { + console.error("Erro ao excluir agendamento:", err); + setError("Falha ao excluir o agendamento. Tente novamente."); + } }; const value = { appointments, + isLoading, + error, + fetchAppointments, addAppointment, updateAppointment, - deleteAppointment, // Disponibilizando a nova função para os componentes + deleteAppointment, }; return ( diff --git a/app/doctor/dashboard/page.tsx b/app/doctor/dashboard/page.tsx index bf91156..d1946cc 100644 --- a/app/doctor/dashboard/page.tsx +++ b/app/doctor/dashboard/page.tsx @@ -1,47 +1,42 @@ "use client"; -import DoctorLayout from "@/components/doctor-layout"; -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 Link from "next/link"; import { useEffect, useState } from "react"; -import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; -import { AvailabilityService } from "@/services/availabilityApi.mjs"; -import { exceptionsService } from "@/services/exceptionApi.mjs"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { disponibilidadeApi, Availability } from "@/services/disponibilidadeApi"; +import { excecoesApi, Exception } from "@/services/excecoesApi"; import { toast } from "@/hooks/use-toast"; -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; -}; - -type Schedule = { - weekday: object; -}; - export default function PatientDashboard() { - const userInfo = JSON.parse(localStorage.getItem("user_info") || "{}"); - const doctorId = "3bb9ee4a-cfdd-4d81-b628-383907dfa225"; //userInfo.id; - const [availability, setAvailability] = useState(null); - const [exceptions, setExceptions] = useState(null); + const [availability, setAvailability] = useState([]); + const [exceptions, setExceptions] = useState([]); const [schedule, setSchedule] = useState>({}); - const formatTime = (time: string) => time.split(":").slice(0, 2).join(":"); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [patientToDelete, setPatientToDelete] = useState(null); + const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); - // Mapa de tradução + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [exceptionToDelete, setExceptionToDelete] = useState(null); + + const formatTime = (time: string) => time.split(":").slice(0, 2).join(":"); + const weekdaysPT: Record = { sunday: "Domingo", monday: "Segunda", @@ -54,76 +49,74 @@ export default function PatientDashboard() { useEffect(() => { const fetchData = async () => { + // TODO: Remover ID fixo e obter do usuário logado + const doctorId = "3bb9ee4a-cfdd-4d81-b628-383907dfa225"; + setIsLoading(true); + setError(null); try { - // fetch para disponibilidade - const response = await AvailabilityService.list(); - const filteredResponse = response.filter((disp: { doctor_id: any }) => disp.doctor_id == doctorId); - setAvailability(filteredResponse); - // fetch para exceções - const res = await exceptionsService.list(); - const filteredRes = res.filter((disp: { doctor_id: any }) => disp.doctor_id == doctorId); - setExceptions(filteredRes); + const [availabilityResponse, exceptionsResponse] = await Promise.all([ + disponibilidadeApi.list(), + excecoesApi.list(), + ]); + + const filteredAvailability = availabilityResponse.filter( + (disp) => disp.doctor_id === doctorId + ); + setAvailability(filteredAvailability); + + const filteredExceptions = exceptionsResponse.filter( + (exc) => exc.doctor_id === doctorId + ); + setExceptions(filteredExceptions); } catch (e: any) { - alert(`${e?.error} ${e?.message}`); + setError("Não foi possível carregar os dados do dashboard."); + console.error(e); + } finally { + setIsLoading(false); } }; fetchData(); }, []); - const openDeleteDialog = (patientId: string) => { - setPatientToDelete(patientId); + const openDeleteDialog = (exceptionId: string) => { + setExceptionToDelete(exceptionId); setDeleteDialogOpen(true); }; - const handleDeletePatient = async (patientId: string) => { - // Remove from current list (client-side deletion) + const handleDeleteException = async (exceptionId: string) => { try { - const res = await exceptionsService.delete(patientId); - - 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 {} - + await excecoesApi.delete(exceptionId); toast({ title: "Sucesso", - description: message, + description: "Exceção deletada com sucesso.", }); - - setExceptions((prev: any[]) => prev.filter((p) => String(p.id) !== String(patientId))); + setExceptions((prev) => prev.filter((ex) => ex.id !== exceptionId)); } catch (e: any) { toast({ title: "Erro", - description: e?.message || "Não foi possível deletar a exceção", + description: e?.message || "Não foi possível deletar a exceção.", }); + } finally { + setDeleteDialogOpen(false); + setExceptionToDelete(null); } - setDeleteDialogOpen(false); - setPatientToDelete(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; - - // 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); - + 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; } @@ -134,94 +127,102 @@ export default function PatientDashboard() { } }, [availability]); + if (isLoading) { + return
Carregando...
; + } + + if (error) { + return
Erro: {error}
; + } + return ( - -
-
-

Dashboard

-

Bem-vindo ao seu portal de consultas médicas

-
+
+
+

Dashboard

+

Bem-vindo ao seu portal de consultas médicas

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

Dr. Silva - 14:30

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

Dr. Silva - 14:30

+
+
- - - Consultas Este Mês - - - -
4
-

4 agendadas

-
-
+ + + Consultas Este Mês + + + +
4
+

4 agendadas

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

Dados completos

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

Dados completos

+
+
+
-
- - - Ações Rápidas - Acesse rapidamente as principais funcionalidades - - - - - - - +
+ + + Ações Rápidas + Acesse rapidamente as principais funcionalidades + + + + + + + - - - Próximas Consultas - Suas consultas agendadas - - -
-
-
-

Dr. João Santos

-

Cardiologia

-
-
-

02 out

-

14:30

-
+ + + Próximas Consultas + Suas consultas agendadas + + +
+
+
+

Dr. João Santos

+

Cardiologia

+
+
+

02 out

+

14:30

- - -
-
- - - Horário Semanal - Confira rapidamente a sua disponibilidade da semana - - - {["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"].map((day) => { +
+
+
+
+
+ + + Horário Semanal + Confira rapidamente a sua disponibilidade da semana + + + {Object.keys(schedule).length > 0 ? ( + ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"].map((day) => { const times = schedule[day] || []; return (
@@ -243,73 +244,75 @@ export default function PatientDashboard() {
); - })} - - -
-
- - - Exceções - Bloqueios e liberações eventuais de agenda - + }) + ) : ( +

Nenhum horário de disponibilidade encontrado.

+ )} + +
+
+
+ + + Exceções + Bloqueios e liberações eventuais de agenda + - - {exceptions && exceptions.length > 0 ? ( - exceptions.map((ex: any) => { - // Formata data e hora - const date = new Date(ex.date).toLocaleDateString("pt-BR", { - weekday: "long", - day: "2-digit", - month: "long", - }); + + {exceptions.length > 0 ? ( + exceptions.map((ex) => { + const date = new Date(ex.date).toLocaleDateString("pt-BR", { + weekday: "long", + day: "2-digit", + month: "long", + }); + 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}
- -

-
-
-

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

-

{ex.reason || "Sem motivo especificado"}

-
-
- -
+ return ( +
+
+
+

{date}

+

+ {startTime} - {endTime}
- +

+
+
+

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

+

{ex.reason || "Sem motivo especificado"}

+
+
+
- ); - }) - ) : ( -

Nenhuma exceção registrada.

- )} - - -
- - - - Confirmar exclusão - Tem certeza que deseja excluir este paciente? Esta ação não pode ser desfeita. - - - Cancelar - patientToDelete && handleDeletePatient(patientToDelete)} className="bg-red-600 hover:bg-red-700"> - Excluir - - - - +
+ ); + }) + ) : ( +

Nenhuma exceção registrada.

+ )} + +
- + + + + Confirmar exclusão + Tem certeza que deseja excluir esta exceção? Esta ação não pode ser desfeita. + + + Cancelar + exceptionToDelete && handleDeleteException(exceptionToDelete)} className="bg-red-600 hover:bg-red-700"> + Excluir + + + + +
); -} +} \ No newline at end of file diff --git a/app/doctor/disponibilidade/excecoes/page.tsx b/app/doctor/disponibilidade/excecoes/page.tsx index 115ff54..d364562 100644 --- a/app/doctor/disponibilidade/excecoes/page.tsx +++ b/app/doctor/disponibilidade/excecoes/page.tsx @@ -3,216 +3,243 @@ import type React from "react"; import Link from "next/link"; import { useState, useEffect } from "react"; -import DoctorLayout from "@/components/doctor-layout"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 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 { Clock, Calendar as CalendarIcon, MapPin, Phone, User, X, RefreshCw } from "lucide-react"; -import { Badge } from "@/components/ui/badge"; +import { Calendar as CalendarIcon, RefreshCw } from "lucide-react"; +import { Calendar } from "@/components/ui/calendar"; +import { format } from "date-fns"; import { useRouter } from "next/navigation"; import { toast } from "@/hooks/use-toast"; -import { exceptionsService } from "@/services/exceptionApi.mjs"; -// IMPORTAR O COMPONENTE CALENDÁRIO DA SHADCN -import { Calendar } from "@/components/ui/calendar"; -import { format } from "date-fns"; // Usaremos o date-fns para formatação e comparação de datas +// [TIPAGEM] Interfaces importadas ou definidas localmente conforme a documentação +import { excecoesApi } from "@/services/excecoesApi"; +import { agendamentosApi, Appointment } from "@/services/agendamentosApi"; +import { usuariosApi, User } from "@/services/usuariosApi"; -const APPOINTMENTS_STORAGE_KEY = "clinic-appointments"; - -// --- TIPAGEM DA CONSULTA SALVA NO LOCALSTORAGE --- -interface LocalStorageAppointment { - id: number; - patientName: string; - doctor: string; - specialty: string; - date: string; // Data no formato YYYY-MM-DD - time: string; // Hora no formato HH:MM - status: "agendada" | "confirmada" | "cancelada" | "realizada"; - location: string; - phone: string; +// Tipos definidos localmente baseados na documentação da API, já que não são exportados pelos serviços +interface DoctorExceptionCreate { + doctor_id: string; + created_by: string; + date: string; + start_time?: string; + end_time?: string; + kind: 'bloqueio' | 'liberacao'; + reason?: string | FormDataEntryValue | null; } -const LOGGED_IN_DOCTOR_NAME = "Dr. João Santos"; +interface Profile { + id: string; + full_name: string; + [key: string]: any; +} -// Função auxiliar para comparar se duas datas (Date objects) são o mesmo dia -const isSameDay = (date1: Date, date2: Date) => { - return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate(); -}; - -// --- COMPONENTE PRINCIPAL --- +interface UserInfoResponse { + user: User; + profile: Profile; + roles: string[]; +} export default function ExceptionPage() { - const [allAppointments, setAllAppointments] = useState([]); const router = useRouter(); - const [filteredAppointments, setFilteredAppointments] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const userInfo = JSON.parse(localStorage.getItem("user_info") || "{}"); - const doctorIdTemp = "3bb9ee4a-cfdd-4d81-b628-383907dfa225"; - const [tipo, setTipo] = useState(""); - // NOVO ESTADO 1: Armazena os dias com consultas (para o calendário) + // [ESTADO] Estados robustos para dados, carregamento e erro + const [appointments, setAppointments] = useState([]); + const [userInfo, setUserInfo] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); + const [bookedDays, setBookedDays] = useState([]); - - // NOVO ESTADO 2: Armazena a data selecionada no calendário const [selectedCalendarDate, setSelectedCalendarDate] = useState(new Date()); + const [tipo, setTipo] = useState<'bloqueio' | 'liberacao' | "">(""); - 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: doctorIdTemp, - created_by: doctorIdTemp, - date: selectedCalendarDate ? format(selectedCalendarDate, "yyyy-MM-dd") : "", - start_time: ((formData.get("horarioEntrada") + ":00") as string) || undefined, - end_time: ((formData.get("horarioSaida") + ":00") as string) || undefined, - kind: tipo || undefined, - reason: formData.get("reason"), - }; - console.log(apiPayload); + // [API] Função centralizada e corrigida para buscar dados + const fetchData = async () => { + setIsLoading(true); + setError(null); try { - const res = await exceptionsService.create(apiPayload); - console.log(res); + // A função getFullData precisa de um ID, então buscamos o usuário atual primeiro. + const currentUser = await usuariosApi.getCurrentUser(); + if (!currentUser || !currentUser.id) { + throw new Error("Usuário não autenticado."); + } - let message = "Exceção cadastrada com sucesso"; - try { - if (!res[0].id) { - throw new Error(`${res.error} ${res.message}` || "A API retornou erro"); - } else { - console.log(message); - } - } catch {} + const [userData, appointmentsData] = await Promise.all([ + usuariosApi.getFullData(currentUser.id), // Corrigido: Passando o ID do usuário + agendamentosApi.list() + ]); - toast({ - title: "Sucesso", - description: message, - }); - router.push("/doctor/dashboard"); // adicionar página para listar a disponibilidade + setUserInfo(userData as UserInfoResponse); + setAppointments(appointmentsData); + + const booked = appointmentsData.map(app => new Date(app.scheduled_at)); + setBookedDays(booked); } catch (err: any) { + const errorMessage = err.message || "Erro ao carregar os dados da agenda. Tente novamente."; + setError(errorMessage); toast({ - title: "Erro", - description: err?.message || "Não foi possível cadastrar a exceção", + title: "Erro de Carregamento", + description: errorMessage, + variant: "destructive", }); } finally { setIsLoading(false); } }; + useEffect(() => { + fetchData(); + }, []); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (isSubmitting || !userInfo?.profile?.id || !userInfo?.user?.id) return; + + setIsSubmitting(true); + const form = e.currentTarget; + const formData = new FormData(form); + + const apiPayload: DoctorExceptionCreate = { + doctor_id: userInfo.profile.id, + created_by: userInfo.user.id, + date: selectedCalendarDate ? format(selectedCalendarDate, "yyyy-MM-dd") : "", + start_time: (formData.get("horarioEntrada") as string) + ":00", + end_time: (formData.get("horarioSaida") as string) + ":00", + kind: tipo as 'bloqueio' | 'liberacao', + reason: formData.get("reason"), + }; + + try { + await excecoesApi.create(apiPayload); + toast({ + title: "Sucesso", + description: "Exceção cadastrada com sucesso.", + }); + router.push("/doctor/dashboard"); + } catch (err: any) { + toast({ + title: "Erro ao Salvar", + description: err?.message || "Não foi possível cadastrar a exceção.", + variant: "destructive", + }); + } finally { + setIsSubmitting(false); + } + }; + const displayDate = selectedCalendarDate ? new Date(selectedCalendarDate).toLocaleDateString("pt-BR", { weekday: "long", day: "2-digit", month: "long" }) : "Selecione uma data"; + // [UI] Feedback Visual para Carregamento e Erro + if (isLoading) { + return
Carregando dados da agenda...
; + } + + if (error) { + return
{error}
; + } + return ( - -
-
-

Adicione exceções

-

Altere a disponibilidade em casos especiais para o Dr. {userInfo.user_metadata.full_name}

+
+
+

Adicionar exceções

+

Altere a disponibilidade em casos especiais para o Dr. {userInfo?.profile?.full_name || '...'}

+
+ +
+

Agenda para: {displayDate}

+ +
+ +
+
+ + + + + Calendário + +

Selecione a data desejada.

+
+ + + +
-
-

Consultas para: {displayDate}

- -
- - {/* NOVO LAYOUT DE DUAS COLUNAS */} -
- {/* COLUNA 1: CALENDÁRIO */} -
- - - - - Calendário - -

Selecione a data desejada.

-
- - - -
-
- - {/* COLUNA 2: FORM PARA ADICIONAR EXCEÇÃO */} -
- {isLoading ? ( -

Carregando a agenda...

- ) : !selectedCalendarDate ? ( -

Selecione uma data.

- ) : ( -
-
-

Dados

-
-
-
- - -
-
- - -
-
- +
+ {!selectedCalendarDate ? ( +

Selecione uma data no calendário.

+ ) : ( + +
+

Dados da Exceção

+
+
-
-
-
-
- - - - +
+ + +
+
+ + +
- - )} -
+
+ +
+ + + + +
+ + )}
- +
); -} +} \ No newline at end of file diff --git a/app/doctor/disponibilidade/page.tsx b/app/doctor/disponibilidade/page.tsx index 127b93f..a8c7181 100644 --- a/app/doctor/disponibilidade/page.tsx +++ b/app/doctor/disponibilidade/page.tsx @@ -6,26 +6,33 @@ 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 DoctorLayout from "@/components/doctor-layout"; -import { AvailabilityService } from "@/services/availabilityApi.mjs"; +import { disponibilidadeApi, Availability } from "@/services/disponibilidadeApi"; import { toast } from "@/hooks/use-toast"; import { useRouter } from "next/navigation"; export default function AvailabilityPage() { + const [availabilities, setAvailabilities] = useState([]); + const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); - const router = useRouter(); - const [isLoading, setIsLoading] = useState(false); - const userInfo = JSON.parse(localStorage.getItem("user_info") || "{}"); - const doctorIdTemp = "3bb9ee4a-cfdd-4d81-b628-383907dfa225"; + const [isSubmitting, setIsSubmitting] = useState(false); const [modalidadeConsulta, setModalidadeConsulta] = useState(""); + const router = useRouter(); + + // TODO: Substituir pelo ID do médico autenticado + const doctorIdTemp = "3bb9ee4a-cfdd-4d81-b628-383907dfa225"; useEffect(() => { const fetchData = async () => { + setIsLoading(true); + setError(null); try { - const response = await AvailabilityService.list(); - console.log(response); + const response = await disponibilidadeApi.list(); + setAvailabilities(response || []); } catch (e: any) { - alert(`${e?.error} ${e?.message}`); + setError("Não foi possível carregar as disponibilidades existentes."); + console.error(e); + } finally { + setIsLoading(false); } }; @@ -34,14 +41,14 @@ export default function AvailabilityPage() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (isLoading) return; - setIsLoading(true); - const form = e.currentTarget; - const formData = new FormData(form); + if (isSubmitting) return; + setIsSubmitting(true); + setError(null); + const formData = new FormData(e.currentTarget); const apiPayload = { doctor_id: doctorIdTemp, - created_by: doctorIdTemp, + created_by: doctorIdTemp, // TODO: Substituir pelo ID do usuário autenticado weekday: (formData.get("weekday") as string) || undefined, start_time: (formData.get("horarioEntrada") as string) || undefined, end_time: (formData.get("horarioSaida") as string) || undefined, @@ -49,138 +56,134 @@ export default function AvailabilityPage() { 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 {} - + await disponibilidadeApi.create(apiPayload as Omit); toast({ title: "Sucesso", - description: message, + description: "Disponibilidade cadastrada com sucesso.", }); - router.push("#"); // adicionar página para listar a disponibilidade + router.push("/doctor/dashboard"); // Redirecionar para uma página de listagem ou dashboard } catch (err: any) { + setError(err?.message || "Não foi possível cadastrar a disponibilidade."); toast({ title: "Erro", - description: err?.message || "Não foi possível cadastrar o paciente", + description: err?.message || "Não foi possível cadastrar a disponibilidade.", + variant: "destructive", }); } finally { - setIsLoading(false); + setIsSubmitting(false); } }; + if (isLoading) { + return
Carregando...
; + } + + if (error) { + return
Erro: {error}
; + } + return ( - -
-
-
-

Definir Disponibilidade

-

Defina sua disponibilidade para consultas

+
+
+
+

Definir Disponibilidade

+

Defina sua disponibilidade para consultas

+
+
+ +
+
+

Dados

+ +
+
+
+ +
+ + + + + + + +
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
- -
-

Dados

- -
-
-
- -
- - - - - - - -
-
-
- -
-
- - -
-
- - -
-
- - -
-
- -
- - -
-
-
- -
- - - - - - - -
-
-
- +
+ + + + + + + +
+ +
); -} +} \ No newline at end of file diff --git a/app/doctor/layout.tsx b/app/doctor/layout.tsx new file mode 100644 index 0000000..b2efb2e --- /dev/null +++ b/app/doctor/layout.tsx @@ -0,0 +1,66 @@ +// Caminho: app/(doctor)/layout.tsx + +"use client"; + +import type React from "react"; +import { useState, useEffect } from "react"; +import { useRouter } from "next/navigation"; + +// Nossas importações centralizadas +import { usuariosApi } from "@/services/usuariosApi"; +import DashboardLayout, { UserProfile } from "@/components/layout/DashboardLayout"; +import { dashboardConfig } from "@/config/dashboard.config"; + +interface DoctorLayoutProps { + children: React.ReactNode; +} + +export default function DoctorLayout({ children }: DoctorLayoutProps) { + const [userProfile, setUserProfile] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const router = useRouter(); + + useEffect(() => { + const checkAuthentication = async () => { + try { + // Busca o usuário logado + const userData = await usuariosApi.getCurrentUser(); + // Pega a configuração específica do "doctor" + const config = dashboardConfig.doctor; + // Formata os dados para o perfil + setUserProfile(config.getUserProfile(userData)); + } catch (error) { + // Se falhar, redireciona para o login + router.push("/login"); + } finally { + setIsLoading(false); + } + }; + + checkAuthentication(); + }, [router]); + + // Enquanto verifica, mostra uma tela de carregamento + if (isLoading) { + return ( +
+

Verificando autenticação...

+
+ ); + } + + // Se não tiver perfil (redirect em andamento), não renderiza nada + if (!userProfile) { + return null; + } + + // Pega os itens de menu da configuração + const menuItems = dashboardConfig.doctor.menuItems; + + // Renderiza o layout genérico com as props corretas + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/app/doctor/login/page.tsx b/app/doctor/login/page.tsx deleted file mode 100644 index 051706e..0000000 --- a/app/doctor/login/page.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// Caminho: app/(doctor)/login/page.tsx - -import { LoginForm } from "@/components/LoginForm"; -import Link from "next/link"; // Adicionado para o link de "Voltar" - -export default function DoctorLoginPage() { - // NOTA: Esta página se tornou obsoleta com a criação do /login central. - // O ideal no futuro é deletar esta página e redirecionar os usuários. - - return ( -
-
-

Área do Médico

-

Acesse o sistema médico

- - {/* --- ALTERAÇÃO PRINCIPAL AQUI --- */} - {/* Chamando o LoginForm unificado sem props desnecessárias */} - - {/* Adicionamos um link de "Voltar" como filho (children) */} -
- - - Voltar à página inicial - - -
-
-
-
- ); -} \ No newline at end of file diff --git a/app/doctor/medicos/[id]/editar/page.tsx b/app/doctor/medicos/[id]/editar/page.tsx index 0049db2..f928d93 100644 --- a/app/doctor/medicos/[id]/editar/page.tsx +++ b/app/doctor/medicos/[id]/editar/page.tsx @@ -1,517 +1,155 @@ "use client"; -import type React from "react"; - -import { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback, FormEvent } from "react"; import { useRouter, useParams } from "next/navigation"; +import Link from "next/link"; +import { medicosApi, Doctor } from "@/services/medicosApi"; 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 { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; -import { ArrowLeft, Save } from "lucide-react"; -import Link from "next/link"; -import DoctorLayout from "@/components/doctor-layout"; +import { ArrowLeft, Save, Loader2 } from "lucide-react"; -// Mock data - in a real app, this would come from an API -const mockDoctors = [ - { - id: 1, - nome: "Dr. Carlos Silva", - cpf: "123.456.789-00", - rg: "12.345.678-9", - sexo: "masculino", - dataNascimento: "1980-05-15", - etnia: "branca", - raca: "caucasiana", - naturalidade: "Aracaju", - nacionalidade: "brasileira", - profissao: "Médico", - estadoCivil: "casado", - nomeMae: "Ana Silva", - nomePai: "José Silva", - nomeEsposo: "Maria Silva", - crm: "CRM/SE 12345", - especialidade: "Cardiologia", - email: "carlos@email.com", - celular: "(79) 99999-1234", - telefone1: "(79) 3214-5678", - telefone2: "", - cep: "49000-000", - endereco: "Rua dos Médicos, 123", - numero: "123", - complemento: "Sala 101", - bairro: "Centro", - cidade: "Aracaju", - estado: "SE", - tipoSanguineo: "A+", - peso: "80", - altura: "1.80", - alergias: "Nenhuma alergia conhecida", - convenio: "Particular", - plano: "Premium", - numeroMatricula: "123456789", - validadeCarteira: "2025-12-31", - observacoes: "Médico experiente", - }, -]; +// Usaremos Partial para o formulário, pois nem todos os campos são obrigatórios na edição. +type DoctorFormData = Partial; export default function EditarMedicoPage() { const router = useRouter(); const params = useParams(); - const doctorId = Number.parseInt(params.id as string); - - const [formData, setFormData] = useState({ - nome: "", - cpf: "", - rg: "", - sexo: "", - dataNascimento: "", - etnia: "", - raca: "", - naturalidade: "", - nacionalidade: "", - profissao: "", - estadoCivil: "", - nomeMae: "", - nomePai: "", - nomeEsposo: "", - crm: "", - especialidade: "", - email: "", - celular: "", - telefone1: "", - telefone2: "", - cep: "", - endereco: "", - numero: "", - complemento: "", - bairro: "", - cidade: "", - estado: "", - tipoSanguineo: "", - peso: "", - altura: "", - alergias: "", - convenio: "", - plano: "", - numeroMatricula: "", - validadeCarteira: "", - observacoes: "", - }); + const doctorId = params.id as string; + const [formData, setFormData] = useState({}); + const [isLoading, setIsLoading] = useState(true); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); const [isGuiaConvenio, setIsGuiaConvenio] = useState(false); const [validadeIndeterminada, setValidadeIndeterminada] = useState(false); - useEffect(() => { - // Load doctor data - const doctor = mockDoctors.find((d) => d.id === doctorId); - if (doctor) { - setFormData(doctor); + const fetchDoctor = useCallback(async () => { + if (!doctorId) { + setError("ID do médico não fornecido."); + setIsLoading(false); + return; + } + setIsLoading(true); + setError(null); + try { + const data = await medicosApi.getById(doctorId); + if (data) { + setFormData(data); + } else { + setError("Médico não encontrado."); + } + } catch (e) { + console.error("Erro ao carregar dados do médico:", e); + setError("Não foi possível carregar os dados do médico."); + } finally { + setIsLoading(false); } }, [doctorId]); - const handleInputChange = (field: string, value: string) => { + useEffect(() => { + fetchDoctor(); + }, [fetchDoctor]); + + const handleInputChange = (field: keyof DoctorFormData, value: string | boolean) => { setFormData((prev) => ({ ...prev, [field]: value })); }; - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: FormEvent) => { e.preventDefault(); - console.log("[v0] Updating doctor:", formData); - // Here you would typically send the data to your API - router.push("/medicos"); + setIsSubmitting(true); + setError(null); + try { + await medicosApi.update(doctorId, formData); + router.push("/medicos"); // Redireciona para a lista após o sucesso + } catch (e) { + console.error("Erro ao atualizar médico:", e); + setError("Não foi possível salvar as alterações. Tente novamente."); + } finally { + setIsSubmitting(false); + } }; + if (isLoading) { + return ( +
+ + Carregando dados do médico... +
+ ); + } + + if (error) { + return ( +
+

{error}

+ + + +
+ ); + } + return ( - -
-
- - - -
-

Editar Médico

-

Atualize as informações do médico

+
+
+ + + +
+

Editar Médico

+

Atualize as informações do médico

+
+
+ +
+
+

Dados Pessoais

+
+
+ + handleInputChange("full_name", e.target.value)} required /> +
+
+ + handleInputChange("cpf", e.target.value)} placeholder="000.000.000-00" required /> +
+
+ + handleInputChange("rg", e.target.value)} placeholder="00.000.000-0" /> +
+ {/* Outros campos do formulário seguem o mesmo padrão, usando formData.nome_da_coluna */}
- -
-

Dados Pessoais

+ {/* Outras seções do formulário (Profissional, Contato, etc.) */} + {/* Omitido para brevidade, mas a lógica de value e onChange deve seguir o padrão acima */} -
-
- - handleInputChange("nome", e.target.value)} required /> -
- -
- - handleInputChange("cpf", e.target.value)} placeholder="000.000.000-00" required /> -
- -
- - handleInputChange("rg", e.target.value)} placeholder="00.000.000-0" /> -
- -
- -
-
- handleInputChange("sexo", e.target.value)} className="w-4 h-4 text-blue-600" /> - -
-
- handleInputChange("sexo", e.target.value)} className="w-4 h-4 text-blue-600" /> - -
-
-
- -
- - handleInputChange("dataNascimento", e.target.value)} required /> -
- -
- - -
- -
- - -
- -
- - handleInputChange("naturalidade", e.target.value)} /> -
- -
- - -
- -
- - handleInputChange("profissao", e.target.value)} /> -
- -
- - -
- -
- - handleInputChange("nomeMae", e.target.value)} /> -
- -
- - handleInputChange("nomePai", e.target.value)} /> -
- -
- - handleInputChange("nomeEsposo", e.target.value)} /> -
-
- -
-
- setIsGuiaConvenio(checked === true)} /> - -
-
- -
- -