From dfa05f2f78911c8817ff15e0c4f3806ddabdc32e Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Thu, 30 Apr 2026 22:47:25 +0300 Subject: task la: finish admin SPA workflows (permissions matrix UI, trash restore, self-delete UX) --- web/js/admin.js | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 3 deletions(-) (limited to 'web/js/admin.js') diff --git a/web/js/admin.js b/web/js/admin.js index f60000e..bc2913a 100644 --- a/web/js/admin.js +++ b/web/js/admin.js @@ -69,8 +69,18 @@ function renderUsers(users) { `; el.querySelectorAll('button[data-id]').forEach((b) => { b.addEventListener('click', async () => { - try { await API.deleteUser(b.dataset.id); toast('User removed'); await refreshAdmin(); } - catch (err) { toast(err.message, 'error'); } + try { + await API.deleteUser(b.dataset.id); + toast('User removed'); + await refreshAdmin(); + } catch (err) { + const msg = err.message || ''; + if (msg.toLowerCase().includes('cannot delete yourself') || msg.toLowerCase().includes('self-delete')) { + toast('You cannot delete yourself', 'error'); + } else { + toast(err.message, 'error'); + } + } }); }); } @@ -78,18 +88,86 @@ function renderUsers(users) { function renderPermissions(data) { const el = document.getElementById('admin-permissions'); if (!el || !data) return; - el.innerHTML = `

Use the API to manage permissions directly.

`; + if (!data.sets?.length || !data.users?.length) { + el.innerHTML = '

No sets or users to manage.

'; + return; + } + + // Build a lookup: setId -> userId -> role + const roleMap = {}; + data.permissions?.forEach((p) => { + if (!roleMap[p.set_id]) roleMap[p.set_id] = {}; + roleMap[p.set_id][p.user_id] = p.role; + }); + + let html = ''; + data.users.forEach((u) => { + html += ``; + }); + html += ''; + + data.sets.forEach((s) => { + html += ``; + data.users.forEach((u) => { + const role = roleMap[s.id]?.[u.id] || ''; + const selectId = `perm-${s.id}-${u.id}`; + html += ``; + }); + html += ''; + }); + + html += '
Set${esc(u.username)}
${esc(s.name)} + +
'; + el.innerHTML = html; + + el.querySelectorAll('.perm-select').forEach((sel) => { + sel.addEventListener('change', async () => { + const setId = sel.dataset.set; + const userId = sel.dataset.user; + const role = sel.value; + try { + if (role) { + await API.setPermissions({ set_id: parseInt(setId), user_id: parseInt(userId), role }); + toast('Permission granted'); + } else { + await API.delPermissions({ set_id: parseInt(setId), user_id: parseInt(userId) }); + toast('Permission revoked'); + } + } catch (err) { toast(err.message, 'error'); } + }); + }); } function renderTrash(data) { const el = document.getElementById('admin-users'); if (!el || !Array.isArray(data)) return; + if (!data.length) { + el.innerHTML = '

Trash

No deleted items.

'; + return; + } el.innerHTML = `

Trash

`; + + el.querySelectorAll('button[data-id]').forEach((b) => { + b.addEventListener('click', async () => { + try { + await API.restore(b.dataset.id); + toast('Item restored'); + const data = await API.trash(); + renderTrash(data); + } catch (err) { toast(err.message, 'error'); } + }); + }); } function toast(msg, type = 'info') { -- cgit v1.2.3