Editor de laudo beta
This commit is contained in:
commit
a3d70acf2c
517
app/doctor/medicos/[id]/editar/page.tsx
Normal file
517
app/doctor/medicos/[id]/editar/page.tsx
Normal file
@ -0,0 +1,517 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import type React from "react";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { useRouter, useParams } from "next/navigation";
|
||||||
|
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 { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { ArrowLeft, Save } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import DoctorLayout from "@/components/doctor-layout";
|
||||||
|
|
||||||
|
// 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",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Load doctor data
|
||||||
|
const doctor = mockDoctors.find((d) => d.id === doctorId);
|
||||||
|
if (doctor) {
|
||||||
|
setFormData(doctor);
|
||||||
|
}
|
||||||
|
}, [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");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DoctorLayout>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Link href="/medicos">
|
||||||
|
<Button variant="ghost" size="sm">
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
|
Voltar
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900">Editar Médico</h1>
|
||||||
|
<p className="text-gray-600">Atualize as informações do médico</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-8">
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900 mb-6">Dados Pessoais</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="nome">Nome *</Label>
|
||||||
|
<Input id="nome" value={formData.nome} onChange={(e) => handleInputChange("nome", e.target.value)} required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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" required />
|
||||||
|
</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 className="space-y-2">
|
||||||
|
<Label>Sexo *</Label>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input type="radio" id="masculino" name="sexo" value="masculino" checked={formData.sexo === "masculino"} onChange={(e) => handleInputChange("sexo", e.target.value)} className="w-4 h-4 text-blue-600" />
|
||||||
|
<Label htmlFor="masculino">Masculino</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<input type="radio" id="feminino" name="sexo" value="feminino" checked={formData.sexo === "feminino"} onChange={(e) => handleInputChange("sexo", e.target.value)} className="w-4 h-4 text-blue-600" />
|
||||||
|
<Label htmlFor="feminino">Feminino</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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)} required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="etnia">Etnia</Label>
|
||||||
|
<Select value={formData.etnia} onValueChange={(value) => handleInputChange("etnia", value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="branca">Branca</SelectItem>
|
||||||
|
<SelectItem value="preta">Preta</SelectItem>
|
||||||
|
<SelectItem value="parda">Parda</SelectItem>
|
||||||
|
<SelectItem value="amarela">Amarela</SelectItem>
|
||||||
|
<SelectItem value="indigena">Indígena</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="raca">Raça</Label>
|
||||||
|
<Select value={formData.raca} onValueChange={(value) => handleInputChange("raca", value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="caucasiana">Caucasiana</SelectItem>
|
||||||
|
<SelectItem value="negroide">Negroide</SelectItem>
|
||||||
|
<SelectItem value="mongoloide">Mongoloide</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="naturalidade">Naturalidade</Label>
|
||||||
|
<Input id="naturalidade" value={formData.naturalidade} onChange={(e) => handleInputChange("naturalidade", e.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="nacionalidade">Nacionalidade</Label>
|
||||||
|
<Select value={formData.nacionalidade} onValueChange={(value) => handleInputChange("nacionalidade", value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="brasileira">Brasileira</SelectItem>
|
||||||
|
<SelectItem value="estrangeira">Estrangeira</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="profissao">Profissão</Label>
|
||||||
|
<Input id="profissao" value={formData.profissao} onChange={(e) => handleInputChange("profissao", e.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="estadoCivil">Estado civil</Label>
|
||||||
|
<Select value={formData.estadoCivil} onValueChange={(value) => handleInputChange("estadoCivil", value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="solteiro">Solteiro(a)</SelectItem>
|
||||||
|
<SelectItem value="casado">Casado(a)</SelectItem>
|
||||||
|
<SelectItem value="divorciado">Divorciado(a)</SelectItem>
|
||||||
|
<SelectItem value="viuvo">Viúvo(a)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="nomeMae">Nome da mãe</Label>
|
||||||
|
<Input id="nomeMae" value={formData.nomeMae} onChange={(e) => handleInputChange("nomeMae", e.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="nomePai">Nome do pai</Label>
|
||||||
|
<Input id="nomePai" value={formData.nomePai} onChange={(e) => handleInputChange("nomePai", e.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="nomeEsposo">Nome do esposo(a)</Label>
|
||||||
|
<Input id="nomeEsposo" value={formData.nomeEsposo} onChange={(e) => handleInputChange("nomeEsposo", e.target.value)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox id="guiaConvenio" checked={isGuiaConvenio} onCheckedChange={(checked) => setIsGuiaConvenio(checked === true)} />
|
||||||
|
<Label htmlFor="guiaConvenio">RN na Guia do convênio</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<Label htmlFor="observacoes">Observações</Label>
|
||||||
|
<Textarea id="observacoes" value={formData.observacoes} onChange={(e) => handleInputChange("observacoes", e.target.value)} placeholder="Digite observações sobre o médico..." className="mt-2" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Professional Information Section */}
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900 mb-6">Informações Profissionais</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="crm">CRM *</Label>
|
||||||
|
<Input id="crm" value={formData.crm} onChange={(e) => handleInputChange("crm", e.target.value)} placeholder="CRM/UF 12345" required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="especialidade">Especialidade *</Label>
|
||||||
|
<Select value={formData.especialidade} onValueChange={(value) => handleInputChange("especialidade", value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione a especialidade" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Cardiologia">Cardiologia</SelectItem>
|
||||||
|
<SelectItem value="Pediatria">Pediatria</SelectItem>
|
||||||
|
<SelectItem value="Ortopedia">Ortopedia</SelectItem>
|
||||||
|
<SelectItem value="Neurologia">Neurologia</SelectItem>
|
||||||
|
<SelectItem value="Ginecologia">Ginecologia</SelectItem>
|
||||||
|
<SelectItem value="Dermatologia">Dermatologia</SelectItem>
|
||||||
|
<SelectItem value="Psiquiatria">Psiquiatria</SelectItem>
|
||||||
|
<SelectItem value="Oftalmologia">Oftalmologia</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact Section */}
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900 mb-6">Contato</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
|
<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)} />
|
||||||
|
</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="(00) 00000-0000" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="telefone1">Telefone 1</Label>
|
||||||
|
<Input id="telefone1" value={formData.telefone1} onChange={(e) => handleInputChange("telefone1", e.target.value)} placeholder="(00) 0000-0000" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="telefone2">Telefone 2</Label>
|
||||||
|
<Input id="telefone2" value={formData.telefone2} onChange={(e) => handleInputChange("telefone2", e.target.value)} placeholder="(00) 0000-0000" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Address Section */}
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900 mb-6">Endereço</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="cep">CEP</Label>
|
||||||
|
<Input id="cep" value={formData.cep} onChange={(e) => handleInputChange("cep", e.target.value)} placeholder="00000-000" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="endereco">Endereço</Label>
|
||||||
|
<Input id="endereco" value={formData.endereco} onChange={(e) => handleInputChange("endereco", e.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="numero">Número</Label>
|
||||||
|
<Input id="numero" value={formData.numero} onChange={(e) => handleInputChange("numero", e.target.value)} />
|
||||||
|
</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 className="space-y-2">
|
||||||
|
<Label htmlFor="bairro">Bairro</Label>
|
||||||
|
<Input id="bairro" value={formData.bairro} onChange={(e) => handleInputChange("bairro", e.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="cidade">Cidade</Label>
|
||||||
|
<Input id="cidade" value={formData.cidade} onChange={(e) => handleInputChange("cidade", e.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="estado">Estado</Label>
|
||||||
|
<Select value={formData.estado} onValueChange={(value) => handleInputChange("estado", value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="AC">Acre</SelectItem>
|
||||||
|
<SelectItem value="AL">Alagoas</SelectItem>
|
||||||
|
<SelectItem value="AP">Amapá</SelectItem>
|
||||||
|
<SelectItem value="AM">Amazonas</SelectItem>
|
||||||
|
<SelectItem value="BA">Bahia</SelectItem>
|
||||||
|
<SelectItem value="CE">Ceará</SelectItem>
|
||||||
|
<SelectItem value="DF">Distrito Federal</SelectItem>
|
||||||
|
<SelectItem value="ES">Espírito Santo</SelectItem>
|
||||||
|
<SelectItem value="GO">Goiás</SelectItem>
|
||||||
|
<SelectItem value="MA">Maranhão</SelectItem>
|
||||||
|
<SelectItem value="MT">Mato Grosso</SelectItem>
|
||||||
|
<SelectItem value="MS">Mato Grosso do Sul</SelectItem>
|
||||||
|
<SelectItem value="MG">Minas Gerais</SelectItem>
|
||||||
|
<SelectItem value="PA">Pará</SelectItem>
|
||||||
|
<SelectItem value="PB">Paraíba</SelectItem>
|
||||||
|
<SelectItem value="PR">Paraná</SelectItem>
|
||||||
|
<SelectItem value="PE">Pernambuco</SelectItem>
|
||||||
|
<SelectItem value="PI">Piauí</SelectItem>
|
||||||
|
<SelectItem value="RJ">Rio de Janeiro</SelectItem>
|
||||||
|
<SelectItem value="RN">Rio Grande do Norte</SelectItem>
|
||||||
|
<SelectItem value="RS">Rio Grande do Sul</SelectItem>
|
||||||
|
<SelectItem value="RO">Rondônia</SelectItem>
|
||||||
|
<SelectItem value="RR">Roraima</SelectItem>
|
||||||
|
<SelectItem value="SC">Santa Catarina</SelectItem>
|
||||||
|
<SelectItem value="SP">São Paulo</SelectItem>
|
||||||
|
<SelectItem value="SE">Sergipe</SelectItem>
|
||||||
|
<SelectItem value="TO">Tocantins</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Medical Information Section */}
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900 mb-6">Informações Médicas</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="tipoSanguineo">Tipo Sanguíneo</Label>
|
||||||
|
<Select value={formData.tipoSanguineo} onValueChange={(value) => handleInputChange("tipoSanguineo", value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="A+">A+</SelectItem>
|
||||||
|
<SelectItem value="A-">A-</SelectItem>
|
||||||
|
<SelectItem value="B+">B+</SelectItem>
|
||||||
|
<SelectItem value="B-">B-</SelectItem>
|
||||||
|
<SelectItem value="AB+">AB+</SelectItem>
|
||||||
|
<SelectItem value="AB-">AB-</SelectItem>
|
||||||
|
<SelectItem value="O+">O+</SelectItem>
|
||||||
|
<SelectItem value="O-">O-</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="peso">Peso (kg)</Label>
|
||||||
|
<Input id="peso" type="number" value={formData.peso} onChange={(e) => handleInputChange("peso", e.target.value)} placeholder="0.0" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="altura">Altura (m)</Label>
|
||||||
|
<Input id="altura" type="number" step="0.01" value={formData.altura} onChange={(e) => handleInputChange("altura", e.target.value)} placeholder="0.00" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>IMC</Label>
|
||||||
|
<Input value={formData.peso && formData.altura ? (Number.parseFloat(formData.peso) / Number.parseFloat(formData.altura) ** 2).toFixed(2) : ""} disabled placeholder="Calculado automaticamente" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<Label htmlFor="alergias">Alergias</Label>
|
||||||
|
<Textarea id="alergias" value={formData.alergias} onChange={(e) => handleInputChange("alergias", e.target.value)} placeholder="Ex: AAS, Dipirona, etc." className="mt-2" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Insurance Information Section */}
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900 mb-6">Informações de convênio</h2>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="convenio">Convênio</Label>
|
||||||
|
<Select value={formData.convenio} onValueChange={(value) => handleInputChange("convenio", value)}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Particular">Particular</SelectItem>
|
||||||
|
<SelectItem value="SUS">SUS</SelectItem>
|
||||||
|
<SelectItem value="Unimed">Unimed</SelectItem>
|
||||||
|
<SelectItem value="Bradesco">Bradesco Saúde</SelectItem>
|
||||||
|
<SelectItem value="Amil">Amil</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="plano">Plano</Label>
|
||||||
|
<Input id="plano" value={formData.plano} onChange={(e) => handleInputChange("plano", e.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="numeroMatricula">Nº de matrícula</Label>
|
||||||
|
<Input id="numeroMatricula" value={formData.numeroMatricula} onChange={(e) => handleInputChange("numeroMatricula", e.target.value)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="validadeCarteira">Validade da Carteira</Label>
|
||||||
|
<Input id="validadeCarteira" type="date" value={formData.validadeCarteira} onChange={(e) => handleInputChange("validadeCarteira", e.target.value)} disabled={validadeIndeterminada} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox id="validadeIndeterminada" checked={validadeIndeterminada} onCheckedChange={(checked) => setValidadeIndeterminada(checked === true)} />
|
||||||
|
<Label htmlFor="validadeIndeterminada">Validade Indeterminada</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-4">
|
||||||
|
<Link href="/medicos">
|
||||||
|
<Button type="button" variant="outline">
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Button type="submit" className="bg-green-600 hover:bg-green-700">
|
||||||
|
<Save className="w-4 h-4 mr-2" />
|
||||||
|
Salvar Alterações
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</DoctorLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
60
app/doctor/medicos/[id]/laudos/page.tsx
Normal file
60
app/doctor/medicos/[id]/laudos/page.tsx
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { useParams, useRouter } from "next/navigation";
|
||||||
|
import DoctorLayout from "@/components/doctor-layout";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
const Tiptap = dynamic(() => import("@/components/ui/tiptap-editor"), { ssr: false });
|
||||||
|
|
||||||
|
export default function LaudoEditorPage() {
|
||||||
|
const [laudoContent, setLaudoContent] = useState("");
|
||||||
|
const [paciente, setPaciente] = useState<{ id: string; nome: string } | null>(null);
|
||||||
|
const params = useParams();
|
||||||
|
const router = useRouter();
|
||||||
|
const pacienteId = params.id;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (pacienteId) {
|
||||||
|
// Em um caso real, você faria uma chamada de API para buscar os dados do paciente
|
||||||
|
setPaciente({ id: pacienteId as string, nome: `Paciente ${pacienteId}` });
|
||||||
|
setLaudoContent(`<p>Laudo para o paciente ${paciente?.nome || ""}</p>`);
|
||||||
|
}
|
||||||
|
}, [pacienteId, paciente?.nome]);
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
console.log("Salvando laudo para o paciente ID:", pacienteId);
|
||||||
|
console.log("Conteúdo:", laudoContent);
|
||||||
|
// Aqui você implementaria a lógica para salvar o laudo no backend
|
||||||
|
alert("Laudo salvo com sucesso!");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContentChange = (richText: string) => {
|
||||||
|
setLaudoContent(richText);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
router.back();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DoctorLayout>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900">Editor de Laudo</h1>
|
||||||
|
{paciente && <p className="text-gray-600">Editando laudo de: {paciente.nome}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
||||||
|
<Tiptap content={laudoContent} onChange={handleContentChange} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-4">
|
||||||
|
<Button variant="outline" onClick={handleCancel}>Cancelar</Button>
|
||||||
|
<Button onClick={handleSave}>Salvar Laudo</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DoctorLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
app/doctor/medicos/loading.tsx
Normal file
3
app/doctor/medicos/loading.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default function Loading() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
471
app/doctor/medicos/novo/page.tsx
Normal file
471
app/doctor/medicos/novo/page.tsx
Normal file
@ -0,0 +1,471 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
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 { Upload, Plus, X, ChevronDown } from "lucide-react";
|
||||||
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||||
|
import DoctorLayout from "@/components/doctor-layout";
|
||||||
|
|
||||||
|
export default function NovoMedicoPage() {
|
||||||
|
const [anexosOpen, setAnexosOpen] = useState(false);
|
||||||
|
const [anexos, setAnexos] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const adicionarAnexo = () => {
|
||||||
|
setAnexos([...anexos, `Documento ${anexos.length + 1}`]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removerAnexo = (index: number) => {
|
||||||
|
setAnexos(anexos.filter((_, i) => i !== index));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DoctorLayout>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900">Novo Médico</h1>
|
||||||
|
<p className="text-gray-600">Cadastre um novo médico no sistema</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form className="space-y-6">
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900 mb-6">Dados Pessoais</h2>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Foto */}
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="w-20 h-20 bg-gray-100 rounded-full flex items-center justify-center">
|
||||||
|
<Upload className="w-8 h-8 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" type="button" size="sm">
|
||||||
|
<Upload className="w-4 h-4 mr-2" />
|
||||||
|
Carregar Foto
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="nome" className="text-sm font-medium text-gray-700">
|
||||||
|
Nome *
|
||||||
|
</Label>
|
||||||
|
<Input id="nome" placeholder="Nome completo" required className="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="nomeSocial" className="text-sm font-medium text-gray-700">
|
||||||
|
Nome Social
|
||||||
|
</Label>
|
||||||
|
<Input id="nomeSocial" placeholder="Nome social ou apelido" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="cpf" className="text-sm font-medium text-gray-700">
|
||||||
|
CPF *
|
||||||
|
</Label>
|
||||||
|
<Input id="cpf" placeholder="000.000.000-00" required className="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="rg" className="text-sm font-medium text-gray-700">
|
||||||
|
RG
|
||||||
|
</Label>
|
||||||
|
<Input id="rg" placeholder="00.000.000-0" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="crm" className="text-sm font-medium text-gray-700">
|
||||||
|
CRM *
|
||||||
|
</Label>
|
||||||
|
<Input id="crm" placeholder="CRM/UF 12345" required className="mt-1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="outrosDocumentos" className="text-sm font-medium text-gray-700">
|
||||||
|
Outros Documentos
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger className="mt-1">
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="cnh">CNH</SelectItem>
|
||||||
|
<SelectItem value="passaporte">Passaporte</SelectItem>
|
||||||
|
<SelectItem value="carteira-trabalho">Carteira de Trabalho</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label className="text-sm font-medium text-gray-700">Sexo</Label>
|
||||||
|
<div className="flex gap-4 mt-2">
|
||||||
|
<label className="flex items-center gap-2">
|
||||||
|
<input type="radio" name="sexo" value="masculino" className="text-blue-600" />
|
||||||
|
<span className="text-sm">Masculino</span>
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center gap-2">
|
||||||
|
<input type="radio" name="sexo" value="feminino" className="text-blue-600" />
|
||||||
|
<span className="text-sm">Feminino</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="dataNascimento" className="text-sm font-medium text-gray-700">
|
||||||
|
Data de Nascimento
|
||||||
|
</Label>
|
||||||
|
<Input id="dataNascimento" type="date" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="estadoCivil" className="text-sm font-medium text-gray-700">
|
||||||
|
Estado Civil
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger className="mt-1">
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="solteiro">Solteiro(a)</SelectItem>
|
||||||
|
<SelectItem value="casado">Casado(a)</SelectItem>
|
||||||
|
<SelectItem value="divorciado">Divorciado(a)</SelectItem>
|
||||||
|
<SelectItem value="viuvo">Viúvo(a)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="etnia" className="text-sm font-medium text-gray-700">
|
||||||
|
Etnia
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger className="mt-1">
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="branca">Branca</SelectItem>
|
||||||
|
<SelectItem value="preta">Preta</SelectItem>
|
||||||
|
<SelectItem value="parda">Parda</SelectItem>
|
||||||
|
<SelectItem value="amarela">Amarela</SelectItem>
|
||||||
|
<SelectItem value="indigena">Indígena</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="raca" className="text-sm font-medium text-gray-700">
|
||||||
|
Raça
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger className="mt-1">
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="branca">Branca</SelectItem>
|
||||||
|
<SelectItem value="preta">Preta</SelectItem>
|
||||||
|
<SelectItem value="parda">Parda</SelectItem>
|
||||||
|
<SelectItem value="amarela">Amarela</SelectItem>
|
||||||
|
<SelectItem value="indigena">Indígena</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="naturalidade" className="text-sm font-medium text-gray-700">
|
||||||
|
Naturalidade
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger className="mt-1">
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="aracaju">Aracaju</SelectItem>
|
||||||
|
<SelectItem value="salvador">Salvador</SelectItem>
|
||||||
|
<SelectItem value="recife">Recife</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="nacionalidade" className="text-sm font-medium text-gray-700">
|
||||||
|
Nacionalidade
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger className="mt-1">
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="brasileira">Brasileira</SelectItem>
|
||||||
|
<SelectItem value="estrangeira">Estrangeira</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="profissao" className="text-sm font-medium text-gray-700">
|
||||||
|
Profissão
|
||||||
|
</Label>
|
||||||
|
<Input id="profissao" placeholder="Médico" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="especialidade" className="text-sm font-medium text-gray-700">
|
||||||
|
Especialidade *
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger className="mt-1">
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="cardiologia">Cardiologia</SelectItem>
|
||||||
|
<SelectItem value="pediatria">Pediatria</SelectItem>
|
||||||
|
<SelectItem value="ortopedia">Ortopedia</SelectItem>
|
||||||
|
<SelectItem value="ginecologia">Ginecologia</SelectItem>
|
||||||
|
<SelectItem value="neurologia">Neurologia</SelectItem>
|
||||||
|
<SelectItem value="dermatologia">Dermatologia</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="nomeMae" className="text-sm font-medium text-gray-700">
|
||||||
|
Nome da Mãe
|
||||||
|
</Label>
|
||||||
|
<Input id="nomeMae" placeholder="Nome da mãe" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="nomePai" className="text-sm font-medium text-gray-700">
|
||||||
|
Nome do Pai
|
||||||
|
</Label>
|
||||||
|
<Input id="nomePai" placeholder="Nome do pai" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="nomeEsposo" className="text-sm font-medium text-gray-700">
|
||||||
|
Nome do Esposo(a)
|
||||||
|
</Label>
|
||||||
|
<Input id="nomeEsposo" placeholder="Nome do esposo(a)" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="codigoLegado" className="text-sm font-medium text-gray-700">
|
||||||
|
Código Legado
|
||||||
|
</Label>
|
||||||
|
<Input id="codigoLegado" placeholder="Código do sistema anterior" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="observacoes" className="text-sm font-medium text-gray-700">
|
||||||
|
Observações
|
||||||
|
</Label>
|
||||||
|
<Textarea id="observacoes" placeholder="Observações gerais sobre o médico" className="min-h-[100px] mt-1" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Collapsible open={anexosOpen} onOpenChange={setAnexosOpen}>
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<Button variant="ghost" type="button" className="w-full justify-between p-0 h-auto text-left">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-4 h-4 bg-gray-400 rounded-sm flex items-center justify-center">
|
||||||
|
<span className="text-white text-xs">📎</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm font-medium text-gray-700">Anexos do médico</span>
|
||||||
|
</div>
|
||||||
|
<ChevronDown className={`w-4 h-4 transition-transform ${anexosOpen ? "rotate-180" : ""}`} />
|
||||||
|
</Button>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent className="space-y-4 mt-4">
|
||||||
|
{anexos.map((anexo, index) => (
|
||||||
|
<div key={index} className="flex items-center justify-between p-3 border rounded-lg bg-gray-50">
|
||||||
|
<span className="text-sm">{anexo}</span>
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => removerAnexo(index)} type="button">
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<Button variant="outline" onClick={adicionarAnexo} type="button" size="sm">
|
||||||
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
|
Adicionar Anexo
|
||||||
|
</Button>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900 mb-6">Contato</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid md:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="email" className="text-sm font-medium text-gray-700">
|
||||||
|
E-mail
|
||||||
|
</Label>
|
||||||
|
<Input id="email" type="email" placeholder="email@exemplo.com" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="celular" className="text-sm font-medium text-gray-700">
|
||||||
|
Celular
|
||||||
|
</Label>
|
||||||
|
<div className="flex mt-1">
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger className="w-20 rounded-r-none">
|
||||||
|
<SelectValue placeholder="+55" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="+55">+55</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Input placeholder="(XX) XXXXX-XXXX" className="rounded-l-none" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="telefone1" className="text-sm font-medium text-gray-700">
|
||||||
|
Telefone 1
|
||||||
|
</Label>
|
||||||
|
<Input id="telefone1" placeholder="(XX) XXXX-XXXX" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="telefone2" className="text-sm font-medium text-gray-700">
|
||||||
|
Telefone 2
|
||||||
|
</Label>
|
||||||
|
<Input id="telefone2" placeholder="(XX) XXXX-XXXX" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900 mb-6">Endereço</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="cep" className="text-sm font-medium text-gray-700">
|
||||||
|
CEP
|
||||||
|
</Label>
|
||||||
|
<Input id="cep" placeholder="00000-000" className="mt-1 max-w-xs" />
|
||||||
|
</div>
|
||||||
|
<div className="grid md:grid-cols-3 gap-4">
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<Label htmlFor="endereco" className="text-sm font-medium text-gray-700">
|
||||||
|
Endereço
|
||||||
|
</Label>
|
||||||
|
<Input id="endereco" placeholder="Rua, Avenida..." className="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="numero" className="text-sm font-medium text-gray-700">
|
||||||
|
Número
|
||||||
|
</Label>
|
||||||
|
<Input id="numero" placeholder="123" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="complemento" className="text-sm font-medium text-gray-700">
|
||||||
|
Complemento
|
||||||
|
</Label>
|
||||||
|
<Input id="complemento" placeholder="Apto, Bloco..." className="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div className="grid md:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="bairro" className="text-sm font-medium text-gray-700">
|
||||||
|
Bairro
|
||||||
|
</Label>
|
||||||
|
<Input id="bairro" placeholder="Bairro" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="cidade" className="text-sm font-medium text-gray-700">
|
||||||
|
Cidade
|
||||||
|
</Label>
|
||||||
|
<Input id="cidade" placeholder="Cidade" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="estado" className="text-sm font-medium text-gray-700">
|
||||||
|
Estado
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger className="mt-1">
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="SE">Sergipe</SelectItem>
|
||||||
|
<SelectItem value="BA">Bahia</SelectItem>
|
||||||
|
<SelectItem value="AL">Alagoas</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
||||||
|
<h2 className="text-lg font-semibold text-gray-900 mb-6">Informações Profissionais</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="numeroConselho" className="text-sm font-medium text-gray-700">
|
||||||
|
Número do Conselho
|
||||||
|
</Label>
|
||||||
|
<Input id="numeroConselho" placeholder="Número do CRM" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="ufConselho" className="text-sm font-medium text-gray-700">
|
||||||
|
UF do Conselho
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger className="mt-1">
|
||||||
|
<SelectValue placeholder="Selecione" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="SE">SE</SelectItem>
|
||||||
|
<SelectItem value="BA">BA</SelectItem>
|
||||||
|
<SelectItem value="AL">AL</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="dataFormatura" className="text-sm font-medium text-gray-700">
|
||||||
|
Data de Formatura
|
||||||
|
</Label>
|
||||||
|
<Input id="dataFormatura" type="date" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="instituicaoFormacao" className="text-sm font-medium text-gray-700">
|
||||||
|
Instituição de Formação
|
||||||
|
</Label>
|
||||||
|
<Input id="instituicaoFormacao" placeholder="Nome da universidade" className="mt-1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="especialidades" className="text-sm font-medium text-gray-700">
|
||||||
|
Especialidades Adicionais
|
||||||
|
</Label>
|
||||||
|
<Textarea id="especialidades" placeholder="Liste outras especialidades ou subespecialidades..." className="min-h-[80px] mt-1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-4">
|
||||||
|
<Link href="/doctor/medicos">
|
||||||
|
<Button variant="outline">Cancelar</Button>
|
||||||
|
</Link>
|
||||||
|
<Button type="submit" className="bg-green-600 hover:bg-green-700">
|
||||||
|
Salvar Médico
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</DoctorLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
141
app/doctor/medicos/page.tsx
Normal file
141
app/doctor/medicos/page.tsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import DoctorLayout from "@/components/doctor-layout";
|
||||||
|
import Link from "next/link";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Eye, Edit, Calendar } from "lucide-react";
|
||||||
|
|
||||||
|
interface Paciente {
|
||||||
|
id: string;
|
||||||
|
nome: string;
|
||||||
|
telefone: string;
|
||||||
|
cidade: string;
|
||||||
|
estado: string;
|
||||||
|
ultimoAtendimento?: string;
|
||||||
|
proximoAtendimento?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PacientesPage() {
|
||||||
|
const [pacientes, setPacientes] = useState<Paciente[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchPacientes() {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
const res = await fetch("https://mock.apidog.com/m1/1053378-0-default/rest/v1/patients");
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
const json = await res.json();
|
||||||
|
// A API pode retornar o array diretamente ou dentro de uma propriedade 'data'
|
||||||
|
const items = Array.isArray(json) ? json : (Array.isArray(json?.data) ? json.data : []);
|
||||||
|
|
||||||
|
const mapped = items.map((p: any) => ({
|
||||||
|
id: String(p.id ?? ""),
|
||||||
|
nome: p.nome ?? "",
|
||||||
|
telefone: p?.contato?.celular ?? p?.contato?.telefone1 ?? p?.telefone ?? "",
|
||||||
|
cidade: p?.endereco?.cidade ?? p?.cidade ?? "",
|
||||||
|
estado: p?.endereco?.estado ?? p?.estado ?? "",
|
||||||
|
ultimoAtendimento: p.ultimo_atendimento ?? p.ultimoAtendimento ?? "",
|
||||||
|
proximoAtendimento: p.proximo_atendimento ?? p.proximoAtendimento ?? "",
|
||||||
|
}));
|
||||||
|
|
||||||
|
setPacientes(mapped);
|
||||||
|
} catch (e: any) {
|
||||||
|
setError(e?.message || "Erro ao carregar pacientes");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchPacientes();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DoctorLayout>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900">Pacientes</h1>
|
||||||
|
<p className="text-gray-600">Lista de pacientes vinculados</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200">
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full">
|
||||||
|
<thead className="bg-gray-50 border-b border-gray-200">
|
||||||
|
<tr>
|
||||||
|
<th className="text-left p-4 font-medium text-gray-700">Nome</th>
|
||||||
|
<th className="text-left p-4 font-medium text-gray-700">Telefone</th>
|
||||||
|
<th className="text-left p-4 font-medium text-gray-700">Cidade</th>
|
||||||
|
<th className="text-left p-4 font-medium text-gray-700">Estado</th>
|
||||||
|
<th className="text-left p-4 font-medium text-gray-700">Último atendimento</th>
|
||||||
|
<th className="text-left p-4 font-medium text-gray-700">Próximo atendimento</th>
|
||||||
|
<th className="text-left p-4 font-medium text-gray-700">Ações</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{loading ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={7} className="p-6 text-gray-600">
|
||||||
|
Carregando pacientes...
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : error ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={7} className="p-6 text-red-600">{`Erro: ${error}`}</td>
|
||||||
|
</tr>
|
||||||
|
) : pacientes.length === 0 ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={7} className="p-8 text-center text-gray-500">
|
||||||
|
Nenhum paciente encontrado
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
pacientes.map((p) => (
|
||||||
|
<tr key={p.id} className="border-b border-gray-100 hover:bg-gray-50">
|
||||||
|
<td className="p-4">{p.nome}</td>
|
||||||
|
<td className="p-4 text-gray-600">{p.telefone}</td>
|
||||||
|
<td className="p-4 text-gray-600">{p.cidade}</td>
|
||||||
|
<td className="p-4 text-gray-600">{p.estado}</td>
|
||||||
|
<td className="p-4 text-gray-600">{p.ultimoAtendimento}</td>
|
||||||
|
<td className="p-4 text-gray-600">{p.proximoAtendimento}</td>
|
||||||
|
<td className="p-4">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<button className="text-blue-600 hover:underline">Ações</button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => alert(`Detalhes para paciente ID: ${p.id}`)}>
|
||||||
|
<Eye className="w-4 h-4 mr-2" />
|
||||||
|
Ver detalhes
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<Link href={`/doctor/medicos/${p.id}/laudos`}>
|
||||||
|
<Edit className="w-4 h-4 mr-2" />
|
||||||
|
Laudos
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => alert(`Agenda para paciente ID: ${p.id}`)}>
|
||||||
|
<Calendar className="w-4 h-4 mr-2" />
|
||||||
|
Ver agenda
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DoctorLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
145
app/globals.css
Normal file
145
app/globals.css
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
@import 'tailwindcss';
|
||||||
|
@import 'tw-animate-css';
|
||||||
|
|
||||||
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: oklch(1 0 0);
|
||||||
|
--foreground: oklch(0.145 0 0);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.145 0 0);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
|
--primary: oklch(0.205 0 0);
|
||||||
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
|
--secondary: oklch(0.97 0 0);
|
||||||
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
|
--muted: oklch(0.97 0 0);
|
||||||
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
|
--accent: oklch(0.97 0 0);
|
||||||
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--destructive-foreground: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.922 0 0);
|
||||||
|
--input: oklch(0.922 0 0);
|
||||||
|
--ring: oklch(0.708 0 0);
|
||||||
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
|
--radius: 0.625rem;
|
||||||
|
--sidebar: oklch(0.985 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.97 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: oklch(0.145 0 0);
|
||||||
|
--foreground: oklch(0.985 0 0);
|
||||||
|
--card: oklch(0.145 0 0);
|
||||||
|
--card-foreground: oklch(0.985 0 0);
|
||||||
|
--popover: oklch(0.145 0 0);
|
||||||
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
|
--primary: oklch(0.985 0 0);
|
||||||
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
|
--secondary: oklch(0.269 0 0);
|
||||||
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
|
--muted: oklch(0.269 0 0);
|
||||||
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
|
--accent: oklch(0.269 0 0);
|
||||||
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
|
--destructive: oklch(0.396 0.141 25.723);
|
||||||
|
--destructive-foreground: oklch(0.637 0.237 25.331);
|
||||||
|
--border: oklch(0.269 0 0);
|
||||||
|
--input: oklch(0.269 0 0);
|
||||||
|
--ring: oklch(0.439 0 0);
|
||||||
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
|
--sidebar: oklch(0.205 0 0);
|
||||||
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||||
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-accent: oklch(0.269 0 0);
|
||||||
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
|
--sidebar-border: oklch(0.269 0 0);
|
||||||
|
--sidebar-ring: oklch(0.439 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--font-sans: var(--font-geist-sans);
|
||||||
|
--font-mono: var(--font-geist-mono);
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-destructive-foreground: var(--destructive-foreground);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
--color-chart-1: var(--chart-1);
|
||||||
|
--color-chart-2: var(--chart-2);
|
||||||
|
--color-chart-3: var(--chart-3);
|
||||||
|
--color-chart-4: var(--chart-4);
|
||||||
|
--color-chart-5: var(--chart-5);
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
|
--color-sidebar: var(--sidebar);
|
||||||
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border outline-ring/50;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker::-webkit-color-swatch-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-picker::-webkit-color-swatch {
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
162
components/ui/tiptap-editor.tsx
Normal file
162
components/ui/tiptap-editor.tsx
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEditor, EditorContent } from '@tiptap/react'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import { Bold, Italic, Strikethrough, Paperclip, Underline } from 'lucide-react'
|
||||||
|
import { TextStyle } from '@tiptap/extension-text-style'
|
||||||
|
import { Color } from '@tiptap/extension-color'
|
||||||
|
import UnderlineExtension from '@tiptap/extension-underline'
|
||||||
|
import { Extension } from '@tiptap/core'
|
||||||
|
|
||||||
|
const FontSizeExtension = Extension.create({
|
||||||
|
name: 'fontSize',
|
||||||
|
|
||||||
|
addOptions() {
|
||||||
|
return {
|
||||||
|
types: ['textStyle'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addGlobalAttributes() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
types: this.options.types,
|
||||||
|
attributes: {
|
||||||
|
fontSize: {
|
||||||
|
default: null,
|
||||||
|
parseHTML: element => element.style.fontSize,
|
||||||
|
renderHTML: attributes => {
|
||||||
|
if (!attributes.fontSize) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
style: `font-size: ${attributes.fontSize}`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
addCommands() {
|
||||||
|
return {
|
||||||
|
setFontSize: (fontSize: string) => ({ chain }) => {
|
||||||
|
return chain()
|
||||||
|
.setMark('textStyle', { fontSize: `${fontSize}px` })
|
||||||
|
.run()
|
||||||
|
},
|
||||||
|
unsetFontSize: () => ({ chain }) => {
|
||||||
|
return chain()
|
||||||
|
.setMark('textStyle', { fontSize: null })
|
||||||
|
.run()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const Tiptap = ({ content, onChange }: { content: string, onChange: (richText: string) => void }) => {
|
||||||
|
const editor = useEditor({
|
||||||
|
extensions: [
|
||||||
|
StarterKit.configure(),
|
||||||
|
TextStyle.configure(),
|
||||||
|
Color.configure(),
|
||||||
|
UnderlineExtension,
|
||||||
|
FontSizeExtension,
|
||||||
|
],
|
||||||
|
content: content,
|
||||||
|
editorProps: {
|
||||||
|
attributes: {
|
||||||
|
class:
|
||||||
|
'prose dark:prose-invert prose-sm sm:prose-base lg:prose-lg xl:prose-2xl m-5 focus:outline-none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onUpdate({ editor }) {
|
||||||
|
onChange(editor.getHTML())
|
||||||
|
},
|
||||||
|
immediatelyRender: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!editor) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFontSizeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const size = e.target.value;
|
||||||
|
if (size) {
|
||||||
|
// @ts-ignore
|
||||||
|
editor.chain().focus().setFontSize(size).run();
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
editor.chain().focus().unsetFontSize().run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleColorChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
editor.chain().focus().setColor(e.target.value).run()
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 p-2 border-b">
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||||
|
className={editor.isActive('bold') ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
<Bold className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||||
|
className={editor.isActive('italic') ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
<Italic className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleStrike().run()}
|
||||||
|
className={editor.isActive('strike') ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
<Strikethrough className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => editor.chain().focus().toggleUnderline().run()}
|
||||||
|
className={editor.isActive('underline') ? 'is-active' : ''}
|
||||||
|
>
|
||||||
|
<Underline className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<select onChange={handleFontSizeChange}>
|
||||||
|
<option value="">Font size</option>
|
||||||
|
<option value="8">8</option>
|
||||||
|
<option value="9">9</option>
|
||||||
|
<option value="10">10</option>
|
||||||
|
<option value="11">11</option>
|
||||||
|
<option value="12">12</option>
|
||||||
|
<option value="14">14</option>
|
||||||
|
<option value="16">16</option>
|
||||||
|
<option value="18">18</option>
|
||||||
|
<option value="20">20</option>
|
||||||
|
<option value="24">24</option>
|
||||||
|
<option value="28">28</option>
|
||||||
|
<option value="32">32</option>
|
||||||
|
<option value="36">36</option>
|
||||||
|
<option value="48">48</option>
|
||||||
|
<option value="60">60</option>
|
||||||
|
<option value="72">72</option>
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
onInput={handleColorChange}
|
||||||
|
value={editor.getAttributes('textStyle').color || '#000000'}
|
||||||
|
className="color-picker"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => alert('API de anexo de arquivos a ser implementada')}
|
||||||
|
>
|
||||||
|
<Paperclip className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<EditorContent editor={editor} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Tiptap
|
||||||
79
package.json
Normal file
79
package.json
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"name": "my-v0-project",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "next build",
|
||||||
|
"dev": "next dev",
|
||||||
|
"lint": "next lint",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hookform/resolvers": "^3.10.0",
|
||||||
|
"@radix-ui/react-accordion": "1.2.2",
|
||||||
|
"@radix-ui/react-alert-dialog": "1.1.4",
|
||||||
|
"@radix-ui/react-aspect-ratio": "1.1.1",
|
||||||
|
"@radix-ui/react-avatar": "1.1.2",
|
||||||
|
"@radix-ui/react-checkbox": "1.1.3",
|
||||||
|
"@radix-ui/react-collapsible": "1.1.2",
|
||||||
|
"@radix-ui/react-context-menu": "2.2.4",
|
||||||
|
"@radix-ui/react-dialog": "1.1.4",
|
||||||
|
"@radix-ui/react-dropdown-menu": "2.1.4",
|
||||||
|
"@radix-ui/react-hover-card": "1.1.4",
|
||||||
|
"@radix-ui/react-label": "2.1.1",
|
||||||
|
"@radix-ui/react-menubar": "1.1.4",
|
||||||
|
"@radix-ui/react-navigation-menu": "1.2.3",
|
||||||
|
"@radix-ui/react-popover": "1.1.4",
|
||||||
|
"@radix-ui/react-progress": "1.1.1",
|
||||||
|
"@radix-ui/react-radio-group": "1.2.2",
|
||||||
|
"@radix-ui/react-scroll-area": "1.2.2",
|
||||||
|
"@radix-ui/react-select": "2.1.4",
|
||||||
|
"@radix-ui/react-separator": "1.1.1",
|
||||||
|
"@radix-ui/react-slider": "1.2.2",
|
||||||
|
"@radix-ui/react-slot": "1.1.1",
|
||||||
|
"@radix-ui/react-switch": "1.1.2",
|
||||||
|
"@radix-ui/react-tabs": "1.1.2",
|
||||||
|
"@radix-ui/react-toast": "1.2.4",
|
||||||
|
"@radix-ui/react-toggle": "1.1.1",
|
||||||
|
"@radix-ui/react-toggle-group": "1.1.1",
|
||||||
|
"@radix-ui/react-tooltip": "1.1.6",
|
||||||
|
"@tiptap/extension-color": "^2.1.12",
|
||||||
|
"@tiptap/extension-text-style": "^2.1.12",
|
||||||
|
"@tiptap/extension-underline": "^2.1.12",
|
||||||
|
"@tiptap/react": "^2.1.12",
|
||||||
|
"@tiptap/starter-kit": "^2.1.12",
|
||||||
|
"@vercel/analytics": "1.3.1",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "1.0.4",
|
||||||
|
"date-fns": "4.1.0",
|
||||||
|
"embla-carousel-react": "8.5.1",
|
||||||
|
"geist": "^1.3.1",
|
||||||
|
"input-otp": "1.4.1",
|
||||||
|
"lucide-react": "^0.454.0",
|
||||||
|
"next": "14.2.16",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
|
"react": "^18",
|
||||||
|
"react-day-picker": "9.8.0",
|
||||||
|
"react-dom": "^18",
|
||||||
|
"react-hook-form": "^7.60.0",
|
||||||
|
"react-resizable-panels": "^2.1.7",
|
||||||
|
"recharts": "2.15.4",
|
||||||
|
"sonner": "latest",
|
||||||
|
"tailwind-merge": "^2.5.5",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"vaul": "^0.9.9",
|
||||||
|
"zod": "3.25.67"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/postcss": "^4.1.9",
|
||||||
|
"@types/node": "^22",
|
||||||
|
"@types/react": "^18",
|
||||||
|
"@types/react-dom": "^18",
|
||||||
|
"postcss": "^8.5",
|
||||||
|
"tailwindcss": "^4.1.9",
|
||||||
|
"tw-animate-css": "1.3.3",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user