Files
evento/public/admin.html

487 lines
18 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Admin Panel</title>
<style>
body {
font-family: system-ui, sans-serif;
padding: 2rem;
max-width: 900px;
margin: 0 auto;
}
h1 {
margin-bottom: 0.5rem;
}
.module {
border: 1px solid #ccc;
padding: 1rem;
border-radius: 10px;
margin-bottom: 1rem;
}
.badge {
display: inline-block;
padding: 0.2rem 0.6rem;
background: #e5e7eb;
border-radius: 9999px;
font-size: 0.8rem;
color: #111827;
}
</style>
</head>
<body>
<h1>Admin Panel</h1>
<p>Modular admin UI: cada módulo se registra automáticamente.</p>
<div id="auth" class="module">
<h2>Acceso</h2>
<div id="login-form">
<strong>Iniciar sesión</strong><br/>
<input id="loginEmail" placeholder="Email" style="margin-bottom:0.5rem;" />
<input id="loginPassword" type="password" placeholder="Contraseña" style="margin-bottom:0.5rem;" />
<button id="loginBtn">Iniciar sesión</button>
<div id="loginStatus" style="margin-top:0.5rem;color:#555;"></div>
<hr />
<strong>Registrar usuario</strong><br/>
<input id="registerName" placeholder="Nombre (opcional)" style="margin-bottom:0.5rem;" />
<input id="registerEmail" placeholder="Email" style="margin-bottom:0.5rem;" />
<input id="registerPassword" type="password" placeholder="Contraseña" style="margin-bottom:0.5rem;" />
<input id="registerTenant" placeholder="Tenant ID" value="default" style="margin-bottom:0.5rem;" />
<button id="registerBtn">Registrar</button>
</div>
</div>
<div id="main" style="display: none;">
<label>
Tenant ID:
<input id="tenantId" value="default" />
</label>
<button id="refresh" style="margin-left: 0.5rem;">Actualizar módulos</button>
<button id="logout" style="margin-left: 0.5rem;">Cerrar sesión</button>
<span id="status" style="margin-left: 1rem; color: #555;"></span>
<div id="modules"></div>
<div id="module-detail"></div>
</div>
<script>
const apiRoot = '/api/admin';
const authRoot = '/api/auth';
const tokenKey = 'planner_admin_token';
const authPanel = document.getElementById('auth');
const mainPanel = document.getElementById('main');
const tenantInput = document.getElementById('tenantId');
const modulesContainer = document.getElementById('modules');
const detailContainer = document.getElementById('module-detail');
const statusEl = document.getElementById('status');
const loginStatus = document.getElementById('loginStatus');
const loginEmail = document.getElementById('loginEmail');
const loginPassword = document.getElementById('loginPassword');
const loginBtn = document.getElementById('loginBtn');
const registerName = document.getElementById('registerName');
const registerEmail = document.getElementById('registerEmail');
const registerPassword = document.getElementById('registerPassword');
const registerTenant = document.getElementById('registerTenant');
const registerBtn = document.getElementById('registerBtn');
const logoutBtn = document.getElementById('logout');
let authToken = localStorage.getItem(tokenKey) || '';
function setStatus(msg) {
if (statusEl) statusEl.textContent = msg;
}
function setLoginStatus(msg) {
if (loginStatus) loginStatus.textContent = msg;
}
function setToken(token) {
authToken = token;
if (token) {
localStorage.setItem(tokenKey, token);
} else {
localStorage.removeItem(tokenKey);
}
}
function withTenant(url) {
const tenantId = tenantInput.value || 'default';
const sep = url.includes('?') ? '&' : '?';
return `${url}${sep}tenantId=${encodeURIComponent(tenantId)}`;
}
async function api(path, options = {}) {
setStatus('Consultando ' + path + '...');
if (authToken) {
options.headers = {
...(options.headers || {}),
Authorization: `Bearer ${authToken}`,
};
}
const res = await fetch(withTenant(`${apiRoot}${path}`), options);
const data = await res.json().catch(() => null);
if (!res.ok) {
const err = data?.message ? data.message : res.statusText;
setStatus(`Error ${res.status}`);
throw new Error(err || `HTTP ${res.status}`);
}
setStatus('OK');
setTimeout(() => setStatus(''), 1500);
return data;
}
async function login() {
setLoginStatus('Iniciando sesión...');
try {
const res = await fetch(`${authRoot}/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tenantId: registerTenant.value || 'default',
email: loginEmail.value,
password: loginPassword.value,
}),
});
const data = await res.json();
if (!res.ok || !data.token) {
setLoginStatus(data.message || 'Credenciales inválidas');
return;
}
setToken(data.token);
setLoginStatus('Sesión iniciada');
showMain();
} catch (err) {
setLoginStatus(err.message || err);
}
}
async function register() {
setLoginStatus('Registrando...');
try {
const res = await fetch(`${authRoot}/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tenantId: registerTenant.value || 'default',
name: registerName.value,
email: registerEmail.value,
password: registerPassword.value,
}),
});
const data = await res.json();
if (!res.ok) {
setLoginStatus(data.message || 'Error al registrar');
return;
}
setLoginStatus('Registrado. Inicia sesión.');
} catch (err) {
setLoginStatus(err.message || err);
}
}
function logout() {
setToken('');
showAuth();
}
function showAuth() {
if (authPanel) authPanel.style.display = 'block';
if (mainPanel) mainPanel.style.display = 'none';
}
function showMain() {
if (authPanel) authPanel.style.display = 'none';
if (mainPanel) mainPanel.style.display = 'block';
loadModules();
}
function formatJson(value) {
return `<pre style="white-space: pre-wrap; word-break: break-word;">${JSON.stringify(value, null, 2)}</pre>`;
}
function showError(err) {
detailContainer.innerHTML = `<div class="module"><strong>Error:</strong> ${err.message || err}</div>`;
}
function renderModules(modules) {
modulesContainer.innerHTML = '';
detailContainer.innerHTML = '';
if (!modules.length) {
modulesContainer.innerHTML = '<p>No hay módulos registrados aún.</p>';
return;
}
modules.forEach((mod) => {
const card = document.createElement('div');
card.className = 'module';
const title = document.createElement('h2');
title.textContent = mod.name;
const meta = document.createElement('p');
meta.innerHTML = `<span class="badge">${mod.key}</span> ${mod.description || ''}`;
const button = document.createElement('button');
button.textContent = 'Administrar';
button.onclick = () => showModule(mod.key);
card.append(title, meta, button);
modulesContainer.appendChild(card);
});
}
async function loadModules() {
modulesContainer.innerHTML = '<p>Cargando módulos...</p>';
try {
const modules = await api('/modules');
renderModules(modules);
} catch (err) {
showError(err);
}
}
function createButton(label, onClick) {
const btn = document.createElement('button');
btn.textContent = label;
btn.style.marginRight = '0.5rem';
btn.onclick = onClick;
return btn;
}
function createInput(name, placeholder = '', type = 'text') {
const input = document.createElement('input');
input.name = name;
input.placeholder = placeholder;
input.type = type;
input.style.marginRight = '0.5rem';
input.style.marginBottom = '0.5rem';
return input;
}
async function showModule(key) {
detailContainer.innerHTML = `<div class="module"><h2>Administrar: ${key}</h2><p>Cargando...</p></div>`;
if (key === 'guest') {
return showGuestAdmin();
}
if (key === 'todo') {
return showTodoAdmin();
}
if (key === 'gift') {
return showGiftAdmin();
}
detailContainer.innerHTML = `<div class="module"><p>No hay operaciones definidas para este módulo.</p></div>`;
}
async function showGuestAdmin() {
detailContainer.innerHTML = '';
const panel = document.createElement('div');
panel.className = 'module';
panel.innerHTML = '<h3>Invitados</h3>';
const listBtn = createButton('Listar invitados', async () => {
try {
const guests = await api('/guest');
panel.querySelector('.result')!.innerHTML = formatJson(guests);
} catch (err) {
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
}
});
const createForm = document.createElement('div');
createForm.innerHTML = '<strong>Crear invitado</strong><br/>';
const nameInput = createInput('name', 'Nombre');
const emailInput = createInput('email', 'Email');
const phoneInput = createInput('phone', 'Teléfono');
const createBtn = createButton('Crear', async () => {
try {
const guest = await api('/guest', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: nameInput.value,
email: emailInput.value,
phone: phoneInput.value,
}),
});
panel.querySelector('.result')!.innerHTML = formatJson(guest);
} catch (err) {
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
}
});
const rsvpForm = document.createElement('div');
rsvpForm.innerHTML = '<strong>Actualizar RSVP</strong><br/>';
const idInput = createInput('id', 'ID del invitado');
const rsvpInput = createInput('rsvp', 'true/false');
const updateBtn = createButton('Actualizar', async () => {
try {
const updated = await api(`/guest/${idInput.value}/rsvp`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ rsvp: rsvpInput.value === 'true' }),
});
panel.querySelector('.result')!.innerHTML = formatJson(updated);
} catch (err) {
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
}
});
createForm.append(nameInput, emailInput, phoneInput, createBtn);
rsvpForm.append(idInput, rsvpInput, updateBtn);
const result = document.createElement('div');
result.className = 'result';
result.style.marginTop = '1rem';
panel.append(listBtn, document.createElement('br'), createForm, rsvpForm, result);
detailContainer.appendChild(panel);
}
async function showTodoAdmin() {
detailContainer.innerHTML = '';
const panel = document.createElement('div');
panel.className = 'module';
panel.innerHTML = '<h3>To-dos</h3>';
const listBtn = createButton('Listar To-dos', async () => {
try {
const todos = await api('/todo');
panel.querySelector('.result')!.innerHTML = formatJson(todos);
} catch (err) {
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
}
});
const createForm = document.createElement('div');
createForm.innerHTML = '<strong>Crear To-do</strong><br/>';
const titleInput = createInput('title', 'Título');
const descInput = createInput('description', 'Descripción');
const createBtn = createButton('Crear', async () => {
try {
const todo = await api('/todo', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: titleInput.value,
description: descInput.value,
}),
});
panel.querySelector('.result')!.innerHTML = formatJson(todo);
} catch (err) {
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
}
});
const completeForm = document.createElement('div');
completeForm.innerHTML = '<strong>Marcar completado</strong><br/>';
const todoIdInput = createInput('todoId', 'ID del To-do');
const completeBtn = createButton('Completar', async () => {
try {
const completed = await api(`/todo/${todoIdInput.value}/complete`, {
method: 'PATCH',
});
panel.querySelector('.result')!.innerHTML = formatJson(completed);
} catch (err) {
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
}
});
completeForm.append(todoIdInput, completeBtn);
const result = document.createElement('div');
result.className = 'result';
result.style.marginTop = '1rem';
panel.append(listBtn, document.createElement('br'), createForm, completeForm, result);
detailContainer.appendChild(panel);
}
async function showGiftAdmin() {
detailContainer.innerHTML = '';
const panel = document.createElement('div');
panel.className = 'module';
panel.innerHTML = '<h3>Regalos</h3>';
const listBtn = createButton('Listar regalos', async () => {
try {
const gifts = await api('/gift');
panel.querySelector('.result')!.innerHTML = formatJson(gifts);
} catch (err) {
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
}
});
const createForm = document.createElement('div');
createForm.innerHTML = '<strong>Crear Regalo</strong><br/>';
const nameInput = createInput('name', 'Nombre');
const descInput = createInput('description', 'Descripción');
const priceInput = createInput('price', 'Precio', 'number');
const createBtn = createButton('Crear', async () => {
try {
const gift = await api('/gift', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: nameInput.value,
description: descInput.value,
price: Number(priceInput.value) || undefined,
}),
});
panel.querySelector('.result')!.innerHTML = formatJson(gift);
} catch (err) {
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
}
});
const contributionForm = document.createElement('div');
contributionForm.innerHTML = '<strong>Agregar contribución</strong><br/>';
const giftIdInput = createInput('giftId', 'ID del regalo');
const contribNameInput = createInput('contributorName', 'Nombre');
const amountInput = createInput('amount', 'Monto', 'number');
const contribBtn = createButton('Agregar', async () => {
try {
const contribution = await api(`/gift/${giftIdInput.value}/contribution`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contributorName: contribNameInput.value,
amount: Number(amountInput.value) || 0,
}),
});
panel.querySelector('.result')!.innerHTML = formatJson(contribution);
} catch (err) {
panel.querySelector('.result')!.innerHTML = `<pre>${err.message || err}</pre>`;
}
});
contributionForm.append(giftIdInput, contribNameInput, amountInput, contribBtn);
const result = document.createElement('div');
result.className = 'result';
result.style.marginTop = '1rem';
panel.append(listBtn, document.createElement('br'), createForm, contributionForm, result);
detailContainer.appendChild(panel);
}
loginBtn.addEventListener('click', login);
registerBtn.addEventListener('click', register);
logoutBtn.addEventListener('click', logout);
document.getElementById('refresh').addEventListener('click', loadModules);
tenantInput.addEventListener('change', loadModules);
if (authToken) {
showMain();
} else {
showAuth();
}
</script>
</body>
</html>