diff --git a/dental-server/dental-client/public/version.txt b/dental-server/dental-client/public/version.txt index 429988a..6a6199d 100644 --- a/dental-server/dental-client/public/version.txt +++ b/dental-server/dental-client/public/version.txt @@ -1 +1 @@ -2.1.53 +2.1.63 diff --git a/dental-server/dental-client/src/components/CreatePatientModal.jsx b/dental-server/dental-client/src/components/CreatePatientModal.jsx index c2e787c..e59fef0 100644 --- a/dental-server/dental-client/src/components/CreatePatientModal.jsx +++ b/dental-server/dental-client/src/components/CreatePatientModal.jsx @@ -14,7 +14,7 @@ export default function CreatePatientModal({ isOpen, onClose, onCreated }) { e.preventDefault(); setSaving(true); try { - await api.post('/patients', form); + await api.post('/images/patients', form); showToast('Paciente criado com sucesso!', 'success'); setForm({ firstName: '', lastName: '', doctor: '', birthday: '', gender: '0', phone: '', remark: '' }); onClose(); diff --git a/dental-server/dental-client/src/components/SyncModal.jsx b/dental-server/dental-client/src/components/SyncModal.jsx index 5433227..ca04cb7 100644 --- a/dental-server/dental-client/src/components/SyncModal.jsx +++ b/dental-server/dental-client/src/components/SyncModal.jsx @@ -85,7 +85,7 @@ export default function SyncModal({ isOpen, onClose, defaultTab = 'devices' }) { // Criar novo paciente se necessário if (newPatientMode) { const fullName = `${newFirstName.trim()} ${newLastName.trim()}`; - await api.post('/patients', { + await api.post('/images/patients', { firstName: newFirstName.trim(), lastName: newLastName.trim(), doctor: newDoctor.trim() diff --git a/dental-server/dental-client/src/pages/GtosPage.jsx b/dental-server/dental-client/src/pages/GtosPage.jsx index be359a9..5695a7f 100644 --- a/dental-server/dental-client/src/pages/GtosPage.jsx +++ b/dental-server/dental-client/src/pages/GtosPage.jsx @@ -75,9 +75,9 @@ function GtoDetailModal({ isOpen, onClose, gto, onUpdated }) { const response = await fetch(url); const blob = await response.blob(); - const ext = getExtension(img.filename); - const pName = (img.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_'); - const obs = (img.image_remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_'); + const ext = getExtension(img.filename) || 'jpg'; + const pName = (img.patient_name || 'paciente').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}`; @@ -136,17 +136,33 @@ function GtoDetailModal({ isOpen, onClose, gto, onUpdated }) { }; const handleCopyFilename = async (img) => { + let fileName = 'imagem.jpg'; try { - const ext = getExtension(img.filename); - const pName = (img.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_'); - const obs = (img.image_remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '_'); + const ext = getExtension(img.filename) || 'jpg'; + const pName = (img.patient_name || 'paciente').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}`; - await navigator.clipboard.writeText(fileName); - showToast('Nome do arquivo copiado para a área de transferência!', 'success'); + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(fileName); + } else { + throw new Error('Fallback'); + } + showToast('Nome copiado: ' + fileName, 'success'); } 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'); + } } }; diff --git a/dental-server/dental-client/src/pages/ImageDetailsPage.jsx b/dental-server/dental-client/src/pages/ImageDetailsPage.jsx index 5107fa4..7a3b060 100644 --- a/dental-server/dental-client/src/pages/ImageDetailsPage.jsx +++ b/dental-server/dental-client/src/pages/ImageDetailsPage.jsx @@ -226,6 +226,9 @@ export default function ImageDetailsPage() { const canvas = document.createElement('canvas'); canvas.width = cw; canvas.height = ch; 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.scale(fH ? -1 : 1, fV ? -1 : 1); ctx.rotate(rad); @@ -233,13 +236,12 @@ export default function ImageDetailsPage() { canvas.toBlob((blob) => { const a = document.createElement('a'); a.href = URL.createObjectURL(blob); - const pName = (image.patient_name || 'paciente').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}_${suffix}.png` : `${pName}_${suffix}.png`; + const pName = (image.patient_name || 'paciente').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '-'); + const obs = (image.image_remark || '').trim().replace(/[\/\\:*?"<>|]/g, '').replace(/\s+/g, '-'); + a.download = obs ? `${pName}-${obs}.jpg` : `${pName}.jpg`; document.body.appendChild(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(a.href), 4000); - }, 'image/png'); + }, 'image/jpeg', 0.95); } catch (e) { window.open(url, '_blank'); } @@ -248,6 +250,19 @@ export default function ImageDetailsPage() { 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 url = imageUrl; @@ -560,7 +575,15 @@ export default function ImageDetailsPage() { onClick={copyImageToClipboard} 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 + + diff --git a/dental-server/routes/images.js b/dental-server/routes/images.js index 9d9e9e6..2e6aeaa 100644 --- a/dental-server/routes/images.js +++ b/dental-server/routes/images.js @@ -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 // Atualiza patient_name, doctor e remark em todas as imagens do paciente. @@ -243,6 +285,15 @@ router.put('/patients/:name', async (req, res) => { 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 }); } catch (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]); + + 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 }); } catch (error) { console.error('Erro ao excluir imagens do paciente:', error); diff --git a/dental-server/test-502.js b/dental-server/test-502.js new file mode 100644 index 0000000..a94557c --- /dev/null +++ b/dental-server/test-502.js @@ -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(); diff --git a/dental-server/version.txt b/dental-server/version.txt index 6a6199d..0b8811f 100644 --- a/dental-server/version.txt +++ b/dental-server/version.txt @@ -1 +1 @@ -2.1.63 +2.1.64