fix: header sizes, transform image crop, sidebar accordion dropdown
This commit is contained in:
@@ -264,14 +264,14 @@ function updateClientsDropdown() {
|
|||||||
|
|
||||||
// Adiciona o item "Todos os Clientes"
|
// Adiciona o item "Todos os Clientes"
|
||||||
const allItem = document.createElement('div');
|
const allItem = document.createElement('div');
|
||||||
allItem.className = `submenu-item${selectedClient === '' ? ' active' : ''}`;
|
allItem.className = `nav-item${selectedClient === '' ? ' active' : ''}`;
|
||||||
allItem.innerHTML = '📋 Todos os Clientes';
|
allItem.innerHTML = `<span class="icon">📋</span> <span style="flex:1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="Todos os Clientes">Todos os Clientes</span>`;
|
||||||
allItem.onclick = () => selectClientFilter('');
|
allItem.onclick = () => selectClientFilter('');
|
||||||
submenu.appendChild(allItem);
|
submenu.appendChild(allItem);
|
||||||
|
|
||||||
sorted.forEach(c => {
|
sorted.forEach(c => {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
item.className = `submenu-item${selectedClient === c.name ? ' active' : ''}`;
|
item.className = `nav-item${selectedClient === c.name ? ' active' : ''}`;
|
||||||
let lbl = c.name;
|
let lbl = c.name;
|
||||||
if (c.name === 'Upload Web') {
|
if (c.name === 'Upload Web') {
|
||||||
lbl = 'Upload Web 💻';
|
lbl = 'Upload Web 💻';
|
||||||
@@ -279,12 +279,25 @@ function updateClientsDropdown() {
|
|||||||
if (c.status === 'identified') lbl += ' ✅';
|
if (c.status === 'identified') lbl += ' ✅';
|
||||||
else if (c.status === 'historic') lbl += ' 📚';
|
else if (c.status === 'historic') lbl += ' 📚';
|
||||||
}
|
}
|
||||||
item.textContent = lbl;
|
item.innerHTML = `<span class="icon">🔹</span> <span style="flex:1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="${escapeHtml(lbl)}">${escapeHtml(lbl)}</span>`;
|
||||||
item.onclick = () => selectClientFilter(c.name);
|
item.onclick = () => selectClientFilter(c.name);
|
||||||
submenu.appendChild(item);
|
submenu.appendChild(item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.toggleClientsSubmenu = function() {
|
||||||
|
const submenu = document.getElementById('clientsSubmenu');
|
||||||
|
const chevron = document.getElementById('clientsChevron');
|
||||||
|
if (!submenu) return;
|
||||||
|
if (submenu.style.display === 'none') {
|
||||||
|
submenu.style.display = 'flex';
|
||||||
|
if (chevron) chevron.style.transform = 'rotate(180deg)';
|
||||||
|
} else {
|
||||||
|
submenu.style.display = 'none';
|
||||||
|
if (chevron) chevron.style.transform = 'rotate(0deg)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function selectClientFilter(clientName) {
|
function selectClientFilter(clientName) {
|
||||||
selectedClient = clientName;
|
selectedClient = clientName;
|
||||||
if (view === 'patients') {
|
if (view === 'patients') {
|
||||||
@@ -627,8 +640,8 @@ async function showTransformModal(imageId) {
|
|||||||
{ name: 'Original', rotation: 0, flipH: false, flipV: false, css: 'transform: none;' },
|
{ name: 'Original', rotation: 0, flipH: false, flipV: false, css: 'transform: none;' },
|
||||||
{ name: 'Flip Horizontal', rotation: 0, flipH: true, flipV: false, css: 'transform: scaleX(-1);' },
|
{ name: 'Flip Horizontal', rotation: 0, flipH: true, flipV: false, css: 'transform: scaleX(-1);' },
|
||||||
{ name: 'Flip Vertical', rotation: 0, flipH: false, flipV: true, css: 'transform: scaleY(-1);' },
|
{ name: 'Flip Vertical', rotation: 0, flipH: false, flipV: true, css: 'transform: scaleY(-1);' },
|
||||||
{ name: 'Rotação +90°', rotation: 90, flipH: false, flipV: false, css: 'transform: rotate(90deg);' },
|
{ name: 'Rotação +90°', rotation: 90, flipH: false, flipV: false, css: 'transform: rotate(90deg) scale(0.65);' },
|
||||||
{ name: 'Rotação -90°', rotation: -90, flipH: false, flipV: false, css: 'transform: rotate(-90deg);' }
|
{ name: 'Rotação -90°', rotation: -90, flipH: false, flipV: false, css: 'transform: rotate(-90deg) scale(0.65);' }
|
||||||
];
|
];
|
||||||
|
|
||||||
currentTransformations = transformations;
|
currentTransformations = transformations;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<title>Gerenciador de Imagens Dentais</title>
|
<title>Gerenciador de Imagens Dentais</title>
|
||||||
<meta name="description" content="Sistema de gerenciamento de imagens de raio-X dental">
|
<meta name="description" content="Sistema de gerenciamento de imagens de raio-X dental">
|
||||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||||
<link rel="stylesheet" href="/style.css?v=2.9">
|
<link rel="stylesheet" href="/style.css?v=2.7">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
@@ -19,12 +19,23 @@
|
|||||||
<a href="/" class="nav-item active">
|
<a href="/" class="nav-item active">
|
||||||
<span class="icon">🖼️</span> Pacientes
|
<span class="icon">🖼️</span> Pacientes
|
||||||
</a>
|
</a>
|
||||||
<div id="clientsSubmenu" class="sidebar-submenu"></div>
|
|
||||||
|
<!-- Menu Accordion para Sistemas Clientes -->
|
||||||
|
<div class="nav-item" id="clientsAccordionToggle" onclick="toggleClientsSubmenu()" style="cursor:pointer; display: flex; align-items: center; justify-content: space-between;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 10px; overflow: hidden;">
|
||||||
|
<span class="icon">🏢</span> <span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">Todos os Clientes</span>
|
||||||
|
</div>
|
||||||
|
<span id="clientsChevron" style="font-size: 0.8rem; transition: transform 0.3s;">▼</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Submenu dos Clientes -->
|
||||||
|
<div id="clientsSubmenu" class="sidebar-submenu" style="display: none; padding-left: 10px; background: rgba(0,0,0,0.02);"></div>
|
||||||
|
|
||||||
<a href="#" class="nav-item" onclick="openCreatePatientModal(); return false;">
|
<a href="#" class="nav-item" onclick="openCreatePatientModal(); return false;">
|
||||||
<span class="icon">➕</span> Novo Paciente
|
<span class="icon">➕</span> Novo Paciente
|
||||||
</a>
|
</a>
|
||||||
<a href="/clients" class="nav-item">
|
<a href="/clients" class="nav-item">
|
||||||
<span class="icon">🖥️</span> Dispositivos
|
<span class="icon">🖥️</span> Dados do Cliente
|
||||||
</a>
|
</a>
|
||||||
<a href="#" class="nav-item" onclick="openSettingsModal(); return false;">
|
<a href="#" class="nav-item" onclick="openSettingsModal(); return false;">
|
||||||
<span class="icon">⚙️</span> Configurações
|
<span class="icon">⚙️</span> Configurações
|
||||||
@@ -455,6 +466,6 @@
|
|||||||
|
|
||||||
<!-- Application Script -->
|
<!-- Application Script -->
|
||||||
<script src="/socket.io/socket.io.js"></script>
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
<script src="/app.js?v=2.9"></script>
|
<script src="/app.js?v=2.7"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -828,9 +828,10 @@ body {
|
|||||||
/* Evita cortes nas rotações de 90° e -90° causados pelo comportamento de rotação 2D do CSS */
|
/* Evita cortes nas rotações de 90° e -90° causados pelo comportamento de rotação 2D do CSS */
|
||||||
.transform-option:nth-child(4) .transform-image-wrapper img,
|
.transform-option:nth-child(4) .transform-image-wrapper img,
|
||||||
.transform-option:nth-child(5) .transform-image-wrapper img {
|
.transform-option:nth-child(5) .transform-image-wrapper img {
|
||||||
width: auto !important;
|
max-width: 100% !important;
|
||||||
|
max-height: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
aspect-ratio: 1 / 1 !important;
|
|
||||||
object-fit: contain !important;
|
object-fit: contain !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1056,7 +1057,11 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
padding: 20px;
|
height: 70px !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 20px !important;
|
||||||
border-bottom: 1px solid rgba(255,255,255,0.15);
|
border-bottom: 1px solid rgba(255,255,255,0.15);
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
}
|
}
|
||||||
@@ -1124,6 +1129,11 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main-content .header {
|
.main-content .header {
|
||||||
|
height: 70px !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 20px !important;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||||
@@ -1143,7 +1153,14 @@ body {
|
|||||||
.app-container .container { display: none; }
|
.app-container .container { display: none; }
|
||||||
|
|
||||||
/* Header padding */
|
/* Header padding */
|
||||||
.header-content { padding: 14px 20px; }
|
.main-content .header .header-content {
|
||||||
|
padding: 0 !important;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
flex-direction: row !important;
|
||||||
|
justify-content: space-between !important;
|
||||||
|
align-items: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------------
|
/* ---------------------------------------------------------------
|
||||||
IMAGES GRID — grid puro sem flex tricks
|
IMAGES GRID — grid puro sem flex tricks
|
||||||
|
|||||||
+81
-5
@@ -41,6 +41,8 @@ const io = socketIo(server, {
|
|||||||
maxHttpBufferSize: 5e7 // 50MB para suportar raios-x grandes
|
maxHttpBufferSize: 5e7 // 50MB para suportar raios-x grandes
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const connectedClients = [];
|
||||||
|
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
const UPLOAD_DIR = process.env.UPLOAD_DIR || path.join(__dirname, 'uploads');
|
const UPLOAD_DIR = process.env.UPLOAD_DIR || path.join(__dirname, 'uploads');
|
||||||
const PROCESSED_DIR = process.env.PROCESSED_DIR || path.join(__dirname, 'processed');
|
const PROCESSED_DIR = process.env.PROCESSED_DIR || path.join(__dirname, 'processed');
|
||||||
@@ -72,6 +74,43 @@ app.get('/status', (req, res) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Verificar se o nome do computador/cliente já existe
|
||||||
|
app.get('/api/clients/check-name', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const name = (req.query.name || '').trim();
|
||||||
|
const token = req.query.token || req.headers['x-api-key'] || req.headers['authorization']?.split(' ')[1];
|
||||||
|
const clientId = req.query.clientId || '';
|
||||||
|
|
||||||
|
const serverApiKey = process.env.CLIENT_API_KEY || 'rf-dental-secure-key-2026';
|
||||||
|
if (token !== serverApiKey) {
|
||||||
|
return res.status(401).json({ success: false, error: 'Chave de API inválida' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return res.status(400).json({ success: false, error: 'Nome não fornecido' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Verificar se existe no banco de dados na tabela images (busca case-insensitive)
|
||||||
|
const row = await db.get('SELECT COUNT(*) as count FROM images WHERE LOWER(client_name) = LOWER(?)', [name]);
|
||||||
|
const existsInDb = row && row.count > 0;
|
||||||
|
|
||||||
|
// 2. Verificar se existe algum cliente atualmente conectado com esse nome (desconsiderando o próprio clientId)
|
||||||
|
const existsConnected = connectedClients.some(c =>
|
||||||
|
c.name.toLowerCase() === name.toLowerCase() &&
|
||||||
|
(!clientId || c.clientId !== clientId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existsInDb || existsConnected) {
|
||||||
|
return res.json({ success: true, available: false, exists: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true, available: true, exists: false });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao verificar nome do cliente:', error);
|
||||||
|
res.status(500).json({ success: false, error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Debug endpoint for browser logs
|
// Debug endpoint for browser logs
|
||||||
app.post('/api/debug-log', (req, res) => {
|
app.post('/api/debug-log', (req, res) => {
|
||||||
console.log('=== BROWSER HTML ===');
|
console.log('=== BROWSER HTML ===');
|
||||||
@@ -454,7 +493,8 @@ async function checkInstallationMiddleware(req, res, next) {
|
|||||||
req.path.startsWith('/status') ||
|
req.path.startsWith('/status') ||
|
||||||
req.path.startsWith('/socket.io') ||
|
req.path.startsWith('/socket.io') ||
|
||||||
req.path.startsWith('/api/auth') ||
|
req.path.startsWith('/api/auth') ||
|
||||||
req.path.startsWith('/api/install')) {
|
req.path.startsWith('/api/install') ||
|
||||||
|
req.path.startsWith('/api/clients/check-name')) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,8 +591,6 @@ io.use((socket, next) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const connectedClients = [];
|
|
||||||
|
|
||||||
io.on('connection', (socket) => {
|
io.on('connection', (socket) => {
|
||||||
console.log('📱 Cliente conectado:', socket.id);
|
console.log('📱 Cliente conectado:', socket.id);
|
||||||
console.log('📱 Socket transport:', socket.conn.transport.name);
|
console.log('📱 Socket transport:', socket.conn.transport.name);
|
||||||
@@ -587,18 +625,56 @@ io.on('connection', (socket) => {
|
|||||||
console.log('👤 Cliente identificado:', data?.type || 'SEM TYPE', data?.name || 'SEM NAME');
|
console.log('👤 Cliente identificado:', data?.type || 'SEM TYPE', data?.name || 'SEM NAME');
|
||||||
console.log('👤 Dados completos:', JSON.stringify(data, null, 2));
|
console.log('👤 Dados completos:', JSON.stringify(data, null, 2));
|
||||||
|
|
||||||
if (!data || !data.type) {
|
if (!data || !data.type || !data.name) {
|
||||||
console.error('❌ [client-identify] Dados inválidos:', data);
|
console.error('❌ [client-identify] Dados inválidos:', data);
|
||||||
socket.emit('error', { message: 'Dados de identificação inválidos' });
|
socket.emit('error', { message: 'Dados de identificação inválidos' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clientName = data.name.trim();
|
||||||
|
const clientId = data.clientId || '';
|
||||||
|
|
||||||
|
// 1. Validar unicidade em relação aos clientes já conectados
|
||||||
|
const duplicate = connectedClients.find(c =>
|
||||||
|
c.name.toLowerCase() === clientName.toLowerCase() &&
|
||||||
|
c.clientId !== clientId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (duplicate) {
|
||||||
|
console.log(`❌ [client-identify] Conexão rejeitada: O nome "${clientName}" já está em uso por outro dispositivo.`);
|
||||||
|
socket.emit('error', {
|
||||||
|
message: `O nome "${clientName}" já está sendo usado por outro computador neste servidor. Escolha um nome exclusivo.`
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
socket.disconnect(true);
|
||||||
|
}, 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Se for uma reconexão do mesmo clientId, desconectar e limpar os sockets antigos do mesmo cliente
|
||||||
|
const oldConnections = connectedClients.filter(c =>
|
||||||
|
c.clientId === clientId && c.socketId !== socket.id
|
||||||
|
);
|
||||||
|
for (const old of oldConnections) {
|
||||||
|
console.log(`🔄 Desconectando socket antigo ${old.socketId} para o mesmo clientId ${clientId}`);
|
||||||
|
const oldSocket = io.sockets.sockets.get(old.socketId);
|
||||||
|
if (oldSocket) {
|
||||||
|
oldSocket.emit('error', { message: 'Nova conexão estabelecida deste computador.' });
|
||||||
|
oldSocket.disconnect(true);
|
||||||
|
}
|
||||||
|
const idx = connectedClients.indexOf(old);
|
||||||
|
if (idx > -1) {
|
||||||
|
connectedClients.splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const clientInfo = {
|
const clientInfo = {
|
||||||
socketId: socket.id,
|
socketId: socket.id,
|
||||||
type: data.type || 'unknown',
|
type: data.type || 'unknown',
|
||||||
name: data.name || 'Unknown',
|
name: clientName,
|
||||||
version: data.version || '1.0.0',
|
version: data.version || '1.0.0',
|
||||||
system: data.system || 'unknown',
|
system: data.system || 'unknown',
|
||||||
|
clientId: clientId,
|
||||||
connectedAt: new Date().toISOString()
|
connectedAt: new Date().toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user