"use client"; import { useEffect, useMemo, useState } from "react"; import { format, parseISO } from "date-fns"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { AlertCircle, ChevronDown, ChevronUp, FileImage, Loader2, Save, Upload, User, X, XCircle, Trash2 } from "lucide-react"; import { Paciente, PacienteInput, buscarCepAPI, atualizarPaciente, uploadFotoPaciente, removerFotoPaciente, adicionarAnexo, listarAnexos, removerAnexo, buscarPacientePorId, criarPaciente, } from "@/lib/api"; import { getAvatarPublicUrl } from '@/lib/api'; import { validarCPFLocal } from "@/lib/utils"; import { verificarCpfDuplicado } from "@/lib/api"; import { CredentialsDialog } from "@/components/credentials-dialog"; type Mode = "create" | "edit"; export interface PatientRegistrationFormProps { open?: boolean; onOpenChange?: (open: boolean) => void; patientId?: string | number | null; inline?: boolean; mode?: Mode; onSaved?: (paciente: Paciente) => void; onClose?: () => void; } type FormData = { photo: File | null; nome: string; nome_social: string; cpf: string; rg: string; sexo: string; birth_date: string; email: string; telefone: string; cep: string; logradouro: string; numero: string; complemento: string; bairro: string; cidade: string; estado: string; observacoes: string; anexos: File[]; }; const initial: FormData = { photo: null, nome: "", nome_social: "", cpf: "", rg: "", sexo: "", birth_date: "", email: "", telefone: "", cep: "", logradouro: "", numero: "", complemento: "", bairro: "", cidade: "", estado: "", observacoes: "", anexos: [], }; export function PatientRegistrationForm({ open = true, onOpenChange, patientId = null, inline = false, mode = "create", onSaved, onClose, }: PatientRegistrationFormProps) { const [form, setForm] = useState(initial); const [errors, setErrors] = useState>({}); const [expanded, setExpanded] = useState({ dados: true, contato: false, endereco: false, obs: false }); const [isSubmitting, setSubmitting] = useState(false); const [isUploadingPhoto, setUploadingPhoto] = useState(false); const [isSearchingCEP, setSearchingCEP] = useState(false); const [photoPreview, setPhotoPreview] = useState(null); const [serverAnexos, setServerAnexos] = useState([]); const [showCredentialsDialog, setShowCredentialsDialog] = useState(false); const [credentials, setCredentials] = useState<{ email: string; password: string; userName: string; userType: 'médico' | 'paciente'; } | null>(null); const title = useMemo(() => (mode === "create" ? "Cadastro de Paciente" : "Editar Paciente"), [mode]); useEffect(() => { async function load() { if (mode !== "edit" || patientId == null) return; try { const p = await buscarPacientePorId(String(patientId)); setForm((s) => ({ ...s, nome: p.full_name || "", nome_social: p.social_name || "", cpf: p.cpf || "", rg: p.rg || "", sexo: p.sex || "", birth_date: p.birth_date ? (() => { try { return format(parseISO(String(p.birth_date)), 'dd/MM/yyyy'); } catch { return String(p.birth_date); } })() : "", telefone: p.phone_mobile || "", email: p.email || "", cep: p.cep || "", logradouro: p.street || "", numero: p.number || "", complemento: p.complement || "", bairro: p.neighborhood || "", cidade: p.city || "", estado: p.state || "", observacoes: p.notes || "", })); const ax = await listarAnexos(String(patientId)).catch(() => []); setServerAnexos(Array.isArray(ax) ? ax : []); try { const url = getAvatarPublicUrl(String(patientId)); try { const head = await fetch(url, { method: 'HEAD' }); if (head.ok) { setPhotoPreview(url); } else { const get = await fetch(url, { method: 'GET' }); if (get.ok) { setPhotoPreview(url); } } } catch (inner) { /* ignore */ } } catch (detectErr) { /* ignore */ } } catch (err) { console.error('[PatientForm] Erro ao carregar paciente:', err); } } load(); }, [mode, patientId]); function setField(k: T, v: FormData[T]) { setForm((s) => ({ ...s, [k]: v })); if (errors[k as string]) setErrors((e) => ({ ...e, [k]: "" })); } function formatCPF(v: string) { const n = v.replace(/\D/g, "").slice(0, 11); return n.replace(/(\d{3})(\d{3})(\d{3})(\d{0,2})/, (_, a, b, c, d) => `${a}.${b}.${c}${d ? "-" + d : ""}`); } function handleCPFChange(v: string) { setField("cpf", formatCPF(v)); } function formatCEP(v: string) { const n = v.replace(/\D/g, "").slice(0, 8); return n.replace(/(\d{5})(\d{0,3})/, (_, a, b) => `${a}${b ? "-" + b : ""}`); } async function fillFromCEP(cep: string) { const clean = cep.replace(/\D/g, ""); if (clean.length !== 8) return; setSearchingCEP(true); try { const res = await buscarCepAPI(clean); if (res?.erro) setErrors((e) => ({ ...e, cep: "CEP não encontrado" })); else { setField("logradouro", res.logradouro ?? ""); setField("bairro", res.bairro ?? ""); setField("cidade", res.localidade ?? ""); setField("estado", res.uf ?? ""); } } catch { setErrors((e) => ({ ...e, cep: "Erro ao buscar CEP" })); } finally { setSearchingCEP(false); } } function validateLocal(): boolean { const e: Record = {}; if (!form.nome.trim()) e.nome = "Nome é obrigatório"; if (!form.cpf.trim()) e.cpf = "CPF é obrigatório"; if (mode === 'create' && !form.email.trim()) e.email = "Email é obrigatório para criar um usuário"; setErrors(e); return Object.keys(e).length === 0; } 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); } } catch {} return { full_name: form.nome, social_name: form.nome_social || null, cpf: form.cpf, rg: form.rg || null, sex: form.sexo || null, birth_date: isoDate, phone_mobile: form.telefone || null, email: form.email || null, cep: form.cep || null, street: form.logradouro || null, number: form.numero || null, complement: form.complemento || null, neighborhood: form.bairro || null, city: form.cidade || null, state: form.estado || null, notes: form.observacoes || null, }; } async function handleSubmit(ev: React.FormEvent) { ev.preventDefault(); if (!validateLocal()) return; try { if (!validarCPFLocal(form.cpf)) { setErrors((e) => ({ ...e, cpf: "CPF inválido" })); return; } if (mode === "create") { const existe = await verificarCpfDuplicado(form.cpf); if (existe) { setErrors((e) => ({ ...e, cpf: "CPF já cadastrado no sistema" })); return; } } } catch (err) { console.error("Erro ao validar CPF", err); setErrors({ submit: "Erro ao validar CPF." }); return; } setSubmitting(true); try { if (mode === "edit") { if (patientId == null) throw new Error("Paciente inexistente para edição"); const payload = toPayload(); const saved = await atualizarPaciente(String(patientId), payload); if (form.photo) { try { setUploadingPhoto(true); try { await removerFotoPaciente(String(patientId)); setPhotoPreview(null); } catch (remErr) { console.warn('[PatientForm] aviso: falha ao remover avatar antes do upload:', remErr); } await uploadFotoPaciente(String(patientId), form.photo); } catch (upErr) { console.warn('[PatientForm] Falha ao enviar foto do paciente:', upErr); alert('Paciente atualizado, mas falha ao enviar a foto. Tente novamente.'); } finally { setUploadingPhoto(false); } } onSaved?.(saved); alert("Paciente atualizado com sucesso!"); setForm(initial); setPhotoPreview(null); setServerAnexos([]); if (inline) onClose?.(); else onOpenChange?.(false); } else { // create const patientPayload = toPayload(); // 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); console.log('Perfil do paciente criado (via Function):', savedPatientProfile); 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' }); setShowCredentialsDialog(true); } if (form.photo) { try { setUploadingPhoto(true); const pacienteId = savedPatientProfile?.id || (savedPatientProfile && (savedPatientProfile as any).id); if (pacienteId) await uploadFotoPaciente(String(pacienteId), form.photo); } catch (upErr) { console.warn('[PatientForm] Falha ao enviar foto do paciente após criação:', upErr); alert('Paciente criado, mas falha ao enviar a foto. Você pode tentar novamente no perfil.'); } finally { setUploadingPhoto(false); } } onSaved?.(savedPatientProfile); setForm(initial); setPhotoPreview(null); setServerAnexos([]); if (inline) onClose?.(); else onOpenChange?.(false); } } catch (err: any) { console.error("❌ Erro no handleSubmit:", err); const userMessage = err?.message?.includes("toPayload") || err?.message?.includes("is not defined") ? "Erro ao processar os dados do formulário. Por favor, verifique os campos e tente novamente." : err?.message || "Erro ao salvar paciente. Por favor, tente novamente."; setErrors({ submit: userMessage }); } finally { setSubmitting(false); } } function handlePhoto(e: React.ChangeEvent) { const f = e.target.files?.[0]; if (!f) return; if (f.size > 5 * 1024 * 1024) { setErrors((e) => ({ ...e, photo: "Arquivo muito grande. Máx 5MB." })); return; } setField("photo", f); const fr = new FileReader(); fr.onload = (ev) => setPhotoPreview(String(ev.target?.result || "")); fr.readAsDataURL(f); } function addLocalAnexos(e: React.ChangeEvent) { const fs = Array.from(e.target.files || []); setField("anexos", [...form.anexos, ...fs]); } function removeLocalAnexo(idx: number) { const clone = [...form.anexos]; clone.splice(idx, 1); setField("anexos", clone); } async function handleRemoverFotoServidor() { if (mode !== "edit" || !patientId) return; try { setUploadingPhoto(true); await removerFotoPaciente(String(patientId)); setPhotoPreview(null); alert('Foto removida com sucesso.'); } catch (e: any) { console.warn('[PatientForm] erro ao remover foto do servidor', e); if (String(e?.message || '').includes('401')) { alert('Falha ao remover a foto: não autenticado. Faça login novamente e tente novamente.\nDetalhe: ' + (e?.message || '')); } else if (String(e?.message || '').includes('403')) { alert('Falha ao remover a foto: sem permissão. Verifique as permissões do token e se o storage aceita esse usuário.\nDetalhe: ' + (e?.message || '')); } else { alert(e?.message || 'Não foi possível remover a foto do storage. Veja console para detalhes.'); } } finally { setUploadingPhoto(false); } } async function handleRemoverAnexoServidor(anexoId: string | number) { if (mode !== "edit" || !patientId) return; try { await removerAnexo(String(patientId), anexoId); setServerAnexos((prev) => prev.filter((a) => String(a.id ?? a.anexo_id) !== String(anexoId))); } catch (e: any) { alert(e?.message || "Não foi possível remover o anexo."); } } const content = ( <> {errors.submit && ( {errors.submit} )}
{/* Personal data, contact, address, attachments... keep markup concise */} setExpanded((s) => ({ ...s, dados: !s.dados }))}> Dados Pessoais{expanded.dados ? : }
{photoPreview ? Preview : }
{mode === "edit" && ()} {errors.photo &&

{errors.photo}

}

Máximo 5MB

setField("nome", e.target.value)} className={errors.nome ? "border-destructive" : ""} />{errors.nome &&

{errors.nome}

}
setField("nome_social", e.target.value)} />
handleCPFChange(e.target.value)} placeholder="000.000.000-00" maxLength={14} className={errors.cpf ? "border-destructive" : ""} />{errors.cpf &&

{errors.cpf}

}
setField("rg", e.target.value)} />
{ const v = e.target.value.replace(/[^0-9\/]*/g, "").slice(0, 10); setField("birth_date", v); }} onBlur={() => { const raw = form.birth_date; const parts = raw.split(/\D+/).filter(Boolean); if (parts.length === 3) { const d = `${parts[0].padStart(2,'0')}/${parts[1].padStart(2,'0')}/${parts[2].padStart(4,'0')}`; setField("birth_date", d); } }} />
setExpanded((s) => ({ ...s, contato: !s.contato }))}> Contato{expanded.contato ? : }
setField("email", e.target.value)} />{errors.email &&

{errors.email}

}
setField("telefone", e.target.value)} />{errors.telefone &&

{errors.telefone}

}
setExpanded((s) => ({ ...s, endereco: !s.endereco }))}> Endereço{expanded.endereco ? : }
{ const v = formatCEP(e.target.value); setField("cep", v); if (v.replace(/\D/g, "").length === 8) fillFromCEP(v); }} placeholder="00000-000" maxLength={9} disabled={isSearchingCEP} className={errors.cep ? "border-destructive" : ""} />{isSearchingCEP && }
{errors.cep &&

{errors.cep}

}
setField("logradouro", e.target.value)} />
setField("numero", e.target.value)} />
setField("complemento", e.target.value)} />
setField("bairro", e.target.value)} />
setField("cidade", e.target.value)} />
setField("estado", e.target.value)} placeholder="UF" />
setExpanded((s) => ({ ...s, obs: !s.obs }))}> Observações e Anexos{expanded.obs ? : }