Add case follow-up queues and personal update notifications

This commit is contained in:
2026-10-17 20:00:13 -04:00
parent 90b94911c5
commit b678de2f6f
19 changed files with 1408 additions and 16 deletions
+70 -1
View File
@@ -249,6 +249,9 @@
error={refreshError ? 'Updates paused' : ''}
/>
<Button onclick={() => refresh()} loading={refreshing}>Refresh</Button>
{#if report.unreadCount}<Button href="/moderation?view=cases&review=updates"
>Your case updates ({report.unreadCount})</Button
>{/if}
{#if permitted}<Button variant="primary" onclick={() => openDialog('restriction')}
>Restrict player</Button
><Button onclick={() => openDialog('action')}>Live action</Button>{/if}
@@ -339,6 +342,7 @@
caseStatus: filters.caseStatus ?? null,
priority: filters.priority ?? null,
assignedTo: filters.assignedTo ?? null,
review: filters.review ?? null,
offset: null
});
}}
@@ -364,6 +368,18 @@
>
<Field label="Player GUID"><Input name="guid" bind:value={filters.guid} mono /></Field>
{#if report.query.view === 'cases'}
<Field label="Review queue"
><Select
name="review"
bind:value={filters.review}
options={[
{ value: '', label: 'All cases' },
{ value: 'due', label: 'Overdue reviews' },
{ value: 'scheduled', label: 'Scheduled reviews' },
{ value: 'updates', label: 'Your unread updates' }
]}
/></Field
>
<Field label="Case status"
><Select
name="caseStatus"
@@ -418,7 +434,8 @@
matchId: null,
caseStatus: null,
priority: null,
assignedTo: null
assignedTo: null,
review: null
})}>Reset</Button
>
</form>
@@ -597,6 +614,31 @@
description="Restrictions matching these filters will appear here, including expiry and revocation history."
/>{/if}
{:else if report.query.view === 'cases'}
{#if report.notifications?.length}
<section class="record" aria-label="Your case notifications">
<h2>Your case updates ({report.unreadCount ?? report.notifications.length})</h2>
<ul class="notification-list">
{#each report.notifications as notification (notification.id)}
<li>
<a href={moderationCaseHref(notification.caseId)}
>{notification.caseTitle}</a
>
· {notification.kind === 'evidence'
? 'Evidence or note added'
: 'Assignment changed'} · <RelativeTime
value={notification.createdAt}
/>
</li>
{/each}
</ul>
<p class="hint">Open a case to review and mark its updates read.</p>
</section>
{/if}
<div class="view-actions">
<Button href="/moderation?view=cases&review=due">Overdue reviews</Button><Button
href="/moderation?view=cases&review=updates">Your unread updates</Button
>
</div>
{#if permitted}<div class="view-actions">
<Button onclick={() => openDialog('case')}>Open case</Button>
</div>{/if}
@@ -628,6 +670,27 @@
{/each}
</div>
{:else}<p class="hint">Not assessed</p>{/if}
{#if item.reviewAt}<div class="record-heading">
<Badge
variant={Date.parse(item.reviewAt) <=
Date.parse(report.checkedAt)
? 'warning'
: 'info'}
>{Date.parse(item.reviewAt) <= Date.parse(report.checkedAt)
? 'Overdue review'
: 'Review scheduled'}</Badge
>
<time datetime={item.reviewAt}
>{$timeFormat.dateTime(item.reviewAt)}</time
>
</div>{/if}
{#if item.unreadUpdates}<p>
<Badge variant="info"
>{item.unreadUpdates} unread {item.unreadUpdates === 1
? 'update'
: 'updates'}</Badge
>
</p>{/if}
<dl>
<div>
<dt>Priority</dt>
@@ -900,4 +963,10 @@
line-clamp: 3;
overflow: hidden;
}
.notification-list {
margin: 0;
padding-left: var(--space-5);
display: grid;
gap: var(--space-2);
}
</style>