feat(gtos): página de gerenciamento de GTOs com filtros e paginação
continuous-integration/webhook Deploy concluído (VPS4)
continuous-integration/webhook Deploy concluído (VPS4)
Backend:
- routes/gtos.js: GET /api/gtos reescrito com filtros (all/vinculada/enviada),
busca ILIKE por patient_name e gto_number, paginação (page/limit),
retorna { rows, total, pages } + thumb_url da primeira imagem vinculada.
Frontend:
- GtosPage.jsx (/gtos): cards de stats clicáveis (Todas/Vinculadas/Enviadas)
com contadores em tempo real; tabela com colunas: thumb, número, paciente,
qtd imagens (badge colorido), descrição, data, status, ações; paginação
numérica; busca com debounce 350ms.
- StatusBadge: Pendente / Vinculada / Enviada com cores distintas.
- GtoDetailModal: info completa, grid de imagens vinculadas com thumb +
botão desvincular, toggle enviada/pendente com feedback visual.
- CreateGtoModal: formulário com número, paciente, descrição.
- Sidebar: item GTOs (fa-file-medical) entre Pacientes e Novo Paciente.
- App.jsx: rota /gtos (ProtectedRoute).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,34 +3,75 @@ const router = express.Router();
|
||||
const db = require('../database');
|
||||
|
||||
// ================================================================
|
||||
// GET /api/gtos?patient=nome — listar GTOs de um paciente
|
||||
// GET /api/gtos — listar todas as GTOs
|
||||
// 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 { patient } = req.query;
|
||||
let rows;
|
||||
if (patient) {
|
||||
rows = await db.all(
|
||||
`SELECT g.*, COUNT(gi.id) as image_count
|
||||
FROM gtos g
|
||||
LEFT JOIN gto_images gi ON gi.gto_id = g.id
|
||||
WHERE g.patient_name = $1
|
||||
GROUP BY g.id
|
||||
ORDER BY g.created_at DESC`,
|
||||
[patient]
|
||||
);
|
||||
} else {
|
||||
rows = await db.all(
|
||||
`SELECT g.*, COUNT(gi.id) as image_count
|
||||
FROM gtos g
|
||||
LEFT JOIN gto_images gi ON gi.gto_id = g.id
|
||||
GROUP BY g.id
|
||||
ORDER BY g.created_at DESC
|
||||
LIMIT 200`
|
||||
);
|
||||
const search = (req.query.search || '').trim();
|
||||
const filter = req.query.filter || 'all';
|
||||
const page = Math.max(1, parseInt(req.query.page) || 1);
|
||||
const limit = Math.min(100, parseInt(req.query.limit) || 20);
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
let params = [];
|
||||
let where = [];
|
||||
let i = 1;
|
||||
|
||||
if (search) {
|
||||
where.push(`(g.patient_name ILIKE $${i} OR g.gto_number ILIKE $${i})`);
|
||||
params.push(`%${search}%`);
|
||||
i++;
|
||||
}
|
||||
res.json(rows);
|
||||
|
||||
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 });
|
||||
|
||||
Reference in New Issue
Block a user