Files
rx.scoreodonto.com/dental-server/public/admin-clinics.js
T
VPS 4 Builder 6df952acae
continuous-integration/webhook Deploy concluido (rx.scoreodonto.com)
feat: update to v2.0.3, premium redesign and fix navigation bugs
2026-05-26 19:41:32 +02:00

252 lines
8.6 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
// Configurações
const API_URL = '/api/admin/clinics';
// Pegar Token JWT do Admin
const token = localStorage.getItem('auth_token') || '';
// Se não tem token, redirecionar imediatamente para login
if (!token) {
window.location.href = '/login';
return;
}
const fetchOptions = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
};
// Elementos UI
const clinicsTableBody = document.getElementById('clinicsTableBody');
const addClinicBtn = document.getElementById('addClinicBtn');
const clinicModal = document.getElementById('clinicModal');
const tokenModal = document.getElementById('tokenModal');
const closeModals = document.querySelectorAll('.close-modal');
const closeTokenModal = document.querySelector('.close-token-modal');
const clinicForm = document.getElementById('clinicForm');
const generatedTokenElem = document.getElementById('generatedToken');
const copyTokenBtn = document.getElementById('copyTokenBtn');
// Stats
const totalDevicesElem = document.getElementById('totalDevices');
const activeDevicesElem = document.getElementById('activeDevices');
const blockedDevicesElem = document.getElementById('blockedDevices');
// Inicializar
loadClinics();
// ==========================================
// Carregar Clínicas
// ==========================================
async function loadClinics() {
try {
const response = await fetch(API_URL, fetchOptions);
// Se recebeu 401 ou 403, token expirou — vai para login
if (response.status === 401 || response.status === 403) {
localStorage.removeItem('auth_token');
window.location.href = '/login';
return;
}
// Verificar se a resposta é realmente JSON (evita erro com HTML)
const contentType = response.headers.get('content-type') || '';
if (!contentType.includes('application/json')) {
throw new Error('Sessão expirada ou servidor indisponível. Faça login novamente.');
}
if (!response.ok) {
throw new Error(`Erro HTTP ${response.status}`);
}
const data = await response.json();
if (data.success) {
renderClinics(data.clinics);
updateStats(data.clinics);
} else {
showToast(data.message, 'error');
}
} catch (error) {
console.error(error);
clinicsTableBody.innerHTML = `<tr><td colspan="7" style="text-align:center;color:red;">${error.message || 'Erro ao carregar dados.'}</td></tr>`;
}
}
// ==========================================
// Renderizar Tabela
// ==========================================
function renderClinics(clinics) {
if (clinics.length === 0) {
clinicsTableBody.innerHTML = `<tr><td colspan="7" style="text-align:center;padding:30px;color:#a0aec0;">Nenhum dispositivo cadastrado.</td></tr>`;
return;
}
clinicsTableBody.innerHTML = '';
clinics.forEach(c => {
const isAct = c.is_active;
const statusClass = isAct ? 'status-active' : 'status-inactive';
const statusIcon = isAct ? 'fa-check' : 'fa-ban';
const statusText = isAct ? 'Ativo' : 'Bloqueado';
const tr = document.createElement('tr');
tr.innerHTML = `
<td><span class="status-badge ${statusClass}"><i class="fa-solid ${statusIcon}"></i> ${statusText}</span></td>
<td><strong>${c.clinic_name}</strong></td>
<td><i class="fa-solid fa-desktop" style="color:#a0aec0;margin-right:6px"></i>${c.pc_name || '-'}</td>
<td>${c.email}</td>
<td>${c.last_ip || 'Nunca conectou'}</td>
<td><span class="token-blur" title="Clique para copiar" onclick="navigator.clipboard.writeText('${c.machine_token}');showToast('Token copiado!')">${c.machine_token.split('-')[0]}...</span></td>
<td>
<div style="display:flex;gap:12px;align-items:center;">
<label class="toggle-switch" title="${isAct ? 'Bloquear Acesso' : 'Ativar Acesso'}">
<input type="checkbox" ${isAct ? 'checked' : ''} onchange="toggleStatus(${c.id}, this.checked)">
<span class="slider"></span>
</label>
<button class="icon-btn delete" onclick="deleteClinic(${c.id})" title="Excluir">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</td>
`;
clinicsTableBody.appendChild(tr);
});
}
function updateStats(clinics) {
const total = clinics.length;
const active = clinics.filter(c => c.is_active).length;
const blocked = total - active;
totalDevicesElem.textContent = total;
activeDevicesElem.textContent = active;
blockedDevicesElem.textContent = blocked;
}
// ==========================================
// Cadastrar Clínica
// ==========================================
clinicForm.addEventListener('submit', async (e) => {
e.preventDefault();
const submitBtn = document.getElementById('submitBtn');
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Salvando...';
const formData = new FormData(clinicForm);
const bodyData = Object.fromEntries(formData.entries());
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: fetchOptions.headers,
body: JSON.stringify(bodyData)
});
const data = await response.json();
if (data.success) {
clinicModal.classList.remove('active');
clinicForm.reset();
loadClinics();
// Show Token Modal
generatedTokenElem.textContent = data.machine_token;
tokenModal.classList.add('active');
} else {
showToast(data.message, 'error');
}
} catch (error) {
showToast('Erro de conexão ao salvar', 'error');
} finally {
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fa-solid fa-check"></i> Salvar e Gerar Token';
}
});
// ==========================================
// Toggle Status
// ==========================================
window.toggleStatus = async (id, isActive) => {
try {
const response = await fetch(`${API_URL}/${id}/status`, {
method: 'PUT',
headers: fetchOptions.headers,
body: JSON.stringify({ is_active: isActive })
});
const data = await response.json();
if(data.success) {
showToast(isActive ? 'Dispositivo Ativado' : 'Dispositivo Bloqueado', 'success');
loadClinics();
} else {
showToast(data.message, 'error');
loadClinics(); // revert UI
}
} catch (error) {
showToast('Erro ao alterar status', 'error');
loadClinics();
}
};
// ==========================================
// Deletar
// ==========================================
window.deleteClinic = async (id) => {
if(!confirm('Tem certeza que deseja excluir este dispositivo permanentemente? O Desktop perderá acesso instantaneamente.')) return;
try {
const response = await fetch(`${API_URL}/${id}`, {
method: 'DELETE',
headers: fetchOptions.headers
});
const data = await response.json();
if(data.success) {
showToast('Dispositivo removido', 'success');
loadClinics();
} else {
showToast(data.message, 'error');
}
} catch (error) {
showToast('Erro ao excluir', 'error');
}
};
// ==========================================
// UI Actions (Modals, Copy, Logout)
// ==========================================
addClinicBtn.addEventListener('click', () => clinicModal.classList.add('active'));
closeModals.forEach(btn => btn.addEventListener('click', () => {
clinicModal.classList.remove('active');
}));
closeTokenModal.addEventListener('click', () => {
tokenModal.classList.remove('active');
});
copyTokenBtn.addEventListener('click', () => {
navigator.clipboard.writeText(generatedTokenElem.textContent).then(() => {
showToast('Token copiado com sucesso!', 'success');
copyTokenBtn.innerHTML = '<i class="fa-solid fa-check" style="color:var(--success)"></i>';
setTimeout(() => {
copyTokenBtn.innerHTML = '<i class="fa-regular fa-copy"></i>';
}, 2000);
});
});
window.logout = function() {
localStorage.removeItem('auth_token');
window.location.href = '/login';
};
function showToast(message, type = 'success') {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.className = `toast show ${type}`;
setTimeout(() => {
toast.className = 'toast';
}, 3000);
}
});