feat: implement remote CRUD for patients and update version
continuous-integration/webhook Deploy concluído (VPS4)
continuous-integration/webhook Deploy concluído (VPS4)
This commit is contained in:
@@ -1 +1 @@
|
|||||||
2.1.53
|
2.1.63
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export default function CreatePatientModal({ isOpen, onClose, onCreated }) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
await api.post('/patients', form);
|
await api.post('/images/patients', form);
|
||||||
showToast('Paciente criado com sucesso!', 'success');
|
showToast('Paciente criado com sucesso!', 'success');
|
||||||
setForm({ firstName: '', lastName: '', doctor: '', birthday: '', gender: '0', phone: '', remark: '' });
|
setForm({ firstName: '', lastName: '', doctor: '', birthday: '', gender: '0', phone: '', remark: '' });
|
||||||
onClose();
|
onClose();
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export default function SyncModal({ isOpen, onClose, defaultTab = 'devices' }) {
|
|||||||
// Criar novo paciente se necessário
|
// Criar novo paciente se necessário
|
||||||
if (newPatientMode) {
|
if (newPatientMode) {
|
||||||
const fullName = `${newFirstName.trim()} ${newLastName.trim()}`;
|
const fullName = `${newFirstName.trim()} ${newLastName.trim()}`;
|
||||||
await api.post('/patients', {
|
await api.post('/images/patients', {
|
||||||
firstName: newFirstName.trim(),
|
firstName: newFirstName.trim(),
|
||||||
lastName: newLastName.trim(),
|
lastName: newLastName.trim(),
|
||||||
doctor: newDoctor.trim()
|
doctor: newDoctor.trim()
|
||||||
|
|||||||
@@ -75,9 +75,9 @@ function GtoDetailModal({ isOpen, onClose, gto, onUpdated }) {
|
|||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
|
|
||||||
const ext = getExtension(img.filename);
|
const ext = getExtension(img.filename) || 'jpg';
|
||||||
const pName = (img.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
|
const pName = (img.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '-');
|
||||||
const obs = (img.image_remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
|
const obs = (img.image_remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '-');
|
||||||
|
|
||||||
const fileName = obs ? `${pName}-${obs}.${ext}` : `${pName}.${ext}`;
|
const fileName = obs ? `${pName}-${obs}.${ext}` : `${pName}.${ext}`;
|
||||||
|
|
||||||
@@ -136,17 +136,33 @@ function GtoDetailModal({ isOpen, onClose, gto, onUpdated }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyFilename = async (img) => {
|
const handleCopyFilename = async (img) => {
|
||||||
|
let fileName = 'imagem.jpg';
|
||||||
try {
|
try {
|
||||||
const ext = getExtension(img.filename);
|
const ext = getExtension(img.filename) || 'jpg';
|
||||||
const pName = (img.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
|
const pName = (img.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '-');
|
||||||
const obs = (img.image_remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
|
const obs = (img.image_remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '-');
|
||||||
|
|
||||||
const fileName = obs ? `${pName}-${obs}.${ext}` : `${pName}.${ext}`;
|
fileName = obs ? `${pName}-${obs}.${ext}` : `${pName}.${ext}`;
|
||||||
|
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
await navigator.clipboard.writeText(fileName);
|
await navigator.clipboard.writeText(fileName);
|
||||||
showToast('Nome do arquivo copiado para a área de transferência!', 'success');
|
} else {
|
||||||
|
throw new Error('Fallback');
|
||||||
|
}
|
||||||
|
showToast('Nome copiado: ' + fileName, 'success');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast('Erro ao copiar nome do arquivo.', 'error');
|
try {
|
||||||
|
const textArea = document.createElement("textarea");
|
||||||
|
textArea.value = fileName;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
textArea.remove();
|
||||||
|
showToast('Nome copiado: ' + fileName, 'success');
|
||||||
|
} catch (e) {
|
||||||
|
showToast('Erro ao copiar nome.', 'error');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -226,6 +226,9 @@ export default function ImageDetailsPage() {
|
|||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = cw; canvas.height = ch;
|
canvas.width = cw; canvas.height = ch;
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
|
// Preencher com branco para JPEG
|
||||||
|
ctx.fillStyle = '#FFFFFF';
|
||||||
|
ctx.fillRect(0, 0, cw, ch);
|
||||||
ctx.translate(cw / 2, ch / 2);
|
ctx.translate(cw / 2, ch / 2);
|
||||||
ctx.scale(fH ? -1 : 1, fV ? -1 : 1);
|
ctx.scale(fH ? -1 : 1, fV ? -1 : 1);
|
||||||
ctx.rotate(rad);
|
ctx.rotate(rad);
|
||||||
@@ -233,13 +236,12 @@ export default function ImageDetailsPage() {
|
|||||||
canvas.toBlob((blob) => {
|
canvas.toBlob((blob) => {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = URL.createObjectURL(blob);
|
a.href = URL.createObjectURL(blob);
|
||||||
const pName = (image.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
|
const pName = (image.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '-');
|
||||||
const obs = (image.image_remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_');
|
const obs = (image.image_remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '-');
|
||||||
const suffix = applyTransform ? `${deg}deg` : 'original';
|
a.download = obs ? `${pName}-${obs}.jpg` : `${pName}.jpg`;
|
||||||
a.download = obs ? `${pName}-${obs}_${suffix}.png` : `${pName}_${suffix}.png`;
|
|
||||||
document.body.appendChild(a); a.click(); a.remove();
|
document.body.appendChild(a); a.click(); a.remove();
|
||||||
setTimeout(() => URL.revokeObjectURL(a.href), 4000);
|
setTimeout(() => URL.revokeObjectURL(a.href), 4000);
|
||||||
}, 'image/png');
|
}, 'image/jpeg', 0.95);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
}
|
}
|
||||||
@@ -248,6 +250,19 @@ export default function ImageDetailsPage() {
|
|||||||
img.src = url;
|
img.src = url;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const copyFileNameToClipboard = async () => {
|
||||||
|
try {
|
||||||
|
const pName = (image.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '-');
|
||||||
|
const obs = (image.image_remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '-');
|
||||||
|
const filename = obs ? `${pName}-${obs}.jpg` : `${pName}.jpg`;
|
||||||
|
|
||||||
|
await navigator.clipboard.writeText(filename);
|
||||||
|
showToast('Nome copiado para a área de transferência!', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
showToast('Erro ao copiar nome: ' + e.message, 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const copyImageToClipboard = async () => {
|
const copyImageToClipboard = async () => {
|
||||||
const url = imageUrl;
|
const url = imageUrl;
|
||||||
|
|
||||||
@@ -560,7 +575,15 @@ export default function ImageDetailsPage() {
|
|||||||
onClick={copyImageToClipboard}
|
onClick={copyImageToClipboard}
|
||||||
style={{ width: '100%', height: 44, marginTop: 12, fontSize: '1rem', fontWeight: 600, display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }}
|
style={{ width: '100%', height: 44, marginTop: 12, fontSize: '1rem', fontWeight: 600, display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }}
|
||||||
>
|
>
|
||||||
📋 Copiar Imagem (Área de Transferência)
|
🖼️ Copiar Imagem
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-secondary"
|
||||||
|
onClick={copyFileNameToClipboard}
|
||||||
|
style={{ width: '100%', height: 44, marginTop: 12, fontSize: '1rem', fontWeight: 600, display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }}
|
||||||
|
>
|
||||||
|
📋 Copiar Nome
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -207,6 +207,48 @@ router.get('/by-patient', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// POST /api/images/patients - Criar paciente
|
||||||
|
// Insere um registro "fantasma" (enabled=0) para manter os dados
|
||||||
|
// ================================================================
|
||||||
|
router.post('/patients', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { firstName, lastName, doctor, birthday, gender, phone, remark } = req.body;
|
||||||
|
const fullName = `${firstName || ''} ${lastName || ''}`.trim();
|
||||||
|
if (!fullName) return res.status(400).json({ error: 'Nome obrigatório' });
|
||||||
|
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const dummyGuid = crypto.randomUUID();
|
||||||
|
|
||||||
|
await db.run(
|
||||||
|
`INSERT INTO images (
|
||||||
|
image_guid, patient_name, doctor, remark, enabled, filename, original_filename
|
||||||
|
) VALUES ($1, $2, $3, $4, 0, $5, $6)`,
|
||||||
|
[
|
||||||
|
dummyGuid,
|
||||||
|
fullName,
|
||||||
|
doctor || null,
|
||||||
|
remark || null,
|
||||||
|
'dummy_patient_record',
|
||||||
|
'dummy_patient_record'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const io = req.app.get('io') || global.io;
|
||||||
|
if (io) {
|
||||||
|
io.emit('remote-crud-patient', {
|
||||||
|
action: 'create',
|
||||||
|
patient: { id: dummyGuid, firstName, lastName, doctor, birthday, gender, phone, remark, fullName }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true, message: 'Paciente criado com sucesso' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao criar paciente:', error);
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// PUT /api/images/patients/:name - Editar dados de um paciente
|
// PUT /api/images/patients/:name - Editar dados de um paciente
|
||||||
// Atualiza patient_name, doctor e remark em todas as imagens do paciente.
|
// Atualiza patient_name, doctor e remark em todas as imagens do paciente.
|
||||||
@@ -243,6 +285,15 @@ router.put('/patients/:name', async (req, res) => {
|
|||||||
params
|
params
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const io = req.app.get('io') || global.io;
|
||||||
|
if (io) {
|
||||||
|
io.emit('remote-crud-patient', {
|
||||||
|
action: 'update',
|
||||||
|
oldName: oldName,
|
||||||
|
patient: { newName: newName?.trim(), doctor, remark }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
res.json({ success: true, updated: result.changes });
|
res.json({ success: true, updated: result.changes });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao editar paciente:', error);
|
console.error('Erro ao editar paciente:', error);
|
||||||
@@ -265,6 +316,12 @@ router.delete('/by-patient', async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await db.run('DELETE FROM images WHERE patient_name = $1', [patientName]);
|
await db.run('DELETE FROM images WHERE patient_name = $1', [patientName]);
|
||||||
|
|
||||||
|
const io = req.app.get('io') || global.io;
|
||||||
|
if (io) {
|
||||||
|
io.emit('remote-crud-patient', { action: 'delete', patientName });
|
||||||
|
}
|
||||||
|
|
||||||
res.json({ success: true, count: images.length });
|
res.json({ success: true, count: images.length });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao excluir imagens do paciente:', error);
|
console.error('Erro ao excluir imagens do paciente:', error);
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
const { generateToken } = require('./auth');
|
||||||
|
const db = require('./database');
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
async function test() {
|
||||||
|
const user = await db.get('SELECT * FROM users LIMIT 1');
|
||||||
|
if (!user) return console.log('No user');
|
||||||
|
|
||||||
|
const token = generateToken(user);
|
||||||
|
console.log('Token:', token);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await axios.post('http://localhost:3000/api/images/5227/transform', {
|
||||||
|
rotation: 90,
|
||||||
|
flipH: false,
|
||||||
|
flipV: false,
|
||||||
|
image_remark: 'Teste 502'
|
||||||
|
}, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
console.log('Success:', res.data);
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error:', err.response ? err.response.status + ' ' + err.response.data : err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test();
|
||||||
@@ -1 +1 @@
|
|||||||
2.1.63
|
2.1.64
|
||||||
|
|||||||
Reference in New Issue
Block a user