Files
rx.scoreodonto.com/dental-server/public/index.html
T

472 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>Gerenciador de Imagens Dentais</title>
<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="stylesheet" href="/style.css?v=2.7">
</head>
<body>
<div class="app-container">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<h2>🦷 DentalSys</h2>
</div>
<nav class="sidebar-nav">
<a href="/" class="nav-item active">
<span class="icon">🖼️</span> Pacientes
</a>
<!-- 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;">
<span class="icon"></span> Novo Paciente
</a>
<a href="/clients" class="nav-item">
<span class="icon">🖥️</span> Dados do Cliente
</a>
<a href="#" class="nav-item" onclick="openSettingsModal(); return false;">
<span class="icon">⚙️</span> Configurações
</a>
<a href="#" class="nav-item" onclick="openPluginsModal(); return false;">
<span class="icon">🔌</span> Plugins / Wasabi
</a>
<a href="#" class="nav-item" onclick="openSyncModal(); return false;">
<span class="icon">🔄</span> Sincronização
</a>
<a href="/reset" class="nav-item" style="color: #dc3545;">
<span class="icon">⚠️</span> Zerar Sistema
</a>
<div class="nav-divider"></div>
<button id="toggleDisabledBtn" class="btn btn-secondary" style="margin: 10px 20px; width: calc(100% - 40px);">
<span id="toggleText">Ocultas</span>
</button>
</nav>
</aside>
<!-- Main Content -->
<main class="main-content">
<!-- Header -->
<header class="header">
<div class="header-content">
<div class="header-left">
<button id="backBtn" class="btn btn-back" style="display:none;">
← Voltar
</button>
<h1 id="mainTitle">Galeria de Imagens</h1>
</div>
<div class="header-actions">
<input
type="search"
id="searchInput"
class="search-input"
placeholder="🔍 Pesquisar paciente..."
>
<button class="btn btn-secondary" onclick="loadClientsList()">
<span class="icon">🔄</span>
</button>
</div>
</div>
<!-- Patient sub-header (shown in patient-images view) -->
<div id="patientHeader" class="patient-header" style="display:none;">
<div class="patient-header-info">
<div id="patientTitle" class="patient-header-name"></div>
<div id="patientMeta" class="patient-header-meta"></div>
</div>
</div>
</header>
<!-- Scrollable content area -->
<div class="content-scroll">
<!-- Loading State -->
<div id="loadingState" class="loading">
<div class="spinner"></div>
<p>Carregando...</p>
</div>
<!-- Empty State -->
<div id="emptyState" class="empty-state" style="display: none;">
<div class="empty-icon">📭</div>
<h2>Nenhum resultado encontrado</h2>
<p>As imagens aparecerão aqui quando forem enviadas pelo cliente Windows</p>
</div>
<!-- Images / Patients Grid -->
<div id="imagesGrid" class="images-grid"> </div>
</div><!-- end .content-scroll -->
</main>
</div>
<!-- Transform Modal -->
<div id="transformModal" class="modal">
<div class="modal-content modal-large">
<div class="modal-header" style="display: flex; justify-content: space-between; width: 100%; align-items: center; border-bottom: 1px solid #eee; padding-bottom: 15px;">
<div style="display: flex; align-items: center; gap: 15px; flex-wrap: wrap;">
<h2 id="transformModalTitle" style="margin: 0; font-size: 1.4rem;"><!-- Dynamic Title (Patient + Date) --></h2>
<div style="display: flex; gap: 8px;">
<button id="btnShowTransform" class="btn btn-primary" onclick="toggleModalView('transform')" style="width: 60px; height: 40px; padding: 0; display: flex; align-items: center; justify-content: center; font-size: 1.2rem;" title="Ajustar Imagem">🔄</button>
<button id="btnShowGto" class="btn btn-secondary" onclick="toggleModalView('gto')" style="width: 60px; height: 40px; padding: 0; display: flex; align-items: center; justify-content: center; font-size: 1.2rem;" title="Gerenciar GTOs">📋</button>
<button id="btnShowInfo" class="btn btn-secondary" onclick="toggleModalInfo()" style="width: 60px; height: 40px; padding: 0; display: flex; align-items: center; justify-content: center; font-size: 1.2rem;" title="Informações"></button>
<button id="btnResetImage" class="btn btn-danger" onclick="resetImageToOriginal()" style="width: 60px; height: 40px; padding: 0; display: flex; align-items: center; justify-content: center; font-size: 1.2rem; background-color: #ff6b6b; border-color: #ff6b6b; color: white;" title="Resetar para Original">↩️</button>
</div>
</div>
<span class="modal-close" id="closeTransformModal">×</span>
</div>
<div class="modal-body" style="padding-top: 15px;">
<!-- Bloco oculto de informações -->
<div id="transformExtraInfo" style="display: none; font-size: 0.95rem; color: #555; background: #f8f9fa; padding: 12px 20px; border-radius: 8px; border: 1px solid #eee; margin-bottom: 15px;">
<!-- Preenchido via JS -->
</div>
<!-- View 1: Transforms -->
<div id="transformViewWrapper" style="display: flex; flex-direction: column; flex: 1; min-height: 0; height: 100%;">
<div id="transformOptions" class="transform-grid" style="flex: 1; min-height: 0;"></div>
<div id="transformActionArea" style="display: none; margin-top: 15px; background: #f8f9fa; padding: 15px; border-radius: 8px; flex-shrink: 0; border: 1px solid #e3e8ee;">
<textarea id="newImageRemark" class="search-input" rows="2" style="width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px;" placeholder="Nova observação (opcional)"></textarea>
<div style="display: flex; gap: 10px; margin-top: 10px;">
<button class="btn btn-secondary" style="flex: 1; padding: 12px; font-size: 1.1rem;" onclick="clearTransformSelection()">Cancelar</button>
<button id="btnSaveTransform" class="btn btn-primary" style="flex: 2; padding: 12px; font-size: 1.1rem;" onclick="saveSelectedTransform()">Salvar Nova Imagem</button>
</div>
</div>
<!-- Bloco de Ajuste Fino (Integrado na legenda do card selecionado) -->
<div id="fineTuneContainer" style="display: none;" onclick="event.stopPropagation()">
<div class="fine-tune-left-btns">
<button type="button" onclick="adjustFineTune(-5)" title="Rotacionar -5°">-5°</button>
<button type="button" onclick="adjustFineTune(-1)" title="Rotacionar -1°">-1°</button>
</div>
<div id="fineTuneLabelPlaceholder"></div>
<div class="fine-tune-right-btns">
<button type="button" onclick="adjustFineTune(1)" title="Rotacionar +1°">+1°</button>
<button type="button" onclick="adjustFineTune(5)" title="Rotacionar +5°">+5°</button>
</div>
<button type="button" id="btnResetFineTune" onclick="resetFineTune()" style="display: none;" title="Resetar ajuste fino">↩️ Reset</button>
</div>
</div>
<!-- View 2: GTO List & Link -->
<div id="gtoView" style="display: none;">
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h3 style="margin-top: 0; font-size: 1.1rem; margin-bottom: 15px;">Cadastrar Nova GTO</h3>
<div style="display: flex; gap: 10px;">
<input type="text" id="inlineGtoNumber" class="search-input" placeholder="Número/ID da GTO" style="flex: 1; padding: 10px;">
<input type="text" id="inlineGtoDesc" class="search-input" placeholder="Descrição (Opcional)" style="flex: 2; padding: 10px;">
<button class="btn btn-primary" onclick="inlineCreateGto()">Cadastrar</button>
</div>
</div>
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px;">
<h3 style="margin: 0; font-size: 1.1rem;">Vincular esta imagem a uma GTO:</h3>
<button class="btn btn-success" onclick="linkImageToGto()" id="btnLinkGto">✅ Vincular Selecionada</button>
</div>
<div id="gtoListContainer" style="border: 1px solid #ddd; border-radius: 8px; overflow: hidden;">
<!-- GTO list goes here -->
</div>
</div>
</div>
</div>
</div>
<!-- Modal Novo Paciente -->
<div class="modal" id="create-patient-modal">
<div class="modal-content" style="max-width: 500px; height: auto; border-radius: var(--radius);">
<div class="modal-header">
<h2>Novo Paciente</h2>
<div class="modal-close" onclick="closeCreatePatientModal()">&times;</div>
</div>
<div class="modal-body" style="overflow-y: auto;">
<form id="create-patient-form" onsubmit="submitCreatePatient(event)">
<div class="form-group">
<label>Nome *</label>
<input type="text" id="patient-first-name" required placeholder="Ex: João">
</div>
<div class="form-group">
<label>Sobrenome *</label>
<input type="text" id="patient-last-name" required placeholder="Ex: Silva">
</div>
<div class="form-group">
<label>Dentista</label>
<input type="text" id="patient-doctor" placeholder="Nome do dentista responsável">
</div>
<div style="display: flex; gap: 12px;">
<div class="form-group" style="flex: 1;">
<label>Data de Nascimento</label>
<input type="date" id="patient-birthday">
</div>
<div class="form-group" style="flex: 1;">
<label>Gênero</label>
<select id="patient-gender">
<option value="0">Masculino</option>
<option value="1">Feminino</option>
</select>
</div>
</div>
<div class="form-group">
<label>Telefone</label>
<input type="text" id="patient-phone" placeholder="(11) 99999-9999">
</div>
<div class="form-group">
<label>Observações</label>
<textarea id="patient-remark" rows="3" placeholder="Informações adicionais..."></textarea>
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 10px;">
Salvar Paciente
</button>
</form>
</div>
</div>
</div>
<!-- Modal Nova GTO -->
<div class="modal" id="create-gto-modal">
<div class="modal-content" style="max-width: 400px; height: auto; border-radius: var(--radius);">
<div class="modal-header">
<h2>Nova GTO</h2>
<div class="modal-close" onclick="closeCreateGtoModal()">&times;</div>
</div>
<div class="modal-body">
<form id="create-gto-form" onsubmit="submitCreateGto(event)">
<div class="form-group">
<label>Número/ID da GTO *</label>
<input type="text" id="gto-number" required placeholder="Ex: GTO-2023-001">
</div>
<div class="form-group">
<label>Descrição (opcional)</label>
<textarea id="gto-description" rows="3" placeholder="Informações da guia..."></textarea>
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 10px;">
Criar GTO
</button>
</form>
</div>
</div>
</div>
<!-- Toast de Notificação -->
<div class="toast" id="toast">Operação realizada com sucesso!</div>
<!-- Settings Modal -->
<div id="settingsModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>⚙️ Configurações da Conta</h2>
<span class="close-modal" onclick="closeSettingsModal()">&times;</span>
</div>
<div class="modal-body">
<form id="settingsForm" onsubmit="updateCredentials(event)">
<div class="form-group">
<label for="currentPassword">Senha Atual (Obrigatório)*</label>
<input type="password" id="currentPassword" class="form-control" placeholder="Digite sua senha atual para autorizar" required>
</div>
<hr style="margin: 20px 0; border: none; border-top: 1px solid #eee;">
<p style="margin-bottom: 15px; color: #666; font-size: 0.9rem;">Preencha apenas os campos que deseja alterar:</p>
<div class="form-group">
<label for="newUsername">Novo Nome de Usuário</label>
<input type="text" id="newUsername" class="form-control" placeholder="Deixe em branco para não alterar">
</div>
<div class="form-group">
<label for="newPassword">Nova Senha</label>
<input type="password" id="newPassword" class="form-control" placeholder="Deixe em branco para não alterar">
</div>
<div class="form-actions" style="margin-top: 25px; display: flex; justify-content: flex-end; gap: 10px;">
<button type="button" class="btn btn-secondary" onclick="closeSettingsModal()">Cancelar</button>
<button type="submit" class="btn btn-primary" id="btnSaveSettings">Salvar Alterações</button>
</div>
</form>
</div>
</div>
</div>
<!-- Plugins / Wasabi Modal -->
<div id="pluginsModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>🔌 Configuração do Wasabi Storage</h2>
<span class="modal-close" onclick="closePluginsModal()">&times;</span>
</div>
<div class="modal-body">
<form id="pluginsForm" onsubmit="updatePluginsConfig(event)">
<div style="background: #f8f9fa; padding: 12px 18px; border-radius: 8px; border: 1px solid #eee; margin-bottom: 20px; font-size: 0.95rem; color: #555;">
Configure as credenciais e bucket do **Wasabi S3** para habilitar o armazenamento na nuvem. Se desativado, o sistema usará o armazenamento local do servidor de forma automática.
</div>
<div class="form-group">
<label for="wasabiAccessKey">Access Key *</label>
<input type="text" id="wasabiAccessKey" placeholder="Ex: AIPL5M4G..." required>
</div>
<div class="form-group">
<label for="wasabiSecretKey">Secret Key *</label>
<input type="password" id="wasabiSecretKey" placeholder="Sua Secret Key" required>
</div>
<div class="form-group">
<label for="wasabiBucket">Bucket Ativo *</label>
<input type="text" id="wasabiBucket" placeholder="Ex: meu-bucket-rx" required>
</div>
<div style="display: flex; gap: 12px;">
<div class="form-group" style="flex: 1;">
<label for="wasabiRegion">Região</label>
<input type="text" id="wasabiRegion" placeholder="Ex: us-east-1 (padrão)">
</div>
<div class="form-group" style="flex: 1;">
<label for="wasabiEndpoint">Endpoint</label>
<input type="text" id="wasabiEndpoint" placeholder="Ex: s3.wasabisys.com">
</div>
</div>
<div id="pluginsStatusAlert" style="display: none; padding: 10px; border-radius: 6px; font-size: 0.9rem; margin-top: 15px;"></div>
<div class="form-actions" style="margin-top: 25px; display: flex; justify-content: flex-end; gap: 10px; border-top: 1px solid #eee; padding-top: 15px;">
<button type="button" class="btn btn-secondary" onclick="closePluginsModal()">Cancelar</button>
<button type="submit" class="btn btn-primary" id="btnSavePlugins">Salvar Configurações</button>
</div>
</form>
</div>
</div>
</div>
<!-- Sync / Upload Modal -->
<div id="syncModal" class="modal">
<div class="modal-content" style="max-width: 650px; height: auto; border-radius: var(--radius);">
<div class="modal-header">
<h2>🔄 Área de Sincronização</h2>
<span class="modal-close" onclick="closeSyncModal()">&times;</span>
</div>
<div class="modal-body">
<div class="sync-tabs">
<button type="button" id="tabBtnDevices" class="sync-tab-btn active" onclick="switchSyncTab('devices')">💻 Dispositivos Conectados</button>
<button type="button" id="tabBtnUpload" class="sync-tab-btn" onclick="switchSyncTab('upload')">📤 Envio Manual</button>
</div>
<!-- Tab 1: Dispositivos -->
<div id="tab-devices" class="sync-tab-content active">
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border: 1px solid #eee; margin-bottom: 20px; font-size: 0.95rem; color: #555; line-height: 1.5;">
Os computadores clientes com Windows instalados e ativos enviam imagens em tempo real. Você pode solicitar que eles façam uma sincronização manual forçada com o servidor enviando um sinal.
</div>
<div id="syncDevicesLoading" class="loading" style="padding: 20px 0;">
<div class="spinner" style="width: 30px; height: 30px; margin-bottom: 10px;"></div>
<p>Buscando dispositivos online...</p>
</div>
<div id="syncDevicesEmpty" style="display: none; text-align: center; padding: 30px 20px; color: #666;">
<span style="font-size: 2.5rem; display: block; margin-bottom: 10px;">📭</span>
<p>Nenhum dispositivo Windows conectado no momento.</p>
</div>
<div id="syncDeviceList" class="sync-device-list" style="display: none;">
<!-- Preenchido via Javascript -->
</div>
<div style="margin-top: 25px; border-top: 1px solid #eee; padding-top: 15px; display: flex; justify-content: flex-end; gap: 10px;">
<button type="button" class="btn btn-secondary" onclick="closeSyncModal()">Fechar</button>
<button type="button" class="btn btn-primary" onclick="triggerSyncFromModal()" id="btnTriggerSyncModal">
<span class="icon">🔄</span> Enviar Sinal de Sincronização
</button>
</div>
</div>
<!-- Tab 2: Envio Manual -->
<div id="tab-upload" class="sync-tab-content">
<form id="manualUploadForm" onsubmit="handleManualUploadSubmit(event)">
<div class="form-group">
<label for="uploadPatientSelect">Paciente *</label>
<div style="display: flex; gap: 10px;">
<select id="uploadPatientSelect" onchange="toggleNewPatientFields()" required style="flex: 1;">
<option value="">-- Selecione o Paciente --</option>
<option value="NEW_PATIENT"> Novo Paciente...</option>
</select>
<button type="button" class="btn btn-secondary" onclick="refreshUploadPatients()" title="Atualizar Lista" style="width: 44px; padding: 0; display: flex; align-items: center; justify-content: center;">
🔄
</button>
</div>
</div>
<!-- Campos para Novo Paciente (ocultos por padrão) -->
<div id="newPatientFields" style="display: none; background: #f8f9fa; padding: 15px; border-radius: 8px; border: 1px solid #eee; margin-bottom: 18px; animation: slideDown 0.2s ease;">
<h4 style="margin: 0 0 12px 0; color: var(--dark-color); font-size: 0.95rem;">Dados do Novo Paciente</h4>
<div style="display: flex; gap: 12px; margin-bottom: 12px;">
<div style="flex: 1;">
<label style="font-size: 0.82rem; font-weight: 600; display: block; margin-bottom: 5px;">Nome *</label>
<input type="text" id="uploadNewPatientFirstName" placeholder="Ex: Maria" style="width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px;">
</div>
<div style="flex: 1;">
<label style="font-size: 0.82rem; font-weight: 600; display: block; margin-bottom: 5px;">Sobrenome *</label>
<input type="text" id="uploadNewPatientLastName" placeholder="Ex: Oliveira" style="width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px;">
</div>
</div>
<div>
<label style="font-size: 0.82rem; font-weight: 600; display: block; margin-bottom: 5px;">Dentista Responsável</label>
<input type="text" id="uploadNewPatientDoctor" placeholder="Nome do dentista (opcional)" style="width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px;">
</div>
</div>
<div class="form-group">
<label for="uploadRemark">Observações / Detalhes (Opcional)</label>
<textarea id="uploadRemark" placeholder="Ex: Raio-X Panorâmico" rows="2" style="width: 100%; padding: 10px; border: 2px solid var(--border-color); border-radius: var(--radius-sm); font-family: inherit; font-size: 1rem; resize: vertical;"></textarea>
</div>
<!-- Zona de Drag and Drop -->
<div class="drag-drop-zone" id="uploadDragDropZone">
<div class="drag-drop-icon">📸</div>
<div class="drag-drop-text">Arraste as imagens aqui ou clique para selecionar</div>
<div class="drag-drop-subtext">Suporta arquivos JPG, JPEG e PNG (máx. 10MB por imagem)</div>
<input type="file" id="uploadFileInput" multiple accept="image/png, image/jpeg, image/jpg" style="display: none;">
</div>
<!-- Previews das Imagens Selecionadas -->
<div class="upload-previews" id="uploadPreviewsContainer"></div>
<!-- Progresso de Envio -->
<div class="upload-progress-container" id="uploadProgressContainer" style="display: none;">
<div class="progress-bar-label">
<span id="uploadProgressText">Enviando imagens...</span>
<span id="uploadProgressPercent">0%</span>
</div>
<div class="progress-bar-track">
<div class="progress-bar-fill" id="uploadProgressBarFill"></div>
</div>
</div>
<div class="form-actions" style="margin-top: 25px; border-top: 1px solid #eee; padding-top: 15px; display: flex; justify-content: flex-end; gap: 10px;">
<button type="button" class="btn btn-secondary" onclick="closeSyncModal()">Cancelar</button>
<button type="submit" class="btn btn-primary" id="btnSubmitManualUpload" disabled>Enviar Imagens</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Application Script -->
<script src="/socket.io/socket.io.js"></script>
<script src="/app.js?v=2.7"></script>
</body>
</html>