feat(patients): fix date bug, convenio dropdown, redesign grupo familiar

- Fix dataNascimento date format error: convert dd/MM/yyyy → yyyy-MM-dd when
  loading EditarPacienteModal so <input type="date"> validates correctly
- Change convênio field from free text to dropdown populated from planos table
- Redesign grupo familiar: search a patient as family member instead of named
  group, with parentesco field (datalist with common options, accepts custom)
- GET /api/pacientes: proper camelCase aliases, LEFT JOINs for grupoFamiliarNome
  and indicadoPorNome, sort parameter support (az/za/convenio/recentes)
- Add startup migrations: ADD COLUMN IF NOT EXISTS for grupofamiliarid,
  grupofamiliarrelacao, indicadorporid on pacientes table
- savePaciente: preserve caller-supplied id instead of overwriting with Math.random
- Add grupoFamiliarRelacao to Paciente type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-05-14 15:54:54 +02:00
parent 1e873cb653
commit ee3f6c3bb2
4 changed files with 130 additions and 105 deletions
+29 -3
View File
@@ -728,14 +728,27 @@ app.put('/api/notificacoes/:id/read', async (req, res) => {
// --- PACIENTES ---
app.get('/api/pacientes', async (req, res) => {
try {
const { q } = req.query;
const { q, sort } = req.query;
const orderMap = { az: 'p.nome ASC', za: 'p.nome DESC', convenio: 'p.convenio ASC NULLS LAST, p.nome ASC', recentes: 'p.nome ASC' };
const orderBy = orderMap[sort] || 'p.nome ASC';
const base = `SELECT p.id, p.nome, p.cpf, p.telefone, p.email,
p.ultimotratamento AS "ultimoTratamento",
p.convenio, p.datanascimento AS "dataNascimento",
p.grupofamiliarid AS "grupoFamiliarId",
pf.nome AS "grupoFamiliarNome",
p.grupofamiliarrelacao AS "grupoFamiliarRelacao",
p.indicadorporid AS "indicadoPorId",
pi.nome AS "indicadoPorNome"
FROM pacientes p
LEFT JOIN pacientes pf ON pf.id = p.grupofamiliarid
LEFT JOIN pacientes pi ON pi.id = p.indicadorporid`;
let text, params;
if (q && q.trim()) {
const term = `%${q.trim().toUpperCase()}%`;
text = `SELECT * FROM pacientes WHERE UPPER(nome) LIKE $1 OR cpf LIKE $1 ORDER BY nome LIMIT 100`;
text = `${base} WHERE UPPER(p.nome) LIKE $1 OR p.cpf LIKE $1 ORDER BY ${orderBy} LIMIT 100`;
params = [term];
} else {
text = `SELECT * FROM pacientes ORDER BY nome LIMIT 50`;
text = `${base} ORDER BY ${orderBy} LIMIT 50`;
params = [];
}
const { rows } = await pool.query(text, params);
@@ -1652,10 +1665,23 @@ async function generateIntelligentNotifications() {
}
}
async function runMigrations() {
const migrations = [
`ALTER TABLE pacientes ADD COLUMN IF NOT EXISTS grupofamiliarid TEXT`,
`ALTER TABLE pacientes ADD COLUMN IF NOT EXISTS grupofamiliarrelacao TEXT`,
`ALTER TABLE pacientes ADD COLUMN IF NOT EXISTS indicadorporid TEXT`,
];
for (const sql of migrations) {
try { await pool.query(sql); } catch (e) { console.error('[MIGRATION]', e.message); }
}
console.log('[MIGRATION] pacientes columns OK');
}
// Start server after all routes are registered
app.listen(PORT, '0.0.0.0', async () => {
console.log(`[SERVER] Running on http://0.0.0.0:${PORT}`);
await loadGoogleCredsFromDb();
await runMigrations();
generateIntelligentNotifications();
setInterval(generateIntelligentNotifications, 5 * 60 * 1000);
});