riseup-squad20/susconecta/components/forms/doctor-registration-form.tsx

546 lines
24 KiB
TypeScript

'use client'
import type React from "react"
import { useState, useEffect } from "react"
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 { salvarMedico, DoctorFormData } from "@/lib/api";
import { formatCPF, formatCelular, formatRG } from "@/lib/formatters";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from "@/components/ui/command"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { ScrollArea } from "@/components/ui/scroll-area"
import {
Upload,
ChevronDown,
ChevronUp,
X,
FileImage,
User,
Phone,
MapPin,
Save,
XCircle,
AlertCircle,
Loader2,
Stethoscope,
Check,
ChevronsUpDown
} from "lucide-react"
interface DoctorRegistrationFormProps {
doctorData?: DoctorFormData | null
onClose?: () => void
}
const especialidadesMedicas = [
"Acupuntura",
"Alergia e Imunologia",
"Anestesiologia",
"Angiologia",
"Cancerologia (Oncologia)",
"Cardiologia",
"Cirurgia Cardiovascular",
"Cirurgia da Mão",
"Cirurgia de Cabeça e Pescoço",
"Cirurgia do Aparelho Digestivo",
"Cirurgia Geral",
"Cirurgia Oncológica",
"Cirurgia Pediátrica",
"Cirurgia Plástica",
"Cirurgia Torácica",
"Cirurgia Vascular",
"Clínica Médica",
"Coloproctologia",
"Dermatologia",
"Endocrinologia e Metabologia",
"Endoscopia",
"Gastroenterologia",
"Genética Médica",
"Geriatria",
"Ginecologia e Obstetrícia",
"Hematologia e Hemoterapia",
"Homeopatia",
"Infectologia",
"Mastologia",
"Medicina de Emergência",
"Medicina de Família e Comunidade",
"Medicina do Trabalho",
"Medicina de Tráfego",
"Medicina Esportiva",
"Medicina Física e Reabilitação",
"Medicina Intensiva",
"Medicina Legal e Perícia Médica",
"Medicina Nuclear",
"Medicina Preventiva e Social",
"Nefrologia",
"Neurocirurgia",
"Neurologia",
"Nutrologia",
"Oftalmologia",
"Ortopedia e Traumatologia",
"Otorrinolaringologia",
"Patologia",
"Patologia Clínica/Medicina Laboratorial",
"Pediatria",
"Pneumologia",
"Psiquiatria",
"Radiologia e Diagnóstico por Imagem",
"Radioterapia",
"Reumatologia",
"Urologia",
];
export function DoctorRegistrationForm({
doctorData = null,
onClose,
}: DoctorRegistrationFormProps) {
const initialFormData: DoctorFormData = {
nome: "",
nomeSocial: "",
cpf: "",
rg: "",
crm: "",
crmUf: "",
especialidade: "",
email: "",
celular: "",
dataNascimento: "",
sexo: "",
cep: "",
logradouro: "",
numero: "",
complemento: "",
bairro: "",
cidade: "",
estado: "",
observacoes: "",
photo: null,
}
const [formData, setFormData] = useState<DoctorFormData>(initialFormData)
const [expandedSections, setExpandedSections] = useState({
dadosPessoais: true,
dadosProfissionais: true,
contato: true,
endereco: true,
observacoes: false,
})
const [photoPreview, setPhotoPreview] = useState<string | null>(null)
const [isLoadingCep, setIsLoadingCep] = useState(false)
const [errors, setErrors] = useState<Record<string, string>>({})
const [isSubmitting, setIsSubmitting] = useState(false)
const [openEspecialidade, setOpenEspecialidade] = useState(false)
useEffect(() => {
if (doctorData) {
setFormData(doctorData);
if (doctorData.photo && doctorData.photo instanceof File) {
const reader = new FileReader()
reader.onload = (e) => setPhotoPreview(e.target?.result as string)
reader.readAsDataURL(doctorData.photo)
}
} else {
setFormData(initialFormData);
setPhotoPreview(null);
}
}, [doctorData])
const toggleSection = (section: keyof typeof expandedSections) => {
setExpandedSections((prev) => ({
...prev,
[section]: !prev[section],
}))
}
const handleInputChange = (field: keyof DoctorFormData, value: string | boolean | File | File[] | null) => {
let formattedValue = value;
if (typeof value === 'string') {
if (field === "cpf") {
formattedValue = formatCPF(value);
} else if (field === "celular") {
formattedValue = formatCelular(value);
} else if (field === "rg") {
formattedValue = formatRG(value);
}
}
setFormData((prev) => ({
...prev,
[field]: formattedValue,
}))
if (errors[field]) {
setErrors((prev) => {
const newErrors = { ...prev }
delete newErrors[field]
return newErrors
})
}
}
const handlePhotoUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (file) {
if (file.size > 5 * 1024 * 1024) { // 5MB limit
setErrors((prev) => ({ ...prev, photo: "Arquivo muito grande. Máximo 5MB." }))
return
}
handleInputChange("photo", file)
const reader = new FileReader()
reader.onload = (e) => setPhotoPreview(e.target?.result as string)
reader.readAsDataURL(file)
}
}
const searchCEP = async (cep: string) => {
const cleanCEP = cep.replace(/\D/g, "")
if (cleanCEP.length !== 8) return
setIsLoadingCep(true)
try {
const response = await fetch(`https://viacep.com.br/ws/${cleanCEP}/json/`)
const data = await response.json()
if (data.erro) {
setErrors((prev) => ({ ...prev, cep: "CEP não encontrado" }))
} else {
handleInputChange("logradouro", data.logradouro || "")
handleInputChange("bairro", data.bairro || "")
handleInputChange("cidade", data.localidade || "")
handleInputChange("estado", data.uf || "")
}
} catch (error) {
console.error("Erro ao buscar CEP:", error)
setErrors((prev) => ({ ...prev, cep: "Erro ao buscar CEP. Tente novamente." }))
} finally {
setIsLoadingCep(false)
}
}
const validateForm = () => {
const newErrors: Record<string, string> = {}
if (!formData.nome.trim()) newErrors.nome = "Nome é obrigatório"
if (!formData.cpf.trim()) newErrors.cpf = "CPF é obrigatório"
if (!formData.crm.trim()) newErrors.crm = "CRM é obrigatório"
if (!formData.crmUf.trim()) newErrors.crmUf = "UF do CRM é obrigatória"
if (!formData.especialidade.trim()) newErrors.especialidade = "Especialidade é obrigatória"
if (!formData.celular.trim()) newErrors.celular = "Celular é obrigatório"
if (!formData.email.trim()) newErrors.email = "E-mail é obrigatório"
if (!formData.cep.trim()) newErrors.cep = "CEP é obrigatório"
if (!formData.logradouro.trim()) newErrors.logradouro = "Logradouro é obrigatório"
if (!formData.numero.trim()) newErrors.numero = "Número é obrigatório"
if (!formData.bairro.trim()) newErrors.bairro = "Bairro é obrigatório"
if (!formData.cidade.trim()) newErrors.cidade = "Cidade é obrigatória"
if (!formData.estado.trim()) newErrors.estado = "Estado é obrigatório"
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault()
if (!validateForm()) return
setIsSubmitting(true)
try {
await salvarMedico(formData)
alert(doctorData?.id ? "Médico atualizado com sucesso!" : "Médico cadastrado com sucesso!")
if (onClose) onClose()
} catch (error) {
setErrors({ submit: "Erro ao salvar médico. Tente novamente." })
} finally {
setIsSubmitting(false)
}
}
return (
<form onSubmit={handleSubmit} className="space-y-6">
{errors.submit && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{errors.submit}</AlertDescription>
</Alert>
)}
{/* Dados Pessoais */}
<Collapsible open={expandedSections.dadosPessoais} onOpenChange={() => toggleSection("dadosPessoais")}>
<Card>
<CollapsibleTrigger asChild>
<CardHeader className="cursor-pointer">
<CardTitle className="flex items-center justify-between">
<span className="flex items-center gap-2"><User className="h-4 w-4" />Dados Pessoais</span>
{expandedSections.dadosPessoais ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</CardTitle>
</CardHeader>
</CollapsibleTrigger>
<CollapsibleContent>
<CardContent className="space-y-4 pt-4">
<div className="flex items-center gap-4">
<div className="w-24 h-24 border-2 border-dashed rounded-lg flex items-center justify-center overflow-hidden">
{photoPreview ? (
<img src={photoPreview} alt="Preview" className="w-full h-full object-cover" />
) : (
<FileImage className="h-8 w-8 text-muted-foreground" />
)}
</div>
<div className="space-y-2">
<Label htmlFor="photo" className="cursor-pointer">
<Button type="button" variant="outline" asChild>
<label htmlFor="photo" className="cursor-pointer"><Upload className="mr-2 h-4 w-4" />Carregar Foto</label>
</Button>
</Label>
<Input id="photo" type="file" accept="image/*" className="hidden" onChange={handlePhotoUpload} />
{errors.photo && <p className="text-sm text-destructive">{errors.photo}</p>}
<p className="text-xs text-muted-foreground">Máximo 5MB</p>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="nome">Nome *</Label>
<Input id="nome" value={formData.nome} onChange={(e) => handleInputChange("nome", e.target.value)} className={errors.nome ? "border-destructive" : ""} />
{errors.nome && <p className="text-sm text-destructive">{errors.nome}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="nomeSocial">Nome Social</Label>
<Input id="nomeSocial" value={formData.nomeSocial || ''} onChange={(e) => handleInputChange("nomeSocial", e.target.value)} />
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="cpf">CPF *</Label>
<Input id="cpf" value={formData.cpf} onChange={(e) => handleInputChange("cpf", e.target.value)} placeholder="000.000.000-00" className={errors.cpf ? "border-destructive" : ""} />
{errors.cpf && <p className="text-sm text-destructive">{errors.cpf}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="rg">RG</Label>
<Input id="rg" value={formData.rg} onChange={(e) => handleInputChange("rg", e.target.value)} placeholder="00.000.000-0"/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label>Sexo</Label>
<Select value={formData.sexo} onValueChange={(value) => handleInputChange("sexo", value)}>
<SelectTrigger><SelectValue placeholder="Selecione o sexo" /></SelectTrigger>
<SelectContent>
<SelectItem value="masculino">Masculino</SelectItem>
<SelectItem value="feminino">Feminino</SelectItem>
<SelectItem value="outro">Outro</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="dataNascimento">Data de Nascimento</Label>
<Input id="dataNascimento" type="date" value={formData.dataNascimento} onChange={(e) => handleInputChange("dataNascimento", e.target.value)} />
</div>
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* Dados Profissionais */}
<Collapsible open={expandedSections.dadosProfissionais} onOpenChange={() => toggleSection("dadosProfissionais")}>
<Card>
<CollapsibleTrigger asChild>
<CardHeader className="cursor-pointer">
<CardTitle className="flex items-center justify-between">
<span className="flex items-center gap-2"><Stethoscope className="h-4 w-4" />Dados Profissionais</span>
{expandedSections.dadosProfissionais ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</CardTitle>
</CardHeader>
</CollapsibleTrigger>
<CollapsibleContent>
<CardContent className="space-y-4 pt-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="crm">CRM *</Label>
<Input id="crm" value={formData.crm} onChange={(e) => handleInputChange("crm", e.target.value)} className={errors.crm ? "border-destructive" : ""} />
{errors.crm && <p className="text-sm text-destructive">{errors.crm}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="crmUf">UF do CRM *</Label>
<Input id="crmUf" value={formData.crmUf} onChange={(e) => handleInputChange("crmUf", e.target.value)} className={errors.crmUf ? "border-destructive" : ""} />
{errors.crmUf && <p className="text-sm text-destructive">{errors.crmUf}</p>}
</div>
</div>
<div className="space-y-2">
<Label htmlFor="especialidade">Especialidade *</Label>
<Popover open={openEspecialidade} onOpenChange={setOpenEspecialidade}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={openEspecialidade}
className={`w-full justify-between ${errors.especialidade ? "border-destructive" : ""}`}>
{formData.especialidade || "Selecione a especialidade"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[--radix-popover-trigger-width] p-0">
<Command>
<CommandInput placeholder="Buscar especialidade..." />
<CommandEmpty>Nenhuma especialidade encontrada.</CommandEmpty>
<ScrollArea className="h-72">
<CommandGroup>
{especialidadesMedicas.map((especialidade) => (
<CommandItem
key={especialidade}
value={especialidade}
onSelect={(currentValue) => {
handleInputChange("especialidade", currentValue === formData.especialidade ? "" : currentValue)
setOpenEspecialidade(false)
}}>
<Check
className={`mr-2 h-4 w-4 ${formData.especialidade === especialidade ? "opacity-100" : "opacity-0"}`}
/>
{especialidade}
</CommandItem>
))}
</CommandGroup>
</ScrollArea>
</Command>
</PopoverContent>
</Popover>
{errors.especialidade && <p className="text-sm text-destructive">{errors.especialidade}</p>}
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* Contato */}
<Collapsible open={expandedSections.contato} onOpenChange={() => toggleSection("contato")}>
<Card>
<CollapsibleTrigger asChild>
<CardHeader className="cursor-pointer">
<CardTitle className="flex items-center justify-between">
<span className="flex items-center gap-2"><Phone className="h-4 w-4" />Contato</span>
{expandedSections.contato ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</CardTitle>
</CardHeader>
</CollapsibleTrigger>
<CollapsibleContent>
<CardContent className="space-y-4 pt-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="email">E-mail *</Label>
<Input id="email" type="email" value={formData.email} onChange={(e) => handleInputChange("email", e.target.value)} className={errors.email ? "border-destructive" : ""} />
{errors.email && <p className="text-sm text-destructive">{errors.email}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="celular">Celular *</Label>
<Input id="celular" value={formData.celular} onChange={(e) => handleInputChange("celular", e.target.value)} placeholder="(XX) XXXXX-XXXX" className={errors.celular ? "border-destructive" : ""} />
{errors.celular && <p className="text-sm text-destructive">{errors.celular}</p>}
</div>
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* Endereço */}
<Collapsible open={expandedSections.endereco} onOpenChange={() => toggleSection("endereco")}>
<Card>
<CollapsibleTrigger asChild>
<CardHeader className="cursor-pointer">
<CardTitle className="flex items-center justify-between">
<span className="flex items-center gap-2"><MapPin className="h-4 w-4" />Endereço</span>
{expandedSections.endereco ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</CardTitle>
</CardHeader>
</CollapsibleTrigger>
<CollapsibleContent>
<CardContent className="space-y-4 pt-4">
<div className="grid grid-cols-3 gap-4 items-end">
<div className="space-y-2 col-span-1">
<Label htmlFor="cep">CEP *</Label>
<Input id="cep" value={formData.cep} onChange={(e) => handleInputChange("cep", e.target.value)} onBlur={(e) => searchCEP(e.target.value)} disabled={isLoadingCep} className={errors.cep ? "border-destructive" : ""} />
{errors.cep && <p className="text-sm text-destructive">{errors.cep}</p>}
</div>
{isLoadingCep && <Loader2 className="h-4 w-4 animate-spin" />}
</div>
<div className="space-y-2">
<Label htmlFor="logradouro">Logradouro *</Label>
<Input id="logradouro" value={formData.logradouro} onChange={(e) => handleInputChange("logradouro", e.target.value)} className={errors.logradouro ? "border-destructive" : ""} />
{errors.logradouro && <p className="text-sm text-destructive">{errors.logradouro}</p>}
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="numero">Número *</Label>
<Input id="numero" value={formData.numero} onChange={(e) => handleInputChange("numero", e.target.value)} className={errors.numero ? "border-destructive" : ""} />
{errors.numero && <p className="text-sm text-destructive">{errors.numero}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="complemento">Complemento</Label>
<Input id="complemento" value={formData.complemento || ''} onChange={(e) => handleInputChange("complemento", e.target.value)} />
</div>
</div>
<div className="space-y-2">
<Label htmlFor="bairro">Bairro *</Label>
<Input id="bairro" value={formData.bairro} onChange={(e) => handleInputChange("bairro", e.target.value)} className={errors.bairro ? "border-destructive" : ""} />
{errors.bairro && <p className="text-sm text-destructive">{errors.bairro}</p>}
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="cidade">Cidade *</Label>
<Input id="cidade" value={formData.cidade} onChange={(e) => handleInputChange("cidade", e.target.value)} className={errors.cidade ? "border-destructive" : ""} />
{errors.cidade && <p className="text-sm text-destructive">{errors.cidade}</p>}
</div>
<div className="space-y-2">
<Label htmlFor="estado">Estado *</Label>
<Input id="estado" value={formData.estado} onChange={(e) => handleInputChange("estado", e.target.value)} className={errors.estado ? "border-destructive" : ""} />
{errors.estado && <p className="text-sm text-destructive">{errors.estado}</p>}
</div>
</div>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
{/* Observações */}
<Collapsible open={expandedSections.observacoes} onOpenChange={() => toggleSection("observacoes")}>
<Card>
<CollapsibleTrigger asChild>
<CardHeader className="cursor-pointer">
<CardTitle className="flex items-center justify-between">
<span className="flex items-center gap-2"><Stethoscope className="h-4 w-4" />Observações</span>
{expandedSections.observacoes ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</CardTitle>
</CardHeader>
</CollapsibleTrigger>
<CollapsibleContent>
<CardContent className="pt-4">
<Textarea
id="observacoes"
value={formData.observacoes || ''}
onChange={(e) => handleInputChange("observacoes", e.target.value)}
placeholder="Notas adicionais sobre o médico..."
rows={4}
/>
</CardContent>
</CollapsibleContent>
</Card>
</Collapsible>
<div className="flex justify-end gap-4 pt-6 border-t">
<Button type="button" variant="outline" onClick={onClose} disabled={isSubmitting}>
<XCircle className="mr-2 h-4 w-4" />
Cancelar
</Button>
<Button type="submit" className="bg-primary hover:bg-primary/90" disabled={isSubmitting}>
{isSubmitting ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Save className="mr-2 h-4 w-4" />}
{isSubmitting ? "Salvando..." : doctorData?.id ? "Atualizar Médico" : "Salvar Médico"}
</Button>
</div>
</form>
)
}