Add sidebar menu with module actions in admin UI

This commit is contained in:
mberlin
2026-03-18 18:00:34 -03:00
parent a18dde063c
commit c5be7df5e4

View File

@@ -63,8 +63,8 @@
<h1>Admin Panel</h1>
<p>Interfaz de administración (Vue 3 + JWT).</p>
<div id="app">
<div v-if="!token" class="module">
<div id="app" style="display: flex; gap: 1rem;">
<div v-if="!token" class="module" style="flex: 1;">
<h2>Iniciar sesión</h2>
<div class="field">
<label>Email</label>
@@ -104,32 +104,60 @@
<div :class="{ error: registerError, success: registerSuccess }">{{ registerMessage }}</div>
</div>
<div v-else class="module">
<div style="display:flex; align-items:center; gap:1rem; margin-bottom:1rem;">
<div>
<strong>Autenticado como:</strong> {{ user?.name || user?.email }}
<div v-else style="flex: 1; display: flex; gap: 1rem;">
<div style="width: 240px;">
<div class="module">
<div style="display:flex; align-items:center; justify-content:space-between;">
<div>
<div><strong>{{ user?.name || user?.email }}</strong></div>
<div style="font-size:0.85rem; color:#555;">Tenant: {{ tenantId }}</div>
</div>
<button @click="logout">Salir</button>
</div>
</div>
<button @click="logout">Cerrar sesión</button>
</div>
<label>
Tenant ID:
<input v-model="tenantId" />
</label>
<button @click="loadModules" style="margin-left:0.5rem;">Actualizar módulos</button>
<span style="margin-left:1rem; color:#555;">{{ status }}</span>
<div class="module">
<h3>Módulos</h3>
<div v-for="mod in modules" :key="mod.key" style="margin-bottom:0.5rem;">
<button
:style="{
width: '100%',
textAlign: 'left',
padding: '0.5rem',
border: selectedModule === mod.key ? '1px solid #333' : '1px solid #ccc',
background: selectedModule === mod.key ? '#f0f0f0' : '#fff',
}"
@click="selectModule(mod.key)">
{{ mod.name }}
</button>
</div>
</div>
<div v-if="modules.length" style="margin-top:1rem;">
<div v-for="mod in modules" :key="mod.key" class="module">
<h2>{{ mod.name }}</h2>
<p><span class="badge">{{ mod.key }}</span> {{ mod.description }}</p>
<button @click="selectModule(mod.key)">Administrar</button>
<div class="module" v-if="selectedModule">
<h3>Acciones</h3>
<div v-for="action in actions" :key="action.key" style="margin-bottom:0.4rem;">
<button style="width: 100%; text-align: left;" @click="action.handler()">{{ action.label }}</button>
</div>
</div>
</div>
<div v-if="selectedModule" class="module">
<h2>Administrar: {{ selectedModule }}</h2>
<div v-html="moduleHtml"></div>
<div style="flex: 1;">
<div class="module">
<div style="display:flex; justify-content:space-between; align-items:center;">
<div>
<label>
Tenant ID:
<input v-model="tenantId" style="margin-left:0.5rem;" />
</label>
</div>
<div style="color:#555;">{{ status }}</div>
</div>
</div>
<div class="module">
<h2 v-if="selectedModule">Administrar: {{ selectedModule }}</h2>
<div v-html="moduleHtml"></div>
</div>
</div>
</div>
</div>
@@ -263,6 +291,125 @@
}
};
const formatJson = (value) => `<pre style="white-space: pre-wrap; word-break: break-word;">${JSON.stringify(value, null, 2)}</pre>`;
const showResult = (value) => {
moduleHtml.value = formatJson(value);
};
const confirmPrompt = (message, defaultValue = '') => {
const result = prompt(message, defaultValue);
return result === null ? null : result.trim();
};
const listGuests = async () => {
const guests = await api('/guest');
showResult(guests);
};
const createGuest = async () => {
const name = confirmPrompt('Nombre');
if (!name) return;
const email = confirmPrompt('Email');
const phone = confirmPrompt('Teléfono');
const guest = await api('/guest', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email, phone }),
});
showResult(guest);
};
const updateGuestRsvp = async () => {
const id = confirmPrompt('ID del invitado');
if (!id) return;
const rsvp = confirmPrompt('RSVP (true/false)', 'true');
const updated = await api(`/guest/${id}/rsvp`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ rsvp: rsvp === 'true' }),
});
showResult(updated);
};
const listTodos = async () => {
const todos = await api('/todo');
showResult(todos);
};
const createTodo = async () => {
const title = confirmPrompt('Título');
if (!title) return;
const description = confirmPrompt('Descripción');
const todo = await api('/todo', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, description }),
});
showResult(todo);
};
const completeTodo = async () => {
const id = confirmPrompt('ID del To-do');
if (!id) return;
const completed = await api(`/todo/${id}/complete`, {
method: 'PATCH',
});
showResult(completed);
};
const listGifts = async () => {
const gifts = await api('/gift');
showResult(gifts);
};
const createGift = async () => {
const name = confirmPrompt('Nombre del regalo');
if (!name) return;
const description = confirmPrompt('Descripción');
const price = confirmPrompt('Precio');
const gift = await api('/gift', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, description, price: Number(price) || 0 }),
});
showResult(gift);
};
const contributeGift = async () => {
const id = confirmPrompt('ID del regalo');
if (!id) return;
const name = confirmPrompt('Nombre del contribuyente');
const amount = confirmPrompt('Monto');
const contribution = await api(`/gift/${id}/contribution`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ contributorName: name, amount: Number(amount) || 0 }),
});
showResult(contribution);
};
const actions = computed(() => {
const map = {
guest: [
{ key: 'list', label: 'Listar invitados', handler: listGuests },
{ key: 'create', label: 'Crear invitado', handler: createGuest },
{ key: 'rsvp', label: 'Actualizar RSVP', handler: updateGuestRsvp },
],
todo: [
{ key: 'list', label: 'Listar To-dos', handler: listTodos },
{ key: 'create', label: 'Crear To-do', handler: createTodo },
{ key: 'complete', label: 'Completar To-do', handler: completeTodo },
],
gift: [
{ key: 'list', label: 'Listar regalos', handler: listGifts },
{ key: 'create', label: 'Crear regalo', handler: createGift },
{ key: 'contribute', label: 'Agregar contribución', handler: contributeGift },
],
};
return map[selectedModule.value] || [];
});
const loadModules = async () => {
if (!token.value) return;
try {
@@ -274,7 +421,7 @@
const selectModule = (key) => {
selectedModule.value = key;
moduleHtml.value = `<p>Seleccionado <strong>${key}</strong>. Usa los botones del módulo para operar.</p>`;
moduleHtml.value = `<p>Seleccionado <strong>${key}</strong>. Usa las acciones en el menú.</p>`;
};
return {
@@ -285,6 +432,7 @@
modules,
selectedModule,
moduleHtml,
actions,
login,
register,
loginMessage,
@@ -304,4 +452,7 @@
}).mount('#app');
</script>
</body>
</html>
</script>
</body>
</html>