351 lines
13 KiB
HTML
351 lines
13 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>
|
|
|
|
<label>
|
|
Tenant ID:
|
|
<input id="tenantId" value="default" />
|
|
</label>
|
|
<button id="refresh" style="margin-left: 0.5rem;">Actualizar módulos</button>
|
|
<span id="status" style="margin-left: 1rem; color: #555;"></span>
|
|
|
|
<div id="modules"></div>
|
|
|
|
<div id="module-detail"></div>
|
|
|
|
<script>
|
|
const apiRoot = '/api/admin';
|
|
|
|
const tenantInput = document.getElementById('tenantId');
|
|
const modulesContainer = document.getElementById('modules');
|
|
const detailContainer = document.getElementById('module-detail');
|
|
|
|
function withTenant(url) {
|
|
const tenantId = tenantInput.value || 'default';
|
|
const sep = url.includes('?') ? '&' : '?';
|
|
return `${url}${sep}tenantId=${encodeURIComponent(tenantId)}`;
|
|
}
|
|
|
|
async function api(path, options = {}) {
|
|
const statusEl = document.getElementById('status');
|
|
statusEl.textContent = 'Consultando ' + path + '...';
|
|
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;
|
|
statusEl.textContent = `Error ${res.status}`;
|
|
throw new Error(err || `HTTP ${res.status}`);
|
|
}
|
|
statusEl.textContent = 'OK';
|
|
setTimeout(() => (statusEl.textContent = ''), 1500);
|
|
return data;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
document.getElementById('refresh').addEventListener('click', loadModules);
|
|
tenantInput.addEventListener('change', loadModules);
|
|
|
|
loadModules();
|
|
</script>
|
|
</body>
|
|
</html>
|