feat: reestruturacao de menus na sidebar, niveis de acesso e migracao para postgresql/dragonflydb
continuous-integration/webhook Deploy concluido (rx.scoreodonto.com)

This commit is contained in:
VPS 4 Builder
2026-05-26 21:08:48 +02:00
parent dc1801f4a7
commit 5b0d80bf22
29 changed files with 257 additions and 7187 deletions
+45 -5
View File
@@ -5,6 +5,20 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gerenciamento de Clínicas - RF Dental</title>
<script>
// Redirecionar se não estiver autenticado ou não for administrador
if (!localStorage.getItem('auth_token')) {
window.location.replace('/login');
} else if (localStorage.getItem('is_admin') !== 'true') {
window.location.replace('/');
} else {
// Adiciona classe de admin no body para que os estilos de admin sejam exibidos
document.addEventListener('DOMContentLoaded', () => {
document.body.classList.add('is-admin');
});
}
</script>
<!-- Fontes -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -28,15 +42,41 @@
</div>
<nav class="sidebar-nav">
<a href="/" class="nav-item">
<div class="nav-section-title">Menu Principal</div>
<a href="/" class="nav-item" id="menu-dashboard">
<i class="fa-solid fa-chart-line"></i> Dashboard
</a>
<a href="/clients" class="nav-item">
<i class="fa-solid fa-network-wired"></i> Conexões
<a href="/?action=new-patient" class="nav-item" id="menu-new-patient" onclick="if(window.location.pathname === '/') { openCreatePatientModal(); return false; }">
<i class="fa-solid fa-user-plus"></i> Novo Paciente
</a>
<a href="/admin-clinics" class="nav-item active">
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos
<a href="/?action=sync-upload" class="nav-item" id="menu-upload" onclick="if(window.location.pathname === '/') { openSyncModal('upload'); return false; }">
<i class="fa-solid fa-upload"></i> Enviar Imagens
</a>
<a href="/?action=settings" class="nav-item" id="menu-settings" onclick="if(window.location.pathname === '/') { openSettingsModal(); return false; }">
<i class="fa-solid fa-user-gear"></i> Minha Conta
</a>
<div class="admin-only">
<div class="nav-section-title">Área Administrativa</div>
<a href="/clients" class="nav-item" id="menu-clients">
<i class="fa-solid fa-network-wired"></i> Conexões
</a>
<a href="/admin-clinics" class="nav-item active" id="menu-clinics">
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos
</a>
<a href="/?action=users" class="nav-item" id="menu-users" onclick="if(window.location.pathname === '/') { openUserManagementModal(); return false; }">
<i class="fa-solid fa-users"></i> Gerenciar Usuários
</a>
<a href="/?action=sync-devices" class="nav-item" id="menu-sync" onclick="if(window.location.pathname === '/') { openSyncModal('devices'); return false; }">
<i class="fa-solid fa-rotate"></i> Sincronização
</a>
<a href="/?action=plugins" class="nav-item" id="menu-plugins" onclick="if(window.location.pathname === '/') { openPluginsModal(); return false; }">
<i class="fa-solid fa-plug"></i> Plugins / Wasabi
</a>
<a href="/reset" class="nav-item" id="menu-reset" style="color: #dc2626;">
<i class="fa-solid fa-triangle-exclamation"></i> Zerar Sistema
</a>
</div>
</nav>
<div class="sidebar-footer">
+7
View File
@@ -4,6 +4,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Pegar Token JWT do Admin
const token = localStorage.getItem('auth_token') || '';
const isAdmin = localStorage.getItem('is_admin') === 'true';
// Se não tem token, redirecionar imediatamente para login
if (!token) {
@@ -11,6 +12,12 @@ document.addEventListener('DOMContentLoaded', () => {
return;
}
// Se não for administrador, redirecionar imediatamente para a home
if (!isAdmin) {
window.location.href = '/';
return;
}
const fetchOptions = {
headers: {
'Content-Type': 'application/json',
+14 -13
View File
@@ -78,15 +78,11 @@ async function verifyAuth() {
}
function updateAdminMenuVisibility(isAdmin) {
// Esconder links não autorizados da Sidebar
const connectionsLink = document.querySelector('.sidebar-nav .nav-item[href="/clients"]');
const clinicsLink = document.querySelector('.sidebar-nav .nav-item[href="/admin-clinics"]');
if (connectionsLink) connectionsLink.style.display = isAdmin ? 'flex' : 'none';
if (clinicsLink) clinicsLink.style.display = isAdmin ? 'flex' : 'none';
// Esconder botão de gerenciamento de usuários na toolbar
const btnUserManagement = document.getElementById('btnUserManagement');
if (btnUserManagement) btnUserManagement.style.display = isAdmin ? 'flex' : 'none';
if (isAdmin) {
document.body.classList.add('is-admin');
} else {
document.body.classList.remove('is-admin');
}
}
async function fetchWithAuth(url, options = {}) {
@@ -189,7 +185,10 @@ document.addEventListener('DOMContentLoaded', async () => {
if (action === 'new-patient') window.openCreatePatientModal();
else if (action === 'settings') window.openSettingsModal();
else if (action === 'plugins') window.openPluginsModal();
else if (action === 'sync') window.openSyncModal();
else if (action === 'sync-devices') window.openSyncModal('devices');
else if (action === 'sync-upload') window.openSyncModal('upload');
else if (action === 'sync') window.openSyncModal('devices');
else if (action === 'users') window.openUserManagementModal();
// Remove querystring to prevent reopening on reload
window.history.replaceState({}, document.title, window.location.pathname);
@@ -1505,7 +1504,7 @@ window.triggerSync = triggerSync;
let selectedUploadFiles = [];
function openSyncModal() {
function openSyncModal(defaultTab = 'devices') {
const modal = document.getElementById('syncModal');
if (!modal) return;
@@ -1513,13 +1512,15 @@ function openSyncModal() {
modal.style.display = 'flex';
// Resetar abas
switchSyncTab('devices');
switchSyncTab(defaultTab);
// Limpar formulário de upload
resetManualUploadForm();
// Carregar dados
loadSyncDevices();
if (defaultTab === 'devices') {
loadSyncDevices();
}
refreshUploadPatients();
// Escuta de clique fora para fechar modal
+40 -7
View File
@@ -179,9 +179,16 @@
</style>
<script>
// Redirecionar se não estiver autenticado
// Redirecionar se não estiver autenticado ou não for administrador
if (!localStorage.getItem('auth_token')) {
window.location.href = '/login';
window.location.replace('/login');
} else if (localStorage.getItem('is_admin') !== 'true') {
window.location.replace('/');
} else {
// Adiciona classe de admin no body para que os estilos de admin sejam exibidos
document.addEventListener('DOMContentLoaded', () => {
document.body.classList.add('is-admin');
});
}
</script>
</head>
@@ -196,15 +203,41 @@
</div>
<nav class="sidebar-nav">
<a href="/" class="nav-item">
<div class="nav-section-title">Menu Principal</div>
<a href="/" class="nav-item" id="menu-dashboard">
<i class="fa-solid fa-chart-line"></i> Dashboard
</a>
<a href="/clients" class="nav-item active">
<i class="fa-solid fa-network-wired"></i> Conexões
<a href="/?action=new-patient" class="nav-item" id="menu-new-patient" onclick="if(window.location.pathname === '/') { openCreatePatientModal(); return false; }">
<i class="fa-solid fa-user-plus"></i> Novo Paciente
</a>
<a href="/admin-clinics" class="nav-item">
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos
<a href="/?action=sync-upload" class="nav-item" id="menu-upload" onclick="if(window.location.pathname === '/') { openSyncModal('upload'); return false; }">
<i class="fa-solid fa-upload"></i> Enviar Imagens
</a>
<a href="/?action=settings" class="nav-item" id="menu-settings" onclick="if(window.location.pathname === '/') { openSettingsModal(); return false; }">
<i class="fa-solid fa-user-gear"></i> Minha Conta
</a>
<div class="admin-only">
<div class="nav-section-title">Área Administrativa</div>
<a href="/clients" class="nav-item active" id="menu-clients">
<i class="fa-solid fa-network-wired"></i> Conexões
</a>
<a href="/admin-clinics" class="nav-item" id="menu-clinics">
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos
</a>
<a href="/?action=users" class="nav-item" id="menu-users" onclick="if(window.location.pathname === '/') { openUserManagementModal(); return false; }">
<i class="fa-solid fa-users"></i> Gerenciar Usuários
</a>
<a href="/?action=sync-devices" class="nav-item" id="menu-sync" onclick="if(window.location.pathname === '/') { openSyncModal('devices'); return false; }">
<i class="fa-solid fa-rotate"></i> Sincronização
</a>
<a href="/?action=plugins" class="nav-item" id="menu-plugins" onclick="if(window.location.pathname === '/') { openPluginsModal(); return false; }">
<i class="fa-solid fa-plug"></i> Plugins / Wasabi
</a>
<a href="/reset" class="nav-item" id="menu-reset" style="color: #dc2626;">
<i class="fa-solid fa-triangle-exclamation"></i> Zerar Sistema
</a>
</div>
</nav>
<div class="sidebar-footer">
+33 -28
View File
@@ -21,15 +21,41 @@
</div>
<nav class="sidebar-nav">
<a href="/" class="nav-item active">
<div class="nav-section-title">Menu Principal</div>
<a href="/" class="nav-item active" id="menu-dashboard" onclick="if(window.location.pathname === '/') { showView('gallery'); return false; }">
<i class="fa-solid fa-chart-line"></i> Dashboard
</a>
<a href="/clients" class="nav-item">
<i class="fa-solid fa-network-wired"></i> Conexões
<a href="#" class="nav-item" id="menu-new-patient" onclick="openCreatePatientModal(); return false;">
<i class="fa-solid fa-user-plus"></i> Novo Paciente
</a>
<a href="/admin-clinics" class="nav-item">
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos
<a href="#" class="nav-item" id="menu-upload" onclick="openSyncModal('upload'); return false;">
<i class="fa-solid fa-upload"></i> Enviar Imagens
</a>
<a href="#" class="nav-item" id="menu-settings" onclick="openSettingsModal(); return false;">
<i class="fa-solid fa-user-gear"></i> Minha Conta
</a>
<div class="admin-only">
<div class="nav-section-title">Área Administrativa</div>
<a href="/clients" class="nav-item" id="menu-clients">
<i class="fa-solid fa-network-wired"></i> Conexões
</a>
<a href="/admin-clinics" class="nav-item" id="menu-clinics">
<i class="fa-solid fa-hospital"></i> Clínicas e Acessos
</a>
<a href="#" class="nav-item" id="menu-users" onclick="openUserManagementModal(); return false;">
<i class="fa-solid fa-users"></i> Gerenciar Usuários
</a>
<a href="#" class="nav-item" id="menu-sync" onclick="openSyncModal('devices'); return false;">
<i class="fa-solid fa-rotate"></i> Sincronização
</a>
<a href="#" class="nav-item" id="menu-plugins" onclick="openPluginsModal(); return false;">
<i class="fa-solid fa-plug"></i> Plugins / Wasabi
</a>
<a href="/reset" class="nav-item" id="menu-reset" style="color: #dc2626;">
<i class="fa-solid fa-triangle-exclamation"></i> Zerar Sistema
</a>
</div>
</nav>
<div class="sidebar-footer">
@@ -75,8 +101,8 @@
<!-- Scrollable content area -->
<div class="content-scroll">
<!-- Toolbar de Filtros e Ações Rápidas (Migradas da Sidebar) -->
<div class="toolbar glass-card" style="margin-bottom: 24px; padding: 16px; display: flex; gap: 16px; align-items: center; justify-content: space-between; flex-wrap: wrap; background: rgba(255, 255, 255, 0.4); border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 12px; box-shadow: var(--shadow-sm);">
<!-- Toolbar de Filtros e Ações Rápidas -->
<div class="toolbar glass-card" style="margin-bottom: 24px; padding: 16px; display: flex; gap: 16px; align-items: center; background: rgba(255, 255, 255, 0.4); border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 12px; box-shadow: var(--shadow-sm);">
<div class="toolbar-left" style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
<!-- Filtro de Clientes -->
<select id="clientFilter" class="client-filter" style="padding: 10px 16px; border-radius: 10px; border: 1px solid var(--border-color); background: var(--bg-surface); color: var(--text-primary); font-size: 0.95rem; font-weight: 500; outline: none; min-width: 200px; cursor: pointer;">
@@ -87,27 +113,6 @@
👁️ <span id="toggleText">Ocultas</span>
</button>
</div>
<div class="toolbar-right" style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
<button class="btn btn-primary" onclick="openCreatePatientModal()" style="padding: 10px 16px; border-radius: 10px; font-weight: 600; font-size: 0.95rem; background: var(--primary-color); color: white; border: none; cursor: pointer; display: flex; align-items: center; gap: 6px; box-shadow: 0 4px 10px rgba(79, 70, 229, 0.2);">
Novo Paciente
</button>
<button id="btnUserManagement" class="btn btn-secondary" onclick="openUserManagementModal()" title="Gerenciar Usuários" style="display: none; padding: 10px 16px; border-radius: 10px; font-weight: 500; font-size: 0.95rem; border: 1px solid var(--border-color); background: var(--bg-surface); color: var(--text-primary); cursor: pointer; align-items: center; gap: 6px;">
👥 Gerenciar Usuários
</button>
<button class="btn btn-secondary" onclick="openSettingsModal()" title="Configurações" style="padding: 10px 16px; border-radius: 10px; font-weight: 500; font-size: 0.95rem; border: 1px solid var(--border-color); background: var(--bg-surface); color: var(--text-primary); cursor: pointer; display: flex; align-items: center; gap: 6px;">
⚙️ Configurações
</button>
<button class="btn btn-secondary" onclick="openPluginsModal()" title="Plugins / Wasabi" style="padding: 10px 16px; border-radius: 10px; font-weight: 500; font-size: 0.95rem; border: 1px solid var(--border-color); background: var(--bg-surface); color: var(--text-primary); cursor: pointer; display: flex; align-items: center; gap: 6px;">
🔌 Plugins / Wasabi
</button>
<button class="btn btn-secondary" onclick="openSyncModal()" title="Sincronização" style="padding: 10px 16px; border-radius: 10px; font-weight: 500; font-size: 0.95rem; border: 1px solid var(--border-color); background: var(--bg-surface); color: var(--text-primary); cursor: pointer; display: flex; align-items: center; gap: 6px;">
🔄 Sincronização
</button>
<a href="/reset" class="btn btn-danger" title="Zerar Sistema" style="padding: 10px 16px; border-radius: 10px; font-weight: 500; font-size: 0.95rem; background: #fee2e2; color: #dc2626; border: 1px solid #fecaca; text-decoration: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;">
⚠️ Zerar Sistema
</a>
</div>
</div>
<!-- Loading State -->
+38
View File
@@ -942,3 +942,41 @@ body {
max-height: 95vh;
}
}
/* RF Dental Premium Sidebar Menus Additions */
.nav-section-title {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1.2px;
font-weight: 700;
color: var(--text-secondary);
padding: 12px 16px 4px 16px;
opacity: 0.6;
}
/* Ocultação inicial de elementos administrativos */
.admin-only {
display: none !important;
}
/* Exibição quando autenticado como admin */
body.is-admin .admin-only {
display: flex !important;
flex-direction: column;
gap: 8px;
width: 100%;
}
/* Responsividade mobile para o agrupamento admin */
@media (max-width: 768px) {
.nav-section-title {
display: none !important;
}
body.is-admin .admin-only {
display: flex !important;
flex-direction: row !important;
gap: 16px !important;
width: auto;
}
}