diff --git a/susconecta/components/forms/doctor-registration-form.tsx b/susconecta/components/forms/doctor-registration-form.tsx index 7c29f30..51530d7 100644 --- a/susconecta/components/forms/doctor-registration-form.tsx +++ b/susconecta/components/forms/doctor-registration-form.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; +import { parse } from 'date-fns'; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -429,18 +430,35 @@ function setField(k: T, v: FormData[T]) { } function toPayload(): MedicoInput { - // Converte dd/MM/yyyy para ISO (yyyy-MM-dd) se possível + // Converte data de nascimento para ISO (yyyy-MM-dd) tentando vários formatos let isoDate: string | null = null; try { - const parts = String(form.data_nascimento).split(/\D+/).filter(Boolean); - if (parts.length === 3) { - const [d, m, y] = parts; - const date = new Date(Number(y), Number(m) - 1, Number(d)); - if (!isNaN(date.getTime())) { - isoDate = date.toISOString().slice(0, 10); + const raw = String(form.data_nascimento || '').trim(); + if (raw) { + const formats = ['dd/MM/yyyy', 'dd-MM-yyyy', 'yyyy-MM-dd', 'MM/dd/yyyy']; + for (const f of formats) { + try { + const d = parse(raw, f, new Date()); + if (!isNaN(d.getTime())) { + isoDate = d.toISOString().slice(0, 10); + break; + } + } catch (e) { + // ignore and try next + } + } + if (!isoDate) { + const parts = raw.split(/\D+/).filter(Boolean); + if (parts.length === 3) { + const [d, m, y] = parts; + const date = new Date(Number(y), Number(m) - 1, Number(d)); + if (!isNaN(date.getTime())) isoDate = date.toISOString().slice(0, 10); + } } } - } catch {} + } catch (err) { + console.debug('[DoctorForm] parse data_nascimento failed:', form.data_nascimento, err); + } return { user_id: null, @@ -512,9 +530,42 @@ async function handleSubmit(ev: React.FormEvent) { console.log("Enviando os dados para a API:", medicoPayload); // 1. Cria o perfil do médico na tabela doctors - const savedDoctorProfile = await criarMedico(medicoPayload); + let savedDoctorProfile: any = await criarMedico(medicoPayload); console.log("✅ Perfil do médico criado:", savedDoctorProfile); + // Fallback: some create flows don't persist optional fields like birth_date/cep/sexo. + // If the returned object is missing those but our payload included them, + // attempt a PATCH (atualizarMedico) to force persistence, mirroring the edit flow. + try { + const resultAny = savedDoctorProfile as any; + let createdDoctorId: string | null = null; + if (resultAny) { + if (resultAny.id) createdDoctorId = String(resultAny.id); + else if (resultAny.doctor && resultAny.doctor.id) createdDoctorId = String(resultAny.doctor.id); + else if (resultAny.doctor_id) createdDoctorId = String(resultAny.doctor_id); + else if (Array.isArray(resultAny) && resultAny[0]?.id) createdDoctorId = String(resultAny[0].id); + } + + const missing: string[] = []; + if (createdDoctorId) { + if (!resultAny?.birth_date && medicoPayload.birth_date) missing.push('birth_date'); + if (!resultAny?.cep && medicoPayload.cep) missing.push('cep'); + // creation payload uses form.sexo (not medicoPayload.sex/sexo), so check form + if (!(resultAny?.sex || resultAny?.sexo) && form.sexo) missing.push('sex'); + } + + if (createdDoctorId && missing.length) { + console.debug('[DoctorForm] create returned without fields, attempting PATCH fallback for:', missing); + const patched = await atualizarMedico(String(createdDoctorId), medicoPayload).catch((e) => { console.warn('[DoctorForm] fallback PATCH failed:', e); return null; }); + if (patched) { + console.debug('[DoctorForm] fallback PATCH result:', patched); + savedDoctorProfile = patched; + } + } + } catch (e) { + console.warn('[DoctorForm] error during fallback PATCH:', e); + } + // The server-side Edge Function `criarMedico` should perform the privileged // operations (create doctor row and auth user) and return a normalized // envelope or the created doctor object. We rely on that single-call flow diff --git a/susconecta/components/forms/patient-registration-form.tsx b/susconecta/components/forms/patient-registration-form.tsx index 31cd412..c362189 100644 --- a/susconecta/components/forms/patient-registration-form.tsx +++ b/susconecta/components/forms/patient-registration-form.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; -import { format, parseISO } from "date-fns"; +import { format, parseISO, parse } from "date-fns"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -190,11 +190,35 @@ export function PatientRegistrationForm({ function toPayload(): PacienteInput { let isoDate: string | null = null; try { - const parts = String(form.birth_date).split(/\D+/).filter(Boolean); - if (parts.length === 3) { - const [d, m, y] = parts; const date = new Date(Number(y), Number(m) - 1, Number(d)); if (!isNaN(date.getTime())) isoDate = date.toISOString().slice(0, 10); + const raw = String(form.birth_date || '').trim(); + if (raw) { + // Try common formats first + const formats = ['dd/MM/yyyy', 'dd-MM-yyyy', 'yyyy-MM-dd', 'MM/dd/yyyy']; + for (const f of formats) { + try { + const d = parse(raw, f, new Date()); + if (!isNaN(d.getTime())) { + isoDate = d.toISOString().slice(0, 10); + break; + } + } catch (e) { + // ignore and try next format + } + } + + // Fallback: split numeric parts (handles 'dd mm yyyy' or 'ddmmyyyy' with separators) + if (!isoDate) { + const parts = raw.split(/\D+/).filter(Boolean); + if (parts.length === 3) { + const [d, m, y] = parts; + const date = new Date(Number(y), Number(m) - 1, Number(d)); + if (!isNaN(date.getTime())) isoDate = date.toISOString().slice(0, 10); + } + } } - } catch {} + } catch (err) { + console.debug('[PatientForm] parse birth_date failed:', form.birth_date, err); + } return { full_name: form.nome, social_name: form.nome_social || null, @@ -236,13 +260,42 @@ export function PatientRegistrationForm({ } else { // create const patientPayload = toPayload(); + // Debug helper: log the exact payload being sent to criarPaciente so + // we can inspect whether `sex`, `birth_date` and `cep` are present + // before the network request. This helps diagnose backends that + // ignore alternate field names or strip optional fields. + console.debug('[PatientForm] payload before criarPaciente:', patientPayload); // require phone when email present for single-call function if (form.email && form.email.includes('@') && (!form.telefone || !String(form.telefone).trim())) { setErrors((e) => ({ ...e, telefone: 'Telefone é obrigatório quando email é informado (fluxo de criação único).' })); setSubmitting(false); return; } - const savedPatientProfile = await criarPaciente(patientPayload); + let savedPatientProfile: any = await criarPaciente(patientPayload); console.log('Perfil do paciente criado (via Function):', savedPatientProfile); + // Fallback: some backend create flows (create-user-with-password) do not + // persist optional patient fields like sex/cep/birth_date. The edit flow + // (atualizarPaciente) writes directly to the patients table and works. + // To make create behave like edit, attempt a PATCH right after create + // when any of those fields are missing from the returned object. + try { + const pacienteId = savedPatientProfile?.id || savedPatientProfile?.patient_id || savedPatientProfile?.user_id; + const missing: string[] = []; + if (!savedPatientProfile?.sex && patientPayload.sex) missing.push('sex'); + if (!savedPatientProfile?.cep && patientPayload.cep) missing.push('cep'); + if (!savedPatientProfile?.birth_date && patientPayload.birth_date) missing.push('birth_date'); + + if (pacienteId && missing.length) { + console.debug('[PatientForm] criando paciente: campos faltando no retorno do create, tentando PATCH fallback:', missing); + const patched = await atualizarPaciente(String(pacienteId), patientPayload).catch((e) => { console.warn('[PatientForm] fallback PATCH falhou:', e); return null; }); + if (patched) { + console.debug('[PatientForm] fallback PATCH result:', patched); + savedPatientProfile = patched; + } + } + } catch (e) { + console.warn('[PatientForm] erro ao tentar fallback PATCH:', e); + } + const maybePassword = (savedPatientProfile as any)?.password || (savedPatientProfile as any)?.generated_password; if (maybePassword) { setCredentials({ email: (savedPatientProfile as any).email || form.email, password: String(maybePassword), userName: form.nome, userType: 'paciente' }); diff --git a/susconecta/lib/api.ts b/susconecta/lib/api.ts index 71758e6..46fdbd1 100644 --- a/susconecta/lib/api.ts +++ b/susconecta/lib/api.ts @@ -125,6 +125,7 @@ export type Medico = { // ...existing code... export type MedicoInput = { user_id?: string | null; + sexo?: string | null; crm: string; crm_uf: string; specialty: string; @@ -1526,6 +1527,29 @@ export async function criarPaciente(input: PacienteInput): Promise { if (input.social_name) payload.social_name = input.social_name; if (input.notes) payload.notes = input.notes; + // Add compatibility aliases so different backend schemas accept these fields + try { + if (input.cep) { + const cleanedCep = String(input.cep).replace(/\D/g, ''); + if (cleanedCep) { + payload.postal_code = cleanedCep; + payload.zip = cleanedCep; + } + } + } catch (e) { /* ignore */ } + + if (input.birth_date) { + payload.date_of_birth = input.birth_date; + payload.dob = input.birth_date; + payload.birthdate = input.birth_date; + } + + if (input.sex) { + payload.sex = input.sex; + payload.sexo = input.sex; + payload.gender = input.sex; + } + // Call the create-user-with-password endpoint (try functions path then root) const fnUrls = [ `${API_BASE}/functions/v1/create-user-with-password`, @@ -1987,7 +2011,25 @@ export async function criarMedico(input: MedicoInput): Promise { if (input.city) payload.city = input.city; if (input.state) payload.state = input.state; if (input.birth_date) payload.birth_date = input.birth_date; + if ((input as any).sexo) payload.sexo = (input as any).sexo; + if ((input as any).sexo) payload.gender = (input as any).sexo; if (input.rg) payload.rg = input.rg; + // compatibility aliases + try { + if ((input as any).cep) { + const cleaned = String((input as any).cep).replace(/\D/g, ''); + if (cleaned) { payload.postal_code = cleaned; payload.zip = cleaned; } + } + } catch (e) {} + if ((input as any).birth_date) { + payload.date_of_birth = (input as any).birth_date; + payload.dob = (input as any).birth_date; + payload.birthdate = (input as any).birth_date; + } + if ((input as any).sexo) { + payload.sexo = (input as any).sexo; + payload.gender = (input as any).sexo; + } const fnUrls = [ `${API_BASE}/functions/v1/create-user-with-password`,