feat: add list and registration of physicians
This commit is contained in:
parent
e06c4376cb
commit
eceae602d4
188
susconecta/app/dashboard/medicos/page.tsx
Normal file
188
susconecta/app/dashboard/medicos/page.tsx
Normal file
@ -0,0 +1,188 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||
import {
|
||||
Search,
|
||||
Plus,
|
||||
MoreHorizontal,
|
||||
Eye,
|
||||
Edit,
|
||||
Trash2,
|
||||
ArrowLeft,
|
||||
} from "lucide-react"
|
||||
import { DoctorRegistrationForm } from "@/components/forms/doctor-registration-form"
|
||||
import { getMedicos, DoctorFormData } from "@/lib/api"
|
||||
|
||||
export default function MedicosPage() {
|
||||
const [searchTerm, setSearchTerm] = useState("")
|
||||
const [medicos, setMedicos] = useState<DoctorFormData[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [showDoctorForm, setShowDoctorForm] = useState(false)
|
||||
const [editingDoctor, setEditingDoctor] = useState<DoctorFormData | null>(null)
|
||||
|
||||
async function fetchMedicos() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const data = await getMedicos()
|
||||
setMedicos(data)
|
||||
} catch (error) {
|
||||
console.error("Erro ao buscar médicos:", error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchMedicos()
|
||||
}, [])
|
||||
|
||||
const filteredMedicos = medicos.filter((medico) =>
|
||||
medico.nome.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(medico.crm && medico.crm.toLowerCase().includes(searchTerm.toLowerCase())) ||
|
||||
(medico.especialidade && medico.especialidade.toLowerCase().includes(searchTerm.toLowerCase()))
|
||||
)
|
||||
|
||||
const handleViewDetails = (doctorId: number) => {
|
||||
console.log("[v0] Ver detalhes do médico:", doctorId)
|
||||
}
|
||||
|
||||
const handleEditDoctor = (doctor: DoctorFormData) => {
|
||||
setEditingDoctor(doctor)
|
||||
setShowDoctorForm(true)
|
||||
}
|
||||
|
||||
const handleDeleteDoctor = (doctorId: number) => {
|
||||
setMedicos(medicos.filter(m => m.id !== doctorId))
|
||||
}
|
||||
|
||||
const handleAddDoctor = () => {
|
||||
setEditingDoctor(null)
|
||||
setShowDoctorForm(true)
|
||||
}
|
||||
|
||||
const handleFormClose = () => {
|
||||
setShowDoctorForm(false)
|
||||
setEditingDoctor(null)
|
||||
fetchMedicos() // Recarrega os dados após fechar o formulário
|
||||
}
|
||||
|
||||
if (showDoctorForm) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button variant="ghost" onClick={handleFormClose} className="p-2">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-foreground">
|
||||
{editingDoctor ? "Editar Médico" : "Cadastrar Novo Médico"}
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{editingDoctor ? "Atualize as informações do médico" : "Preencha os dados do novo médico"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DoctorRegistrationForm doctorData={editingDoctor} onClose={handleFormClose} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-foreground">Médicos</h1>
|
||||
<p className="text-muted-foreground">Gerencie os médicos da clínica</p>
|
||||
</div>
|
||||
<Button className="bg-primary hover:bg-primary/90" onClick={handleAddDoctor}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Adicionar Médico
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-4 items-center">
|
||||
<div className="relative flex-1 min-w-64">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
|
||||
<Input
|
||||
placeholder="Buscar por nome, CRM ou especialidade"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-lg">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Nome</TableHead>
|
||||
<TableHead>CRM</TableHead>
|
||||
<TableHead>Especialidade</TableHead>
|
||||
<TableHead>Celular</TableHead>
|
||||
<TableHead>E-mail</TableHead>
|
||||
<TableHead className="w-[100px]">Ações</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="text-center">Carregando...</TableCell>
|
||||
</TableRow>
|
||||
) : filteredMedicos.map((medico) => (
|
||||
<TableRow key={medico.id}>
|
||||
<TableCell className="font-medium">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 bg-muted rounded-full flex items-center justify-center">
|
||||
<span className="text-xs font-medium">{medico.nome.charAt(0).toUpperCase()}</span>
|
||||
</div>
|
||||
<button onClick={() => handleViewDetails(medico.id!)} className="hover:text-primary cursor-pointer">
|
||||
{medico.nome}
|
||||
</button>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{medico.crm} - {medico.crmUf}</TableCell>
|
||||
<TableCell>{medico.especialidade}</TableCell>
|
||||
<TableCell>{medico.celular}</TableCell>
|
||||
<TableCell>{medico.email}</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="h-8 w-8 p-0 flex items-center justify-center rounded-md hover:bg-accent">
|
||||
<span className="sr-only">Abrir menu</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => handleViewDetails(medico.id!)}>
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
Ver detalhes
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleEditDoctor(medico)}>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleDeleteDoctor(medico.id!)} className="text-destructive">
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Excluir
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Mostrando {filteredMedicos.length} de {medicos.length} médicos
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -9,6 +9,7 @@ const navigation = [
|
||||
{ name: "Dashboard", href: "/dashboard", icon: Home },
|
||||
{ name: "Agenda", href: "/dashboard/agenda", icon: Calendar },
|
||||
{ name: "Pacientes", href: "/dashboard/pacientes", icon: Users },
|
||||
{ name: "Médicos", href: "/dashboard/medicos", icon: Stethoscope },
|
||||
{ name: "Consultas", href: "/dashboard/consultas", icon: UserCheck },
|
||||
{ name: "Prontuários", href: "/dashboard/prontuarios", icon: FileText },
|
||||
{ name: "Relatórios", href: "/dashboard/relatorios", icon: BarChart3 },
|
||||
|
||||
@ -1,157 +1,545 @@
|
||||
"use client"
|
||||
'use client'
|
||||
|
||||
import { useState } from "react"
|
||||
import { Input } from "@/components/ui/input"
|
||||
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 { Card, CardContent, CardHeader } from "@/components/ui/card"
|
||||
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"
|
||||
|
||||
export function DoctorRegistrationForm() {
|
||||
const [formData, setFormData] = useState({
|
||||
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: "",
|
||||
cpf: "",
|
||||
dataNascimento: "",
|
||||
sexo: "",
|
||||
cep: "",
|
||||
logradouro: "",
|
||||
logradouro: "",
|
||||
numero: "",
|
||||
complemento: "",
|
||||
bairro: "",
|
||||
cidade: "",
|
||||
estado: "",
|
||||
observacoes: "",
|
||||
})
|
||||
const [errors, setErrors] = useState({})
|
||||
photo: null,
|
||||
}
|
||||
|
||||
const handleInputChange = (field, value) => {
|
||||
setFormData((prev) => ({ ...prev, [field]: value }))
|
||||
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 = {}
|
||||
const newErrors: Record<string, string> = {}
|
||||
if (!formData.nome.trim()) newErrors.nome = "Nome é obrigatório"
|
||||
if (!formData.crm.trim()) newErrors.crm = "CRM é obrigatório"
|
||||
if (!formData.especialidade.trim()) newErrors.especialidade = "Especialidade é obrigatória"
|
||||
if (!formData.email.trim()) newErrors.email = "E-mail é obrigatório"
|
||||
if (!formData.celular.trim()) newErrors.celular = "Celular é obrigatório"
|
||||
if (!formData.cpf.trim()) newErrors.cpf = "CPF é obrigatório"
|
||||
if (!formData.dataNascimento.trim()) newErrors.dataNascimento = "Data de nascimento é obrigatória"
|
||||
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 = (e) => {
|
||||
e.preventDefault()
|
||||
const handleSubmit = async (event: React.FormEvent) => {
|
||||
event.preventDefault()
|
||||
if (!validateForm()) return
|
||||
// Aqui você pode chamar a API para salvar o médico
|
||||
alert("Médico cadastrado com sucesso!")
|
||||
|
||||
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">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h2 className="text-lg font-semibold">Dados do Médico</h2>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="nome">Nome</Label>
|
||||
<Input id="nome" value={formData.nome} onChange={e => handleInputChange("nome", e.target.value)} />
|
||||
{errors.nome && <p className="text-sm text-destructive">{errors.nome}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="crm">CRM</Label>
|
||||
<Input id="crm" value={formData.crm} onChange={e => handleInputChange("crm", e.target.value)} />
|
||||
{errors.crm && <p className="text-sm text-destructive">{errors.crm}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="especialidade">Especialidade</Label>
|
||||
<Input id="especialidade" value={formData.especialidade} onChange={e => handleInputChange("especialidade", e.target.value)} />
|
||||
{errors.especialidade && <p className="text-sm text-destructive">{errors.especialidade}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="email">E-mail</Label>
|
||||
<Input id="email" value={formData.email} onChange={e => handleInputChange("email", e.target.value)} />
|
||||
{errors.email && <p className="text-sm text-destructive">{errors.email}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="celular">Celular</Label>
|
||||
<Input id="celular" value={formData.celular} onChange={e => handleInputChange("celular", e.target.value)} />
|
||||
{errors.celular && <p className="text-sm text-destructive">{errors.celular}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="cpf">CPF</Label>
|
||||
<Input id="cpf" value={formData.cpf} onChange={e => handleInputChange("cpf", e.target.value)} />
|
||||
{errors.cpf && <p className="text-sm text-destructive">{errors.cpf}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="dataNascimento">Data de Nascimento</Label>
|
||||
<Input id="dataNascimento" type="date" value={formData.dataNascimento} onChange={e => handleInputChange("dataNascimento", e.target.value)} />
|
||||
{errors.dataNascimento && <p className="text-sm text-destructive">{errors.dataNascimento}</p>}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{errors.submit && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{errors.submit}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h2 className="text-lg font-semibold">Endereço</h2>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="cep">CEP</Label>
|
||||
<Input id="cep" value={formData.cep} onChange={e => handleInputChange("cep", e.target.value)} />
|
||||
{errors.cep && <p className="text-sm text-destructive">{errors.cep}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="logradouro">Logradouro</Label>
|
||||
<Input id="logradouro" value={formData.logradouro} onChange={e => handleInputChange("logradouro", e.target.value)} />
|
||||
{errors.logradouro && <p className="text-sm text-destructive">{errors.logradouro}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="numero">Número</Label>
|
||||
<Input id="numero" value={formData.numero} onChange={e => handleInputChange("numero", e.target.value)} />
|
||||
{errors.numero && <p className="text-sm text-destructive">{errors.numero}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="bairro">Bairro</Label>
|
||||
<Input id="bairro" value={formData.bairro} onChange={e => handleInputChange("bairro", e.target.value)} />
|
||||
{errors.bairro && <p className="text-sm text-destructive">{errors.bairro}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="cidade">Cidade</Label>
|
||||
<Input id="cidade" value={formData.cidade} onChange={e => handleInputChange("cidade", e.target.value)} />
|
||||
{errors.cidade && <p className="text-sm text-destructive">{errors.cidade}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="estado">Estado</Label>
|
||||
<Input id="estado" value={formData.estado} onChange={e => handleInputChange("estado", e.target.value)} />
|
||||
{errors.estado && <p className="text-sm text-destructive">{errors.estado}</p>}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* 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>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h2 className="text-lg font-semibold">Observações</h2>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Label htmlFor="observacoes">Observações</Label>
|
||||
<Input id="observacoes" value={formData.observacoes} onChange={e => handleInputChange("observacoes", e.target.value)} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* 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>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button type="button" variant="outline">Cancelar</Button>
|
||||
<Button type="submit">Cadastrar Médico</Button>
|
||||
{/* 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,25 +35,19 @@ const buttonVariants = cva(
|
||||
}
|
||||
)
|
||||
|
||||
function Button({
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Button = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants> & { asChild?: boolean }
|
||||
>(({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
data-slot="button"
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
export async function salvarPaciente(formData) {
|
||||
var myHeaders = new Headers();
|
||||
myHeaders.append("Content-Type", "application/json");
|
||||
|
||||
var raw = JSON.stringify(formData);
|
||||
|
||||
var requestOptions = {
|
||||
method: 'POST',
|
||||
headers: myHeaders,
|
||||
body: raw,
|
||||
redirect: 'follow'
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes", requestOptions);
|
||||
const result = await response.json();
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.log('error', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,3 +11,104 @@ export async function salvarPaciente(formData: Record<string, any>) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Define the Doctor data interface
|
||||
export interface DoctorFormData {
|
||||
id?: number;
|
||||
nome: string;
|
||||
nomeSocial?: string;
|
||||
cpf: string;
|
||||
rg: string;
|
||||
crm: string;
|
||||
crmUf: string;
|
||||
especialidade: string;
|
||||
email: string;
|
||||
celular: string;
|
||||
dataNascimento: string;
|
||||
sexo: string;
|
||||
cep: string;
|
||||
logradouro: string;
|
||||
numero: string;
|
||||
complemento?: string;
|
||||
bairro: string;
|
||||
cidade: string;
|
||||
estado: string;
|
||||
observacoes?: string;
|
||||
photo?: File | null;
|
||||
}
|
||||
|
||||
|
||||
// Mock data for doctors
|
||||
const medicosMock: DoctorFormData[] = [
|
||||
{
|
||||
id: 1,
|
||||
nome: "Dr. João da Silva",
|
||||
nomeSocial: "",
|
||||
cpf: "111.111.111-11",
|
||||
rg: "11.111.111-1",
|
||||
crm: "12345",
|
||||
crmUf: "SP",
|
||||
especialidade: "Cardiologia",
|
||||
email: "joao.silva@example.com",
|
||||
celular: "(11) 99999-1234",
|
||||
dataNascimento: "1980-01-15",
|
||||
sexo: "masculino",
|
||||
cep: "01001-000",
|
||||
logradouro: "Praça da Sé",
|
||||
numero: "1",
|
||||
bairro: "Sé",
|
||||
cidade: "São Paulo",
|
||||
estado: "SP",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
nome: "Dra. Maria Oliveira",
|
||||
nomeSocial: "Dra. Maria",
|
||||
cpf: "222.222.222-22",
|
||||
rg: "22.222.222-2",
|
||||
crm: "54321",
|
||||
crmUf: "RJ",
|
||||
especialidade: "Dermatologia",
|
||||
email: "maria.oliveira@example.com",
|
||||
celular: "(21) 98888-5678",
|
||||
dataNascimento: "1985-05-20",
|
||||
sexo: "feminino",
|
||||
cep: "20031-050",
|
||||
logradouro: "Av. Pres. Wilson",
|
||||
numero: "231",
|
||||
bairro: "Centro",
|
||||
cidade: "Rio de Janeiro",
|
||||
estado: "RJ",
|
||||
},
|
||||
];
|
||||
|
||||
export async function getMedicos(): Promise<DoctorFormData[]> {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve(medicosMock);
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getMedicoById(id: number): Promise<DoctorFormData | undefined> {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
const medico = medicosMock.find(m => m.id === id);
|
||||
resolve(medico);
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export async function salvarMedico(formData: DoctorFormData) {
|
||||
try {
|
||||
const response = await axios.post("https://mock.apidog.com/m1/1053378-0-default/medicos", formData,
|
||||
{ headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
console.log("Médico salvo com sucesso:", response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Erro ao salvar médico:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
26
susconecta/lib/formatters.ts
Normal file
26
susconecta/lib/formatters.ts
Normal file
@ -0,0 +1,26 @@
|
||||
export const formatCPF = (cpf: string): string => {
|
||||
const cleaned = cpf.replace(/\D/g, "");
|
||||
const match = cleaned.match(/^(\d{3})(\d{3})(\d{3})(\d{2})$/);
|
||||
if (match) {
|
||||
return `${match[1]}.${match[2]}.${match[3]}-${match[4]}`;
|
||||
}
|
||||
return cpf;
|
||||
};
|
||||
|
||||
export const formatCelular = (celular: string): string => {
|
||||
const cleaned = celular.replace(/\D/g, "");
|
||||
const match = cleaned.match(/^(\d{2})(\d{5})(\d{4})$/);
|
||||
if (match) {
|
||||
return `(${match[1]}) ${match[2]}-${match[3]}`;
|
||||
}
|
||||
return celular;
|
||||
};
|
||||
|
||||
export const formatRG = (rg: string): string => {
|
||||
const cleaned = rg.replace(/\D/g, "");
|
||||
const match = cleaned.match(/^(\d{2})(\d{3})(\d{3})(\d{1})$/);
|
||||
if (match) {
|
||||
return `${match[1]}.${match[2]}.${match[3]}-${match[4]}`;
|
||||
}
|
||||
return rg;
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user