35248fb27b
continuous-integration/webhook Deploy concluído (VPS4)
A refatoração do GET /api/gtos para retornar { rows, total, pages }
quebrou o TransformModal que esperava um array direto.
Correções:
- routes/gtos.js: restaura suporte a ?patient=NOME (match exato) para
o TransformModal continuar filtrando só as GTOs do paciente exibido,
sem retornar resultados de outros pacientes via ILIKE.
- TransformModal.jsx: lê data.rows (novo formato) com fallback para
array direto (compatibilidade durante transição).
As duas superfícies (TransformModal e GtosPage) agora usam a mesma
API, mesmos dados, sem lógica duplicada ou conflitante.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
225 lines
8.5 KiB
JavaScript
225 lines
8.5 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const db = require('../database');
|
|
|
|
// ================================================================
|
|
// GET /api/gtos — listar GTOs com filtros e paginação
|
|
// Query params:
|
|
// search — busca por patient_name ou gto_number (ILIKE)
|
|
// filter — 'all' | 'vinculada' | 'enviada'
|
|
// page — número da página (default 1)
|
|
// limit — itens por página (default 20)
|
|
// ================================================================
|
|
router.get('/', async (req, res) => {
|
|
try {
|
|
const search = (req.query.search || '').trim();
|
|
const patient = (req.query.patient || '').trim(); // match exato (usado pelo TransformModal)
|
|
const filter = req.query.filter || 'all';
|
|
const page = Math.max(1, parseInt(req.query.page) || 1);
|
|
const limit = Math.min(200, parseInt(req.query.limit) || 20);
|
|
const offset = (page - 1) * limit;
|
|
|
|
let params = [];
|
|
let where = [];
|
|
let i = 1;
|
|
|
|
// Match exato por paciente (TransformModal: mostra só as GTOs deste paciente)
|
|
if (patient) {
|
|
where.push(`g.patient_name = $${i}`);
|
|
params.push(patient);
|
|
i++;
|
|
} else if (search) {
|
|
// Busca ILIKE por nome ou número (GtosPage)
|
|
where.push(`(g.patient_name ILIKE $${i} OR g.gto_number ILIKE $${i})`);
|
|
params.push(`%${search}%`);
|
|
i++;
|
|
}
|
|
|
|
if (filter === 'vinculada') {
|
|
where.push(`EXISTS (SELECT 1 FROM gto_images gi2 WHERE gi2.gto_id = g.id)`);
|
|
} else if (filter === 'enviada') {
|
|
where.push(`g.sent = TRUE`);
|
|
}
|
|
|
|
const whereClause = where.length ? `WHERE ${where.join(' AND ')}` : '';
|
|
|
|
// Total para paginação
|
|
const countRow = await db.get(
|
|
`SELECT COUNT(*) as total FROM gtos g ${whereClause}`,
|
|
params
|
|
);
|
|
const total = parseInt(countRow.total);
|
|
|
|
const rows = await db.all(
|
|
`SELECT g.*,
|
|
COUNT(gi.id)::int AS image_count
|
|
FROM gtos g
|
|
LEFT JOIN gto_images gi ON gi.gto_id = g.id
|
|
${whereClause}
|
|
GROUP BY g.id
|
|
ORDER BY g.created_at DESC
|
|
LIMIT ${limit} OFFSET ${offset}`,
|
|
params
|
|
);
|
|
|
|
// Adiciona thumb_url da primeira imagem vinculada para preview
|
|
for (const row of rows) {
|
|
if (parseInt(row.image_count) > 0) {
|
|
const firstImg = await db.get(
|
|
`SELECT i.id FROM gto_images gi
|
|
JOIN images i ON i.id = gi.image_id
|
|
WHERE gi.gto_id = $1
|
|
ORDER BY gi.created_at ASC LIMIT 1`,
|
|
[row.id]
|
|
);
|
|
row.thumb_url = firstImg ? `/api/images/${firstImg.id}/thumbnail` : null;
|
|
} else {
|
|
row.thumb_url = null;
|
|
}
|
|
}
|
|
|
|
res.json({ rows, total, page, limit, pages: Math.ceil(total / limit) });
|
|
} catch (err) {
|
|
console.error('Erro ao listar GTOs:', err);
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ================================================================
|
|
// GET /api/gtos/:id — detalhe de uma GTO com imagens vinculadas
|
|
// ================================================================
|
|
router.get('/:id', async (req, res) => {
|
|
try {
|
|
const gto = await db.get('SELECT * FROM gtos WHERE id = $1', [req.params.id]);
|
|
if (!gto) return res.status(404).json({ error: 'GTO não encontrada' });
|
|
|
|
const images = await db.all(
|
|
`SELECT i.*, gi.id as link_id
|
|
FROM gto_images gi
|
|
JOIN images i ON i.id = gi.image_id
|
|
WHERE gi.gto_id = $1
|
|
ORDER BY gi.created_at DESC`,
|
|
[req.params.id]
|
|
);
|
|
res.json({ ...gto, images });
|
|
} catch (err) {
|
|
console.error('Erro ao buscar GTO:', err);
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ================================================================
|
|
// POST /api/gtos — criar nova GTO
|
|
// Body: { gto_number, patient_name, description }
|
|
// ================================================================
|
|
router.post('/', async (req, res) => {
|
|
try {
|
|
const { gto_number, patient_name, description } = req.body;
|
|
if (!gto_number || !patient_name) {
|
|
return res.status(400).json({ error: 'gto_number e patient_name são obrigatórios' });
|
|
}
|
|
const result = await db.run(
|
|
`INSERT INTO gtos (gto_number, patient_name, description) VALUES ($1, $2, $3) RETURNING id`,
|
|
[gto_number.trim(), patient_name.trim(), description || '']
|
|
);
|
|
const gto = await db.get('SELECT * FROM gtos WHERE id = $1', [result.lastID]);
|
|
res.status(201).json(gto);
|
|
} catch (err) {
|
|
console.error('Erro ao criar GTO:', err);
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ================================================================
|
|
// PUT /api/gtos/:id — atualizar GTO
|
|
// ================================================================
|
|
router.put('/:id', async (req, res) => {
|
|
try {
|
|
const { gto_number, description } = req.body;
|
|
await db.run(
|
|
`UPDATE gtos SET gto_number = $1, description = $2 WHERE id = $3`,
|
|
[gto_number, description || '', req.params.id]
|
|
);
|
|
const gto = await db.get('SELECT * FROM gtos WHERE id = $1', [req.params.id]);
|
|
res.json(gto);
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ================================================================
|
|
// DELETE /api/gtos/:id — remover GTO e vínculos
|
|
// ================================================================
|
|
router.delete('/:id', async (req, res) => {
|
|
try {
|
|
await db.run('DELETE FROM gto_images WHERE gto_id = $1', [req.params.id]);
|
|
await db.run('DELETE FROM gtos WHERE id = $1', [req.params.id]);
|
|
res.json({ success: true });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ================================================================
|
|
// POST /api/gtos/:id/images — vincular imagem à GTO
|
|
// Body: { image_id }
|
|
// ================================================================
|
|
router.post('/:id/images', async (req, res) => {
|
|
try {
|
|
const { image_id } = req.body;
|
|
if (!image_id) return res.status(400).json({ error: 'image_id é obrigatório' });
|
|
|
|
// Verificar se já está vinculada
|
|
const existing = await db.get(
|
|
'SELECT id FROM gto_images WHERE gto_id = $1 AND image_id = $2',
|
|
[req.params.id, image_id]
|
|
);
|
|
if (existing) return res.status(409).json({ error: 'Imagem já vinculada a esta GTO' });
|
|
|
|
await db.run(
|
|
'INSERT INTO gto_images (gto_id, image_id) VALUES ($1, $2) RETURNING id',
|
|
[req.params.id, image_id]
|
|
);
|
|
res.status(201).json({ success: true, message: 'Imagem vinculada à GTO' });
|
|
} catch (err) {
|
|
console.error('Erro ao vincular imagem:', err);
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ================================================================
|
|
// DELETE /api/gtos/:id/images/:imageId — desvincular imagem
|
|
// ================================================================
|
|
router.delete('/:id/images/:imageId', async (req, res) => {
|
|
try {
|
|
await db.run(
|
|
'DELETE FROM gto_images WHERE gto_id = $1 AND image_id = $2',
|
|
[req.params.id, req.params.imageId]
|
|
);
|
|
res.json({ success: true });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ================================================================
|
|
// POST /api/gtos/:id/sent — marcar/desmarcar GTO como enviada
|
|
// Body: { sent: true|false }
|
|
// ================================================================
|
|
router.post('/:id/sent', async (req, res) => {
|
|
try {
|
|
const sent = req.body.sent !== false; // default true
|
|
await db.run(
|
|
'UPDATE gtos SET sent = $1, sent_at = CASE WHEN $1 THEN NOW() ELSE NULL END WHERE id = $2',
|
|
[sent, req.params.id]
|
|
);
|
|
const gto = await db.get('SELECT * FROM gtos WHERE id = $1', [req.params.id]);
|
|
res.json(gto);
|
|
} catch (err) {
|
|
console.error('Erro ao marcar GTO como enviada:', err);
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|