From 10f0693b6ee36f71a44596aa36d628a5971c8e99 Mon Sep 17 00:00:00 2001 From: Lucas Rodrigues Date: Tue, 30 Sep 2025 21:49:03 -0300 Subject: [PATCH] =?UTF-8?q?API=20de=20produ=C3=A7=C3=A3o=20CRUD=20m=C3=A9d?= =?UTF-8?q?icos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/cadastro/page.tsx | 4 +- app/manager/home/[id]/editar/page.tsx | 974 +++++++++++--------------- app/manager/home/novo/page.tsx | 911 +++++++++++++----------- app/manager/home/page.tsx | 486 +++++++------ app/manager/login/page.tsx | 1 - services/api.mjs | 60 +- services/doctorsApi.mjs | 9 + 7 files changed, 1183 insertions(+), 1262 deletions(-) diff --git a/app/cadastro/page.tsx b/app/cadastro/page.tsx index 5aefa10..8427531 100644 --- a/app/cadastro/page.tsx +++ b/app/cadastro/page.tsx @@ -117,7 +117,7 @@ export default function HomePage() { Gestão de usuários - + @@ -144,7 +144,7 @@ export default function HomePage() { Controle de pagamentos - + diff --git a/app/manager/home/[id]/editar/page.tsx b/app/manager/home/[id]/editar/page.tsx index 0085429..6619f67 100644 --- a/app/manager/home/[id]/editar/page.tsx +++ b/app/manager/home/[id]/editar/page.tsx @@ -1,170 +1,309 @@ "use client" -import type React from "react" - -import { useState, useEffect } from "react" +import { useState, useEffect, useCallback } from "react" import { useRouter, useParams } from "next/navigation" +import Link from "next/link" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Textarea } from "@/components/ui/textarea" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Checkbox } from "@/components/ui/checkbox" -import { ArrowLeft, Save } from "lucide-react" -import Link from "next/link" +import { Save, Loader2, ArrowLeft } from "lucide-react" import ManagerLayout from "@/components/manager-layout" +import { doctorsService } from "services/doctorsApi.mjs"; -// 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", - }, -] +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; +} +const apiMap: { [K in keyof DoctorFormData]: string | null } = { + nomeCompleto: 'full_name', crm: 'crm', crmEstado: 'crm_uf', especialidade: 'specialty', + cpf: 'cpf', email: 'email', dataNascimento: 'birth_date', rg: 'rg', + telefoneCelular: 'phone_mobile', telefone2: 'phone2', cep: 'cep', + endereco: 'street', numero: 'number', complemento: 'complement', + bairro: 'neighborhood', cidade: 'city', estado: 'state', ativo: 'active', + observacoes: null, +}; + +const defaultFormData: DoctorFormData = { + nomeCompleto: '', crm: '', crmEstado: '', especialidade: '', cpf: '', email: '', + dataNascimento: '', rg: '', telefoneCelular: '', telefone2: '', cep: '', + endereco: '', numero: '', complemento: '', bairro: '', cidade: '', estado: '', + ativo: true, observacoes: '', +}; + +const cleanNumber = (value: string): string => value.replace(/\D/g, ''); + +const formatCPF = (value: string): string => { + const cleaned = cleanNumber(value).substring(0, 11); + return cleaned.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4'); +}; + +const formatCEP = (value: string): string => { + const cleaned = cleanNumber(value).substring(0, 8); + return cleaned.replace(/(\d{5})(\d{3})/, '$1-$2'); +}; + +const formatPhoneMobile = (value: string): string => { + const cleaned = cleanNumber(value).substring(0, 11); + if (cleaned.length > 10) { + return cleaned.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3'); + } + return cleaned.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3'); +}; export default function EditarMedicoPage() { - const router = useRouter() - 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 [isGuiaConvenio, setIsGuiaConvenio] = useState(false) - const [validadeIndeterminada, setValidadeIndeterminada] = useState(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' + }; + useEffect(() => { - // Load doctor data - const doctor = mockDoctors.find((d) => d.id === doctorId) - if (doctor) { - setFormData(doctor) + 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); + } + }; + 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 })); } - }, [doctorId]) + }; - const handleInputChange = (field: string, value: string) => { - setFormData((prev) => ({ ...prev, [field]: value })) - } - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault() - console.log("[v0] Updating doctor:", formData) - // Here you would typically send the data to your API - router.push("/medicos") + + 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...

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

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

+

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

+
- -
-

Editar Médico

-

Atualize as informações do médico

-
-
-
-

Dados Pessoais

+ + + {error && ( +
+

Erro na Atualização:

+

{error}

+
+ )} -
-
- +
+

+ Dados Principais e Pessoais +

+
+
+ handleInputChange("nome", e.target.value)} - required + id="nomeCompleto" + value={formData.nomeCompleto} + onChange={(e) => 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" - required + maxLength={14} />
-
- -
- -
-
- 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)} - /> - -
-
- -
- -