riseup-squad19/cadastro do paciente.html

630 lines
24 KiB
HTML

<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Cadastro de Paciente</title>
<style>
:root {
--bg: #0f172a; /* azul-escuro elegante */
--panel: #111827; /* painel */
--card: #0b1220; /* cards */
--text: #f1f5f9;
--muted: #94a3b8;
--primary: #3b82f6;
--danger: #ef4444;
--success: #22c55e;
--border: #1f2937;
--focus: #93c5fd;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans,
Helvetica Neue, Arial, "Apple Color Emoji", "Segoe UI Emoji";
background: linear-gradient(180deg, var(--bg), #0b1021 60%);
color: var(--text);
}
header {
padding: 24px 16px;
border-bottom: 1px solid var(--border);
background: rgba(17, 24, 39, 0.6);
position: sticky; top: 0; backdrop-filter: blur(8px);
}
.container {
max-width: 1100px;
margin: 20px auto;
padding: 0 16px 60px;
}
h1 { margin: 0; font-size: 1.6rem; letter-spacing: 0.4px; }
h2 { font-size: 1.25rem; margin: 14px 0; color: var(--text); }
p.helper { color: var(--muted); margin-top: 4px; font-size: 0.9rem; }
.grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 14px;
}
.card {
background: linear-gradient(180deg, var(--card), #0a0f1c);
border: 1px solid var(--border);
border-radius: 14px;
padding: 16px;
box-shadow: 0 10px 30px rgba(0,0,0,.25);
}
label { display: block; font-weight: 600; margin-bottom: 6px; }
.row { display: flex; gap: 10px; align-items: center; }
input[type="text"], input[type="email"], input[type="date"], input[type="number"], select, textarea {
width: 100%;
background: #0b1220;
color: var(--text);
border: 1px solid var(--border);
border-radius: 10px;
padding: 10px 12px;
outline: none;
transition: border .15s, box-shadow .15s;
}
textarea { min-height: 110px; resize: vertical; }
input:focus, select:focus, textarea:focus {
border-color: var(--focus);
box-shadow: 0 0 0 3px rgba(147, 197, 253, .15);
}
.field { grid-column: span 12; }
.col-6 { grid-column: span 6; }
.col-4 { grid-column: span 4; }
.col-3 { grid-column: span 3; }
.actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 18px; }
button {
padding: 10px 14px;
border-radius: 12px;
border: 1px solid var(--border);
background: #0b1220;
color: var(--text);
cursor: pointer;
font-weight: 600;
}
.btn-primary { background: var(--primary); border-color: #2563eb; }
.btn-danger { background: var(--danger); border-color: #b91c1c; }
.error { font-size: 0.85rem; color: var(--danger); margin-top: 6px; display: none; }
.show { display: block; }
.avatar {
width: 120px; height: 120px; border-radius: 12px; overflow: hidden; border: 1px solid var(--border);
background: #0b1220; display: grid; place-items: center; color: var(--muted);
}
.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.file-btn { position: relative; overflow: hidden; }
.file-btn input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
details {
border: 1px solid var(--border);
border-radius: 12px;
background: linear-gradient(180deg, var(--panel), #0b1220);
padding: 10px 12px;
}
summary { cursor: pointer; font-weight: 700; list-style: none; }
summary::-webkit-details-marker { display:none; }
.inline { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; align-items: end; }
.inline-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; align-items: end; }
.toggle {
--h: 28px; --w: 52px;
width: var(--w); height: var(--h); border-radius: 999px; position: relative; background: #253046; border:1px solid var(--border);
}
.toggle input { display: none; }
.knob { position: absolute; top: 2px; left: 2px; width: calc(var(--h) - 4px); height: calc(var(--h) - 4px); border-radius: 999px; background: #e5e7eb; transition: left .15s, background .15s; }
.toggle input:checked + .knob { left: calc(var(--w) - var(--h) + 2px); background: #bbf7d0; }
.attachments { margin-top: 10px; }
.att-row { display:flex; align-items:center; justify-content:space-between; padding:8px 10px; border:1px dashed var(--border); border-radius:10px; margin-top:8px; }
.att-row small { color: var(--muted); }
.att-row button { padding:6px 10px; }
.muted { color: var(--muted); font-size: .9rem; }
.required:after { content:" *"; color: var(--danger); font-weight: 800; }
@media (max-width: 820px) {
.col-6, .col-4, .col-3 { grid-column: span 12; }
.inline, .inline-3 { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<header>
<h1>Cadastro de Paciente</h1>
</header>
<div class="container">
<form id="pacienteForm" novalidate>
<section class="card">
<h2>1. Dados pessoais</h2>
<div class="grid">
<div class="field col-3">
<label>Foto</label>
<div class="row">
<div class="avatar" id="avatarPreview" aria-label="Pré-visualização da foto">Prévia</div>
<label class="file-btn btn-primary" style="padding:10px 12px;">
<span>Carregar</span>
<input type="file" id="foto" accept="image/*" />
</label>
</div>
<p class="muted">PNG/JPG até 5MB.</p>
</div>
<div class="field col-9">
<label class="required" for="nome">Nome</label>
<input id="nome" name="nome" type="text" autocomplete="name" required />
<div class="error" id="err-nome">Informe o nome completo.</div>
</div>
<div class="field col-6">
<label for="nomeSocial">Nome social</label>
<input id="nomeSocial" name="nomeSocial" type="text" />
</div>
<div class="field col-3">
<label class="required" for="cpf">CPF</label>
<input id="cpf" name="cpf" type="text" inputmode="numeric" maxlength="14" placeholder="000.000.000-00" required />
<div class="error" id="err-cpf">CPF inválido.</div>
</div>
<div class="field col-3">
<label for="rg">RG</label>
<input id="rg" name="rg" type="text" />
</div>
<div class="field col-6">
<label for="docTipo">Outros documentos</label>
<div class="inline">
<select id="docTipo" name="docTipo">
<option value="">Selecione…</option>
<option>CNH</option>
<option>Passaporte</option>
<option>RNE</option>
<option>Cart. Profissional</option>
</select>
<input id="docNumero" name="docNumero" type="text" placeholder="Número do documento" disabled />
</div>
</div>
<div class="field col-6">
<label>Sexo</label>
<div class="row" role="radiogroup" aria-label="Sexo">
<label><input type="radio" name="sexo" value="M" /> Masculino</label>
<label><input type="radio" name="sexo" value="F" /> Feminino</label>
<label><input type="radio" name="sexo" value="O" /> Outro / Prefiro não informar</label>
</div>
</div>
<div class="field col-3">
<label for="nascimento">Data de nascimento</label>
<input id="nascimento" name="nascimento" type="date" />
</div>
<div class="field col-3">
<label for="estadoCivil">Estado civil</label>
<select id="estadoCivil" name="estadoCivil">
<option value="">Selecione…</option>
<option>Solteiro(a)</option>
<option>Casado(a)</option>
<option>Divorciado(a)</option>
<option>Viúvo(a)</option>
<option>União estável</option>
</select>
</div>
<div class="field col-4">
<label for="raca">Raça/Cor (IBGE)</label>
<select id="raca" name="raca">
<option value="">Selecione…</option>
<option>Branca</option>
<option>Preta</option>
<option>Parda</option>
<option>Amarela</option>
<option>Indígena</option>
<option>Não informada</option>
</select>
</div>
<div class="field col-4">
<label for="etnia">Etnia (IBGE/autoidentificação)</label>
<select id="etnia" name="etnia">
<option value="">Selecione…</option>
<option>Não se aplica</option>
<option>Indígena (especificar em observações)</option>
<option>Quilombola</option>
<option>Cigana</option>
<option>Outra / Prefiro não informar</option>
</select>
</div>
<div class="field col-4">
<label for="profissao">Profissão</label>
<input id="profissao" name="profissao" type="text" />
</div>
<div class="field col-6">
<label for="naturalidade">Naturalidade (cidade)</label>
<input list="cidadesBR" id="naturalidade" name="naturalidade" placeholder="Cidade de nascimento" />
<datalist id="cidadesBR">
<option>São Paulo (SP)</option>
<option>Rio de Janeiro (RJ)</option>
<option>Belo Horizonte (MG)</option>
<option>Porto Alegre (RS)</option>
<option>Salvador (BA)</option>
<option>Curitiba (PR)</option>
<option>Fortaleza (CE)</option>
<option>Recife (PE)</option>
<option>Manaus (AM)</option>
<option>Brasília (DF)</option>
<option>Maceió (AL)</option>
</datalist>
</div>
<div class="field col-6">
<label for="nacionalidade">Nacionalidade (país)</label>
<input list="paises" id="nacionalidade" name="nacionalidade" placeholder="Brasil" />
<datalist id="paises">
<option>Brasil</option>
<option>Argentina</option>
<option>Portugal</option>
<option>Estados Unidos</option>
<option>Japão</option>
<option>Itália</option>
<option>Espanha</option>
<option>França</option>
<option>Alemanha</option>
<option>Outros</option>
</datalist>
</div>
<div class="field col-6">
<label for="mae">Nome da mãe</label>
<input id="mae" name="mae" type="text" autocomplete="on" />
</div>
<div class="field col-6">
<label for="profMae">Profissão da mãe</label>
<input id="profMae" name="profMae" type="text" />
</div>
<div class="field col-6">
<label for="pai">Nome do pai</label>
<input id="pai" name="pai" type="text" />
</div>
<div class="field col-6">
<label for="profPai">Profissão do pai</label>
<input id="profPai" name="profPai" type="text" />
</div>
<div class="field col-6">
<label for="resp">Nome do responsável</label>
<input id="resp" name="resp" type="text" />
</div>
<div class="field col-6">
<label for="cpfResp">CPF do responsável</label>
<input id="cpfResp" name="cpfResp" type="text" inputmode="numeric" maxlength="14" placeholder="000.000.000-00" />
</div>
<div class="field col-6">
<label for="conjuge">Nome do esposo(a)</label>
<input id="conjuge" name="conjuge" type="text" />
</div>
<div class="field col-3">
<label>RN na guia do convênio</label>
<div class="row" style="gap:12px; align-items:center;">
<span class="muted">Não</span>
<label class="toggle">
<input type="checkbox" id="rnGuia" />
<span class="knob"></span>
</label>
<span class="muted">Sim</span>
</div>
</div>
<div class="field col-3">
<label for="codLegado">Código legado</label>
<input id="codLegado" name="codLegado" type="text" />
</div>
</div>
</section>
<details class="card" open>
<summary>2. Observações e anexos</summary>
<div class="grid" style="margin-top:10px;">
<div class="field col-12">
<label for="obs">Observações</label>
<textarea id="obs" name="obs" placeholder="Alergias, restrições, notas…"></textarea>
</div>
<div class="field col-12">
<label>Anexos do paciente</label>
<div class="row" style="gap:10px; align-items: center;">
<label class="file-btn btn-primary">
<span>Adicionar arquivos</span>
<input type="file" id="anexos" multiple />
</label>
<span class="muted">Cartão de convênio, exames, etc.</span>
</div>
<div class="attachments" id="attachmentsList" aria-live="polite"></div>
</div>
</div>
</details>
<details class="card" open>
<summary>3. Contato</summary>
<div class="grid" style="margin-top:10px;">
<div class="field col-6">
<label for="email">E-mail</label>
<input id="email" name="email" type="email" placeholder="nome@exemplo.com" />
<div class="error" id="err-email">Formato de e-mail inválido.</div>
</div>
<div class="field col-6">
<label for="celular">Celular</label>
<input id="celular" name="celular" type="text" inputmode="tel" placeholder="+55 (11) 91234-5678" />
</div>
<div class="field col-6">
<label for="tel1">Telefone 1</label>
<input id="tel1" name="tel1" type="text" inputmode="tel" placeholder="+55 (11) 1234-5678" />
</div>
<div class="field col-6">
<label for="tel2">Telefone 2</label>
<input id="tel2" name="tel2" type="text" inputmode="tel" placeholder="+55 (11) 1234-5678" />
</div>
</div>
</details>
<details class="card" open>
<summary>4. Endereço</summary>
<div class="grid" style="margin-top:10px;">
<div class="field col-3">
<label for="cep">CEP</label>
<input id="cep" name="cep" type="text" inputmode="numeric" maxlength="9" placeholder="00000-000" />
<div class="error" id="err-cep">CEP não encontrado.</div>
</div>
<div class="field col-9">
<label for="logradouro">Logradouro</label>
<input id="logradouro" name="logradouro" type="text" />
</div>
<div class="field col-3">
<label for="numero">Número</label>
<input id="numero" name="numero" type="text" />
</div>
<div class="field col-3">
<label for="complemento">Complemento</label>
<input id="complemento" name="complemento" type="text" />
</div>
<div class="field col-3">
<label for="bairro">Bairro</label>
<input id="bairro" name="bairro" type="text" />
</div>
<div class="field col-3">
<label for="cidade">Cidade</label>
<input id="cidade" name="cidade" type="text" />
</div>
<div class="field col-3">
<label for="estado">Estado</label>
<input id="estado" name="estado" type="text" maxlength="2" placeholder="UF" />
</div>
<div class="field col-9">
<label for="referencia">Referência</label>
<input id="referencia" name="referencia" type="text" placeholder="Ponto de referência (opcional)" />
</div>
</div>
</details>
<div class="actions">
<button type="button" class="btn-danger" id="btnCancelar">Cancelar</button>
<button type="submit" class="btn-primary">Salvar</button>
</div>
</form>
</div>
<script>
// === Utilidades de máscara ===
const onlyDigits = (v) => v.replace(/\D+/g, "");
const maskCPF = (v) => {
v = onlyDigits(v).slice(0,11);
return v
.replace(/(\d{3})(\d)/, '$1.$2')
.replace(/(\d{3})(\d)/, '$1.$2')
.replace(/(\d{3})(\d{1,2})$/, '$1-$2');
};
const maskCEP = (v) => {
v = onlyDigits(v).slice(0,8);
return v.replace(/(\d{5})(\d{1,3})$/, '$1-$2');
};
const maskCell = (v) => {
v = onlyDigits(v).slice(0,13); // 55 + 2 + 9 + 4 = 19, mas guardamos apenas dígitos
// Formato: +55 (XX) XXXXX-XXXX
let d = onlyDigits(v);
if (d.startsWith('55') === false) d = '55' + d;
d = d.slice(0,13); // 55 + 2 + 9 + 4 (mas sem sinais)
const cc = d.slice(0,2); // 55
const dd = d.slice(2,4);
const p1 = d.slice(4,9);
const p2 = d.slice(9,13);
let out = `+${cc}`;
if (dd) out += ` (${dd})`;
if (p1) out += ` ${p1}`;
if (p2) out += `-${p2}`;
return out;
};
const maskPhone = (v) => {
v = onlyDigits(v);
if (v.startsWith('55') === false) v = '55' + v;
v = v.slice(0,12); // 55 + 2 + 8
const cc = v.slice(0,2); // 55
const dd = v.slice(2,4);
const p1 = v.slice(4,8);
const p2 = v.slice(8,12);
let out = `+${cc}`;
if (dd) out += ` (${dd})`;
if (p1) out += ` ${p1}`;
if (p2) out += `-${p2}`;
return out;
};
// === Validação de CPF ===
function validaCPF(cpf) {
cpf = onlyDigits(cpf);
if (!cpf || cpf.length !== 11) return false;
if (/^(\d)\1+$/.test(cpf)) return false; // evita sequências
let soma = 0;
for (let i = 0; i < 9; i++) soma += parseInt(cpf.charAt(i)) * (10 - i);
let resto = 11 - (soma % 11);
let dig1 = resto === 10 || resto === 11 ? 0 : resto;
soma = 0;
for (let i = 0; i < 10; i++) soma += parseInt(cpf.charAt(i)) * (11 - i);
resto = 11 - (soma % 11);
let dig2 = resto === 10 || resto === 11 ? 0 : resto;
return dig1 === parseInt(cpf.charAt(9)) && dig2 === parseInt(cpf.charAt(10));
}
// === Foto: pré-visualização ===
const fotoInput = document.getElementById('foto');
const avatarPreview = document.getElementById('avatarPreview');
fotoInput.addEventListener('change', () => {
const file = fotoInput.files?.[0];
if (!file) return;
if (!file.type.startsWith('image/')) { alert('Selecione um arquivo de imagem.'); return; }
if (file.size > 5 * 1024 * 1024) { alert('Imagem acima de 5MB.'); return; }
const url = URL.createObjectURL(file);
avatarPreview.innerHTML = `<img alt="Foto do paciente" src="${url}">`;
});
// === Documentos: habilitar número quando tipo selecionado ===
const docTipo = document.getElementById('docTipo');
const docNumero = document.getElementById('docNumero');
docTipo.addEventListener('change', () => {
docNumero.disabled = !docTipo.value;
if (!docTipo.value) docNumero.value = '';
docNumero.focus();
});
// === Máscaras ===
const cpf = document.getElementById('cpf');
const cpfResp = document.getElementById('cpfResp');
const cep = document.getElementById('cep');
const celular = document.getElementById('celular');
const tel1 = document.getElementById('tel1');
const tel2 = document.getElementById('tel2');
const applyMask = (el, fn) => {
el.addEventListener('input', () => el.value = fn(el.value));
el.addEventListener('blur', () => el.value = fn(el.value));
};
applyMask(cpf, maskCPF);
applyMask(cpfResp, maskCPF);
applyMask(cep, maskCEP);
applyMask(celular, maskCell);
applyMask(tel1, maskPhone);
applyMask(tel2, maskPhone);
// === CEP: auto-preencher via ViaCEP ===
async function buscaCEP(v) {
const dig = onlyDigits(v);
if (dig.length !== 8) return null;
try {
const res = await fetch(`https://viacep.com.br/ws/${dig}/json/`);
if (!res.ok) return null;
const data = await res.json();
if (data.erro) return null;
return data; // {logradouro, bairro, localidade, uf}
} catch { return null; }
}
const logradouro = document.getElementById('logradouro');
const bairro = document.getElementById('bairro');
const cidade = document.getElementById('cidade');
const estado = document.getElementById('estado');
const errCEP = document.getElementById('err-cep');
cep.addEventListener('blur', async () => {
errCEP.classList.remove('show');
const data = await buscaCEP(cep.value);
if (!data) { errCEP.classList.add('show'); return; }
logradouro.value = data.logradouro || '';
bairro.value = data.bairro || '';
cidade.value = data.localidade || '';
estado.value = data.uf || '';
});
// === Anexos: listar e remover ===
const anexosInput = document.getElementById('anexos');
const attachmentsList = document.getElementById('attachmentsList');
const anexosState = [];
function renderAnexos() {
attachmentsList.innerHTML = '';
anexosState.forEach((f, idx) => {
const row = document.createElement('div');
row.className = 'att-row';
const left = document.createElement('div');
left.innerHTML = `<strong>${f.name}</strong><br><small>${new Date(f.addedAt).toLocaleString()}${(f.size/1024).toFixed(1)} KB</small>`;
const del = document.createElement('button');
del.type = 'button';
del.textContent = 'Excluir';
del.addEventListener('click', () => { anexosState.splice(idx,1); renderAnexos(); });
row.append(left, del);
attachmentsList.appendChild(row);
});
}
anexosInput.addEventListener('change', () => {
const files = Array.from(anexosInput.files || []);
files.forEach(f => anexosState.push({ name: f.name, size: f.size, addedAt: Date.now() }));
anexosInput.value = '';
renderAnexos();
});
// === Validações no submit ===
const form = document.getElementById('pacienteForm');
const errNome = document.getElementById('err-nome');
const errCpf = document.getElementById('err-cpf');
const errEmail = document.getElementById('err-email');
form.addEventListener('submit', (e) => {
e.preventDefault();
let ok = true;
// nome obrigatório
if (!nome.value.trim()) { errNome.classList.add('show'); ok = false; } else errNome.classList.remove('show');
// cpf válido
if (!validaCPF(cpf.value)) { errCpf.classList.add('show'); ok = false; } else errCpf.classList.remove('show');
// email (se preenchido) precisa ser válido
if (email.value && !/^\S+@\S+\.\S+$/.test(email.value)) { errEmail.classList.add('show'); ok = false; } else errEmail.classList.remove('show');
if (!ok) { window.scrollTo({ top: 0, behavior: 'smooth' }); return; }
// Coleta dos dados (exemplo). Aqui você enviaria para sua API com fetch().
const dados = Object.fromEntries(new FormData(form).entries());
dados.cpf = onlyDigits(dados.cpf);
dados.cpfResp = onlyDigits(dados.cpfResp || '');
dados.cep = onlyDigits(dados.cep || '');
dados.anexos = anexosState;
dados.rnGuia = document.getElementById('rnGuia').checked;
console.log('Salvar paciente:', dados);
alert('Paciente salvo (exemplo). Veja o console do navegador para os dados coletados.');
form.reset();
avatarPreview.textContent = 'Prévia';
attachmentsList.innerHTML = '';
anexosState.length = 0;
docNumero.disabled = true;
});
// Cancelar
document.getElementById('btnCancelar').addEventListener('click', () => {
if (confirm('Deseja cancelar e voltar à lista? Alterações serão perdidas.')) {
// Redirecione conforme sua aplicação:
window.history.back();
}
});
</script>
</body>
</html>