fix(appeals): always show action controls + add Reopen

The 'resolved' state was a dead-end — no way to change your mind after
clicking approve/deny, and no way to re-action a needs_info appeal once
the submitter replied. Action controls now show on every row regardless
of status, and resolved rows get a Reopen button that moves the appeal
back to pending.

Backend action handler already overwrites status unconditionally, so
this is purely removing a view-layer gate plus adding /:id/reopen as
a thin wrapper over the shared actionAppeal helper.

Chronicler #81
This commit is contained in:
Claude
2026-04-12 00:27:19 +00:00
parent 30a1920d58
commit b156c0b63f
2 changed files with 12 additions and 11 deletions

View File

@@ -84,5 +84,6 @@ async function actionAppeal(req, res, newStatus, label, colorClass) {
router.post('/:id/approve', (req, res) => actionAppeal(req, res, 'approved', 'Approved', 'text-green-500'));
router.post('/:id/deny', (req, res) => actionAppeal(req, res, 'denied', 'Denied', 'text-red-500'));
router.post('/:id/info', (req, res) => actionAppeal(req, res, 'needs_info','Info Requested','text-yellow-500'));
router.post('/:id/reopen', (req, res) => actionAppeal(req, res, 'pending', 'Reopened', 'text-gray-500'));
module.exports = router;

View File

@@ -68,18 +68,18 @@
<span class="px-2 py-1 rounded text-xs font-semibold <%= badge %>"><%= a.status %></span>
</td>
<td class="px-4 py-3 text-xs">
<% if (a.status === 'pending' || a.status === 'needs_info') { %>
<div class="flex flex-col gap-1 min-w-[160px]">
<input type="text" id="notes-<%= a.id %>" name="notes" placeholder="Notes (optional)" class="px-2 py-1 text-xs border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white" />
<div class="flex gap-1">
<button hx-post="/admin/appeals/<%= a.id %>/approve" hx-include="#notes-<%= a.id %>" hx-swap="outerHTML" class="bg-green-600 hover:bg-green-700 text-white px-2 py-1 rounded">Approve</button>
<button hx-post="/admin/appeals/<%= a.id %>/deny" hx-include="#notes-<%= a.id %>" hx-swap="outerHTML" class="bg-red-600 hover:bg-red-700 text-white px-2 py-1 rounded">Deny</button>
<button hx-post="/admin/appeals/<%= a.id %>/info" hx-include="#notes-<%= a.id %>" hx-swap="outerHTML" class="bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded">Info</button>
</div>
<div class="flex flex-col gap-1 min-w-[160px]">
<input type="text" id="notes-<%= a.id %>" name="notes" placeholder="Notes (optional)" class="px-2 py-1 text-xs border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white" />
<div class="flex gap-1 flex-wrap">
<button hx-post="/admin/appeals/<%= a.id %>/approve" hx-include="#notes-<%= a.id %>" hx-swap="outerHTML" class="bg-green-600 hover:bg-green-700 text-white px-2 py-1 rounded">Approve</button>
<button hx-post="/admin/appeals/<%= a.id %>/deny" hx-include="#notes-<%= a.id %>" hx-swap="outerHTML" class="bg-red-600 hover:bg-red-700 text-white px-2 py-1 rounded">Deny</button>
<button hx-post="/admin/appeals/<%= a.id %>/info" hx-include="#notes-<%= a.id %>" hx-swap="outerHTML" class="bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded">Info</button>
<% if (a.status !== 'pending') { %>
<button hx-post="/admin/appeals/<%= a.id %>/reopen" hx-include="#notes-<%= a.id %>" hx-swap="outerHTML" class="bg-gray-600 hover:bg-gray-700 text-white px-2 py-1 rounded">Reopen</button>
<% } %>
</div>
<% } else { %>
<span class="text-gray-400 italic">resolved</span>
<% } %>
</div>
</td>
</td>
</tr>
<% }); %>