diff --git a/dental-server/dental-client/public/version.txt b/dental-server/dental-client/public/version.txt index 6a6199d..d4fe8d4 100644 --- a/dental-server/dental-client/public/version.txt +++ b/dental-server/dental-client/public/version.txt @@ -1 +1 @@ -2.1.63 +2.1.65 diff --git a/dental-server/dental-client/src/components/CreatePatientModal.jsx b/dental-server/dental-client/src/components/CreatePatientModal.jsx index e59fef0..1d7c2be 100644 --- a/dental-server/dental-client/src/components/CreatePatientModal.jsx +++ b/dental-server/dental-client/src/components/CreatePatientModal.jsx @@ -14,8 +14,14 @@ export default function CreatePatientModal({ isOpen, onClose, onCreated }) { e.preventDefault(); setSaving(true); try { - await api.post('/images/patients', form); - showToast('Paciente criado com sucesso!', 'success'); + const response = await api.post('/images/patients', form); + if (response.data?.warning) { + showToast(response.data.warning, 'warning'); + } else if (response.data?.message) { + showToast(response.data.message, 'success'); + } else { + showToast('Paciente criado com sucesso!', 'success'); + } setForm({ firstName: '', lastName: '', doctor: '', birthday: '', gender: '0', phone: '', remark: '' }); onClose(); onCreated?.(); diff --git a/dental-server/dental-client/src/components/EditPatientModal.jsx b/dental-server/dental-client/src/components/EditPatientModal.jsx index d8bf0a1..76c83ff 100644 --- a/dental-server/dental-client/src/components/EditPatientModal.jsx +++ b/dental-server/dental-client/src/components/EditPatientModal.jsx @@ -25,8 +25,14 @@ export default function EditPatientModal({ isOpen, onClose, patient, onSaved }) if (!form.newName.trim()) return showToast('Nome é obrigatório.', 'error'); setSaving(true); try { - await api.put(`/images/patients/${encodeURIComponent(patient.patient_name)}`, form); - showToast('Paciente atualizado com sucesso!', 'success'); + const response = await api.put(`/images/patients/${encodeURIComponent(patient.patient_name)}`, form); + if (response.data?.warning) { + showToast(response.data.warning, 'warning'); + } else if (response.data?.message) { + showToast(response.data.message, 'success'); + } else { + showToast('Paciente atualizado com sucesso!', 'success'); + } onSaved?.(); onClose(); } catch (err) { diff --git a/dental-server/dental-client/src/pages/DashboardPage.jsx b/dental-server/dental-client/src/pages/DashboardPage.jsx index de89ae5..437ed07 100644 --- a/dental-server/dental-client/src/pages/DashboardPage.jsx +++ b/dental-server/dental-client/src/pages/DashboardPage.jsx @@ -253,8 +253,14 @@ export default function DashboardPage() { if (!confirm(`⚠️ Excluir permanentemente todas as imagens de "${patientName}"?\n\nEsta ação não pode ser desfeita.`)) return; showToast('Excluindo imagens do paciente...', 'info'); try { - await api.delete(`/images/by-patient?name=${encodeURIComponent(patientName)}`); - showToast('✅ Todas as imagens foram excluídas!', 'success'); + const response = await api.delete(`/images/by-patient?name=${encodeURIComponent(patientName)}`); + if (response.data?.warning) { + showToast(response.data.warning, 'warning'); + } else if (response.data?.message) { + showToast(response.data.message, 'success'); + } else { + showToast('✅ Todas as imagens foram excluídas!', 'success'); + } loadPatients(); loadClientsList(); } catch (e) { showToast(e.response?.data?.error || 'Falha ao excluir.', 'error'); } diff --git a/dental-server/dental-client/src/pages/PatientsPage.jsx b/dental-server/dental-client/src/pages/PatientsPage.jsx index 444196b..40e49ef 100644 --- a/dental-server/dental-client/src/pages/PatientsPage.jsx +++ b/dental-server/dental-client/src/pages/PatientsPage.jsx @@ -83,8 +83,14 @@ export default function PatientsPage() { const deletePatient = async (patientName) => { if (!confirm(`⚠️ Excluir permanentemente todas as imagens de "${patientName}"?\n\nEsta ação não pode ser desfeita.`)) return; try { - await api.delete(`/images/by-patient?name=${encodeURIComponent(patientName)}`); - showToast('Paciente excluído com sucesso!', 'success'); + const response = await api.delete(`/images/by-patient?name=${encodeURIComponent(patientName)}`); + if (response.data?.warning) { + showToast(response.data.warning, 'warning'); + } else if (response.data?.message) { + showToast(response.data.message, 'success'); + } else { + showToast('Paciente excluído com sucesso!', 'success'); + } loadPatients(true); } catch (e) { showToast(e.response?.data?.error || 'Falha ao excluir.', 'error'); diff --git a/dental-server/package-lock.json b/dental-server/package-lock.json index 597a8ad..77b9cf5 100644 --- a/dental-server/package-lock.json +++ b/dental-server/package-lock.json @@ -1,12 +1,12 @@ { "name": "dental-image-server", - "version": "2.0.5", + "version": "2.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dental-image-server", - "version": "2.0.5", + "version": "2.1.4", "license": "MIT", "dependencies": { "@aws-sdk/client-s3": "^3.1055.0", diff --git a/dental-server/package.json b/dental-server/package.json index 950f5c3..1a2df7c 100644 --- a/dental-server/package.json +++ b/dental-server/package.json @@ -1,6 +1,6 @@ { "name": "dental-image-server", - "version": "2.1.3", + "version": "2.1.4", "description": "Servidor Socket.IO para receber e processar imagens dentais com interface web", "main": "server.js", "scripts": { diff --git a/dental-server/routes/images.js b/dental-server/routes/images.js index 2e6aeaa..678947c 100644 --- a/dental-server/routes/images.js +++ b/dental-server/routes/images.js @@ -2,10 +2,44 @@ const express = require('express'); const router = express.Router(); const db = require('../database'); const imageProcessor = require('../image-processor'); +const crypto = require('crypto'); const path = require('path'); const fs = require('fs').promises; const storage = require('../storage'); +async function emitAndAwaitAck(io, action, payload) { + if (!io) return { success: true, warning: 'Socket.IO não inicializado no servidor.' }; + + const eventId = crypto.randomUUID(); + payload.eventId = eventId; + payload.action = action; + + const ackPromise = new Promise((resolve) => { + if (global.pendingAcks) { + global.pendingAcks.set(eventId, resolve); + setTimeout(() => { + if (global.pendingAcks.has(eventId)) { + global.pendingAcks.delete(eventId); + resolve(null); // Timeout + } + }, 4000); + } else { + resolve(null); + } + }); + + io.emit('remote-crud-patient', payload); + + const result = await ackPromise; + if (result && result.success) { + return { success: true, message: `Sincronizado com PC: ${result.pcName || 'Desconhecido'}`, ackInfo: result }; + } else if (result && !result.success) { + return { success: true, warning: `Erro no app Windows (${result.pcName || 'Desconhecido'}): ${result.error || 'Erro local'}` }; + } else { + return { success: true, warning: 'Salvo na nuvem, porém o App Windows não respondeu. Verifique se o PC está ligado e conectado.' }; + } +} + // ================================================================ // GET /api/images/unique-clients - Buscar lista leve de clínicas // ================================================================ @@ -217,7 +251,6 @@ router.post('/patients', async (req, res) => { 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( @@ -235,14 +268,11 @@ router.post('/patients', async (req, res) => { ); 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 } - }); - } + const syncResult = await emitAndAwaitAck(io, 'create', { + patient: { id: dummyGuid, firstName, lastName, doctor, birthday, gender, phone, remark, fullName } + }); - res.json({ success: true, message: 'Paciente criado com sucesso' }); + res.json(syncResult); } catch (error) { console.error('Erro ao criar paciente:', error); res.status(500).json({ error: error.message }); @@ -286,15 +316,12 @@ router.put('/patients/:name', async (req, res) => { ); 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 }); + const syncResult = await emitAndAwaitAck(io, 'update', { + oldName: oldName, + patient: { newName: newName?.trim(), doctor, remark } + }); + + res.json({ ...syncResult, updated: result.changes }); } catch (error) { console.error('Erro ao editar paciente:', error); res.status(500).json({ error: error.message }); @@ -318,11 +345,9 @@ 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 }); - } + const syncResult = await emitAndAwaitAck(io, 'delete', { patientName }); - res.json({ success: true, count: images.length }); + res.json({ ...syncResult, count: images.length }); } catch (error) { console.error('Erro ao excluir imagens do paciente:', error); res.status(500).json({ error: error.message }); diff --git a/dental-server/server.js b/dental-server/server.js index e701a73..083bd0a 100644 --- a/dental-server/server.js +++ b/dental-server/server.js @@ -42,6 +42,10 @@ const io = socketIo(server, { maxHttpBufferSize: 5e7 // 50MB para suportar raios-x grandes }); +app.set('io', io); +global.io = io; +global.pendingAcks = new Map(); + const connectedClients = []; const PORT = process.env.PORT || 3000; @@ -851,6 +855,15 @@ io.on('connection', (socket) => { } }, 1000); + // Ouvinte para receber confirmações do CRUD remoto + socket.on('remote-crud-ack', (data) => { + if (data && data.eventId && global.pendingAcks && global.pendingAcks.has(data.eventId)) { + const resolve = global.pendingAcks.get(data.eventId); + resolve(data); + global.pendingAcks.delete(data.eventId); + } + }); + // Identificação de clientes socket.on('client-identify', (data) => { console.log('👤 [client-identify] Evento recebido de:', socket.id);