Implement client authentication and admin clinics panel
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Configurações
|
||||
const API_URL = '/api/admin/clinics';
|
||||
|
||||
// Pegar Token JWT do Admin (salvo no localStorage ou cookies)
|
||||
// Assumindo que a sessão já está lidada por cookies ou podemos buscar da rota,
|
||||
// mas usaremos credenciais padrão do fetch se usar cookie de sessão.
|
||||
// Se usar JWT localStorage:
|
||||
const token = localStorage.getItem('token') || '';
|
||||
|
||||
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);
|
||||
if (!response.ok) {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
window.location.href = '/login';
|
||||
return;
|
||||
}
|
||||
throw new Error('Falha ao buscar clínicas');
|
||||
}
|
||||
|
||||
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;">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);
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('logoutBtn').addEventListener('click', () => {
|
||||
localStorage.removeItem('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);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user