feat: fix how complaints are displayed after resolution
Build Images and Deploy / Update-PROD-Stack (push) Successful in 19s
Build Images and Deploy / Update-PROD-Stack (push) Successful in 19s
This commit is contained in:
@@ -585,6 +585,7 @@ const ComplaintReports = {
|
|||||||
JOIN packages p ON c.package_id = p.id
|
JOIN packages p ON c.package_id = p.id
|
||||||
JOIN hunts h ON c.hunt_id = h.id
|
JOIN hunts h ON c.hunt_id = h.id
|
||||||
LEFT JOIN users u ON c.reported_by_user_id = u.id
|
LEFT JOIN users u ON c.reported_by_user_id = u.id
|
||||||
|
WHERE c.status = 'open'
|
||||||
ORDER BY c.created_at ASC
|
ORDER BY c.created_at ASC
|
||||||
`).all();
|
`).all();
|
||||||
},
|
},
|
||||||
@@ -607,6 +608,14 @@ const ComplaintReports = {
|
|||||||
SET status = 'dismissed', reviewed_by = ?, reviewed_at = datetime('now'), resolution_note = ?
|
SET status = 'dismissed', reviewed_by = ?, reviewed_at = datetime('now'), resolution_note = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`).run(reviewedBy, resolutionNote || null, id);
|
`).run(reviewedBy, resolutionNote || null, id);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateModeration(id, reviewedBy, status, resolutionNote) {
|
||||||
|
db.prepare(`
|
||||||
|
UPDATE complaint_reports
|
||||||
|
SET status = ?, reviewed_by = ?, reviewed_at = datetime('now'), resolution_note = ?
|
||||||
|
WHERE id = ?
|
||||||
|
`).run(status, reviewedBy, resolutionNote || null, id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -192,6 +192,33 @@ router.post('/hunts/:id/complaints/:complaintId/dismiss', requireHuntAccess, (re
|
|||||||
res.redirect(`/admin/hunts/${hunt.id}`);
|
res.redirect(`/admin/hunts/${hunt.id}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/hunts/:id/complaints/:complaintId/update', requireHuntAccess, (req, res) => {
|
||||||
|
const hunt = req.hunt;
|
||||||
|
const complaint = ComplaintReports.findById(parseInt(req.params.complaintId, 10));
|
||||||
|
if (!complaint || complaint.hunt_id !== hunt.id) {
|
||||||
|
req.session.flash = { type: 'danger', message: 'Complaint not found.' };
|
||||||
|
return res.redirect(`/admin/hunts/${hunt.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusMap = {
|
||||||
|
open: 'open',
|
||||||
|
resolved: 'resolved',
|
||||||
|
ignored: 'dismissed',
|
||||||
|
dismissed: 'dismissed'
|
||||||
|
};
|
||||||
|
const statusInput = String(req.body.status || '').toLowerCase();
|
||||||
|
const normalizedStatus = statusMap[statusInput];
|
||||||
|
if (!normalizedStatus) {
|
||||||
|
req.session.flash = { type: 'danger', message: 'Invalid complaint status.' };
|
||||||
|
return res.redirect(`/admin/hunts/${hunt.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const note = (req.body.note || '').trim();
|
||||||
|
ComplaintReports.updateModeration(complaint.id, req.session.userId, normalizedStatus, note || null);
|
||||||
|
req.session.flash = { type: 'success', message: 'Complaint updated.' };
|
||||||
|
res.redirect(`/admin/hunts/${hunt.id}`);
|
||||||
|
});
|
||||||
|
|
||||||
// ─── Manage user roles (admin only) ───────────────────────
|
// ─── Manage user roles (admin only) ───────────────────────
|
||||||
router.post('/users/:id/role', requireAdmin, (req, res) => {
|
router.post('/users/:id/role', requireAdmin, (req, res) => {
|
||||||
const userId = parseInt(req.params.id, 10);
|
const userId = parseInt(req.params.id, 10);
|
||||||
|
|||||||
@@ -99,6 +99,9 @@
|
|||||||
<div style="font-weight: 700;">
|
<div style="font-weight: 700;">
|
||||||
<a href="/admin/hunts/<%= c.hunt_id %>" style="color: var(--primary);"><%= c.hunt_name %></a>
|
<a href="/admin/hunts/<%= c.hunt_id %>" style="color: var(--primary);"><%= c.hunt_name %></a>
|
||||||
· Package #<%= c.card_number %>
|
· Package #<%= c.card_number %>
|
||||||
|
<% if (c.status !== 'open') { %>
|
||||||
|
<span class="badge" style="margin-left: 0.35rem; font-size: 0.68rem;"><%= c.status === 'dismissed' ? 'Ignored' : 'Resolved' %></span>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: 0.85rem; color: var(--muted);">
|
<div style="font-size: 0.85rem; color: var(--muted);">
|
||||||
<% if (c.reported_by_name) { %>
|
<% if (c.reported_by_name) { %>
|
||||||
@@ -110,6 +113,14 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
· <time datetime="<%= c.created_at %>"><%= new Date(c.created_at).toLocaleString() %></time>
|
· <time datetime="<%= c.created_at %>"><%= new Date(c.created_at).toLocaleString() %></time>
|
||||||
</div>
|
</div>
|
||||||
|
<% if (c.status !== 'open') { %>
|
||||||
|
<div style="font-size: 0.85rem; color: var(--muted); margin-top: 0.2rem;">
|
||||||
|
Status: <strong><%= c.status === 'dismissed' ? 'Ignored' : 'Resolved' %></strong>
|
||||||
|
</div>
|
||||||
|
<% if (c.resolution_note) { %>
|
||||||
|
<p style="margin: 0.5rem 0 0; font-size: 0.9rem; padding: 0.65rem; background: var(--body-bg); border-radius: 6px;"><strong>Note:</strong> <%= c.resolution_note %></p>
|
||||||
|
<% } %>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<a href="/admin/hunts/<%= c.hunt_id %>" class="btn btn-sm btn-outline">Review in Hunt</a>
|
<a href="/admin/hunts/<%= c.hunt_id %>" class="btn btn-sm btn-outline">Review in Hunt</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -98,13 +98,17 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<% if (typeof complaints !== 'undefined' && complaints && complaints.length > 0) { %>
|
<% if (typeof complaints !== 'undefined' && complaints && complaints.length > 0) { %>
|
||||||
<h2 style="margin-top: 1.5rem; margin-bottom: 1rem;">🚩 Open Complaints <span class="badge" style="font-size: 0.75rem; vertical-align: middle;"><%= complaints.length %></span></h2>
|
<h2 style="margin-top: 1.5rem; margin-bottom: 1rem;">🚩 Complaints <span class="badge" style="font-size: 0.75rem; vertical-align: middle;"><%= complaints.length %></span></h2>
|
||||||
<div class="card" style="margin-bottom: 1rem;">
|
<div class="card" style="margin-bottom: 1rem;">
|
||||||
<% complaints.forEach(c => { %>
|
<% complaints.forEach(c => { %>
|
||||||
<div style="padding: 1rem 0; border-bottom: 1px solid var(--border-color);">
|
<div style="padding: 1rem 0; border-bottom: 1px solid var(--border-color);">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 0.75rem;">
|
<div style="display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 0.75rem;">
|
||||||
<div style="flex: 1; min-width: 240px;">
|
<div style="flex: 1; min-width: 240px;">
|
||||||
<div style="font-weight: 700;">Package #<%= c.card_number %></div>
|
<div style="font-weight: 700;">Package #<%= c.card_number %>
|
||||||
|
<% if (c.status !== 'open') { %>
|
||||||
|
<span class="badge" style="margin-left: 0.35rem; font-size: 0.68rem;"><%= c.status === 'dismissed' ? 'Ignored' : 'Resolved' %></span>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
<div style="font-size: 0.85rem; color: var(--muted);">
|
<div style="font-size: 0.85rem; color: var(--muted);">
|
||||||
<% if (c.reported_by_name) { %>
|
<% if (c.reported_by_name) { %>
|
||||||
Reporter: <a href="/player/<%= c.reported_by_username %>"><%= c.reported_by_name %></a>
|
Reporter: <a href="/player/<%= c.reported_by_username %>"><%= c.reported_by_name %></a>
|
||||||
@@ -117,16 +121,39 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="font-size: 0.8rem; color: var(--muted); margin-top: 0.2rem;"><time datetime="<%= c.created_at %>"><%= new Date(c.created_at).toLocaleString() %></time></div>
|
<div style="font-size: 0.8rem; color: var(--muted); margin-top: 0.2rem;"><time datetime="<%= c.created_at %>"><%= new Date(c.created_at).toLocaleString() %></time></div>
|
||||||
<p style="margin: 0.6rem 0 0; font-size: 0.95rem; background: var(--body-bg); border-radius: 6px; padding: 0.75rem;"><%= c.message %></p>
|
<p style="margin: 0.6rem 0 0; font-size: 0.95rem; background: var(--body-bg); border-radius: 6px; padding: 0.75rem;"><%= c.message %></p>
|
||||||
|
<% if (c.status !== 'open') { %>
|
||||||
|
<div style="font-size: 0.85rem; color: var(--muted); margin-top: 0.45rem;">
|
||||||
|
Status: <strong><%= c.status === 'dismissed' ? 'Ignored' : 'Resolved' %></strong>
|
||||||
|
</div>
|
||||||
|
<% if (c.resolution_note) { %>
|
||||||
|
<p style="margin: 0.45rem 0 0; font-size: 0.9rem; background: var(--body-bg); border-radius: 6px; padding: 0.65rem;"><strong>Note:</strong> <%= c.resolution_note %></p>
|
||||||
|
<% } %>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
|
<div style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
|
||||||
<form method="POST" action="/admin/hunts/<%= hunt.id %>/complaints/<%= c.id %>/resolve" style="margin:0; display:flex; gap:0.4rem; align-items:center;">
|
<% if (c.status === 'open') { %>
|
||||||
<input type="text" name="note" class="form-control" style="max-width: 220px;" placeholder="Optional resolution note">
|
<form method="POST" action="/admin/hunts/<%= hunt.id %>/complaints/<%= c.id %>/resolve" style="margin:0; display:flex; gap:0.4rem; align-items:center;">
|
||||||
<button type="submit" class="btn btn-sm btn-success">Resolve</button>
|
<input type="text" name="note" class="form-control" style="max-width: 220px;" placeholder="Optional resolution note">
|
||||||
</form>
|
<button type="submit" class="btn btn-sm btn-success">Resolve</button>
|
||||||
<form method="POST" action="/admin/hunts/<%= hunt.id %>/complaints/<%= c.id %>/dismiss" style="margin:0; display:flex; gap:0.4rem; align-items:center;">
|
</form>
|
||||||
<input type="text" name="note" class="form-control" style="max-width: 220px;" placeholder="Optional dismissal note">
|
<form method="POST" action="/admin/hunts/<%= hunt.id %>/complaints/<%= c.id %>/dismiss" style="margin:0; display:flex; gap:0.4rem; align-items:center;">
|
||||||
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Dismiss this complaint?')">Dismiss</button>
|
<input type="text" name="note" class="form-control" style="max-width: 220px;" placeholder="Optional ignore note">
|
||||||
</form>
|
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Ignore this complaint?')">Ignore</button>
|
||||||
|
</form>
|
||||||
|
<% } else { %>
|
||||||
|
<details>
|
||||||
|
<summary class="btn btn-sm btn-outline" style="list-style: none; cursor: pointer;">Edit</summary>
|
||||||
|
<form method="POST" action="/admin/hunts/<%= hunt.id %>/complaints/<%= c.id %>/update" style="margin-top: 0.5rem; display:flex; gap:0.4rem; align-items:center; flex-wrap:wrap;">
|
||||||
|
<select name="status" class="form-control" style="max-width: 150px;">
|
||||||
|
<option value="resolved" <%= c.status === 'resolved' ? 'selected' : '' %>>Resolved</option>
|
||||||
|
<option value="ignored" <%= c.status === 'dismissed' ? 'selected' : '' %>>Ignored</option>
|
||||||
|
<option value="open">Open</option>
|
||||||
|
</select>
|
||||||
|
<input type="text" name="note" class="form-control" style="max-width: 260px;" placeholder="Optional note" value="<%= c.resolution_note || '' %>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-primary">Save</button>
|
||||||
|
</form>
|
||||||
|
</details>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user