This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
import { canModerate } from '$lib/auth.js';
|
||||
import {
|
||||
canRevokeModerationRestriction,
|
||||
moderationCaseStatuses,
|
||||
moderationCaseStatusLabels,
|
||||
type ModerationOverview,
|
||||
type ModerationQuery,
|
||||
type ModerationGame
|
||||
@@ -14,7 +16,8 @@
|
||||
import {
|
||||
moderationActionLabels,
|
||||
moderationGameName,
|
||||
moderationEventDetail
|
||||
moderationEventDetail,
|
||||
moderationCaseHref
|
||||
} from '$ui/moderation.js';
|
||||
import { timeFormat } from '$ui/stores/time-zone.js';
|
||||
import { pollVisible } from '$ui/poll.js';
|
||||
@@ -52,9 +55,7 @@
|
||||
let generation = 0;
|
||||
let dialogOpen = $state(false);
|
||||
let dialogKey = $state(0);
|
||||
let mode = $state<
|
||||
'restriction' | 'action' | 'case' | 'note' | 'revoke' | 'case-note' | 'resolve'
|
||||
>('restriction');
|
||||
let mode = $state<'restriction' | 'action' | 'case' | 'note' | 'revoke'>('restriction');
|
||||
let seed = $state<{
|
||||
game?: ModerationGame;
|
||||
guid?: string;
|
||||
@@ -160,7 +161,15 @@
|
||||
dialogKey++;
|
||||
dialogOpen = true;
|
||||
}
|
||||
function submitted(target: { game?: ModerationGame; guid?: string | null }) {
|
||||
function submitted(target: { game?: ModerationGame; guid?: string | null; id?: string }) {
|
||||
if (mode === 'case' && target.id) {
|
||||
cancelRefresh();
|
||||
void goto(moderationCaseHref(target.id), { invalidateAll: true }).catch(() => {
|
||||
navigationError = 'Case saved. Open it from the case list or refresh to try again.';
|
||||
void refresh();
|
||||
});
|
||||
return;
|
||||
}
|
||||
notice = 'Saved. Enforcement and delivery status are shown below.';
|
||||
cancelRefresh();
|
||||
const view =
|
||||
@@ -320,7 +329,15 @@
|
||||
class="filters"
|
||||
onsubmit={(event) => {
|
||||
event.preventDefault();
|
||||
void navigate({ q: filters.q, game: filters.game, guid: filters.guid, offset: null });
|
||||
void navigate({
|
||||
q: filters.q,
|
||||
game: filters.game,
|
||||
guid: filters.guid,
|
||||
caseStatus: filters.caseStatus ?? null,
|
||||
priority: filters.priority ?? null,
|
||||
assignedTo: filters.assignedTo ?? null,
|
||||
offset: null
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Field label="Search moderation" class="search"
|
||||
@@ -343,6 +360,47 @@
|
||||
/></Field
|
||||
>
|
||||
<Field label="Player GUID"><Input name="guid" bind:value={filters.guid} mono /></Field>
|
||||
{#if report.query.view === 'cases'}
|
||||
<Field label="Case status"
|
||||
><Select
|
||||
name="caseStatus"
|
||||
bind:value={filters.caseStatus}
|
||||
options={[
|
||||
{ value: '', label: 'All statuses' },
|
||||
{ value: 'active', label: 'Active cases' },
|
||||
{ value: 'closed', label: 'Closed cases' },
|
||||
...moderationCaseStatuses.map((status) => ({
|
||||
value: status,
|
||||
label: moderationCaseStatusLabels[status]
|
||||
}))
|
||||
]}
|
||||
/></Field
|
||||
>
|
||||
<Field label="Priority"
|
||||
><Select
|
||||
name="priority"
|
||||
bind:value={filters.priority}
|
||||
options={[
|
||||
{ value: '', label: 'All priorities' },
|
||||
{ value: 'urgent', label: 'Urgent' },
|
||||
{ value: 'high', label: 'High' },
|
||||
{ value: 'normal', label: 'Normal' },
|
||||
{ value: 'low', label: 'Low' }
|
||||
]}
|
||||
/></Field
|
||||
>
|
||||
<Field label="Assignment"
|
||||
><Select
|
||||
name="assignedTo"
|
||||
bind:value={filters.assignedTo}
|
||||
options={[
|
||||
{ value: '', label: 'Anyone' },
|
||||
{ value: 'me', label: 'Assigned to me' },
|
||||
{ value: 'unassigned', label: 'Unassigned' }
|
||||
]}
|
||||
/></Field
|
||||
>
|
||||
{/if}
|
||||
<Button type="submit" disabled={busy}>Apply</Button>
|
||||
<Button
|
||||
disabled={busy}
|
||||
@@ -354,7 +412,10 @@
|
||||
offset: null,
|
||||
server: null,
|
||||
message: null,
|
||||
matchId: null
|
||||
matchId: null,
|
||||
caseStatus: null,
|
||||
priority: null,
|
||||
assignedTo: null
|
||||
})}>Reset</Button
|
||||
>
|
||||
</form>
|
||||
@@ -507,6 +568,9 @@
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
{#if sanction.caseId}<a href={moderationCaseHref(sanction.caseId)}
|
||||
>View linked case</a
|
||||
>{/if}
|
||||
{#if sanction.note}<p class="private-note">
|
||||
<strong>Private note</strong>
|
||||
{sanction.note}
|
||||
@@ -536,47 +600,46 @@
|
||||
{#if report.cases.length}<div class="record-list">
|
||||
{#each report.cases as item (item.id)}<article class="record">
|
||||
<div class="record-heading">
|
||||
<h2>{item.title}</h2>
|
||||
<h2><a href={moderationCaseHref(item.id)}>{item.title}</a></h2>
|
||||
<StatusBadge state={`moderation-${item.status}`} />
|
||||
</div>
|
||||
<p>
|
||||
<a href={contextHref(playerProfileHref(item.guid), $page.url)}
|
||||
>{item.guid}</a
|
||||
>
|
||||
· {moderationGameName(item.game)} · Opened by {item.actor} · <RelativeTime
|
||||
value={item.createdAt}
|
||||
/>
|
||||
· {moderationGameName(item.game)}
|
||||
</p>
|
||||
<details>
|
||||
<summary>Evidence and staff notes ({item.evidence.length})</summary>
|
||||
<ol class="evidence-list">
|
||||
{#each item.evidence as evidence (evidence.id)}<li>
|
||||
<p>{evidence.text}</p>
|
||||
<small
|
||||
>{evidence.source} · {evidence.actor} · <RelativeTime
|
||||
value={evidence.createdAt}
|
||||
/></small
|
||||
>
|
||||
</li>{:else}<li>No evidence or notes recorded.</li>{/each}
|
||||
</ol>
|
||||
</details>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>Priority</dt>
|
||||
<dd>
|
||||
{item.priority.charAt(0).toUpperCase() +
|
||||
item.priority.slice(1)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Assigned to</dt>
|
||||
<dd>{item.assignee ?? 'Unassigned'}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Updated</dt>
|
||||
<dd><RelativeTime value={item.updatedAt} /></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Opened by</dt>
|
||||
<dd>{item.actor} · <RelativeTime value={item.createdAt} /></dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="record-heading">
|
||||
<CopyValue value={item.id} label="Case ID" />{#if permitted}<div
|
||||
class="row-actions"
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
onclick={() => openDialog('case-note', { id: item.id })}
|
||||
>Add case note</Button
|
||||
>{#if item.status === 'open'}<Button
|
||||
size="sm"
|
||||
onclick={() =>
|
||||
openDialog('resolve', {
|
||||
id: item.id,
|
||||
revision: item.revision
|
||||
})}>Review case</Button
|
||||
>{/if}
|
||||
</div>{/if}
|
||||
<span class="hint"
|
||||
>{item.evidence.length} evidence and staff note {item.evidence
|
||||
.length === 1
|
||||
? 'entry'
|
||||
: 'entries'}</span
|
||||
>
|
||||
<Button href={moderationCaseHref(item.id)} size="sm"
|
||||
>View case</Button
|
||||
>
|
||||
</div>
|
||||
</article>{/each}
|
||||
</div>{:else}<EmptyState
|
||||
@@ -628,10 +691,12 @@
|
||||
: ''}{event.game
|
||||
? ` · ${moderationGameName(event.game)}`
|
||||
: ''}</small
|
||||
>{#if event.subjectId}<CopyValue
|
||||
value={event.subjectId}
|
||||
label="Record ID"
|
||||
/>{/if}
|
||||
>{#if event.subjectId}
|
||||
{#if event.action.startsWith('case.')}<a
|
||||
href={moderationCaseHref(event.subjectId)}>View case</a
|
||||
>{/if}
|
||||
<CopyValue value={event.subjectId} label="Record ID" />
|
||||
{/if}
|
||||
</li>{/each}
|
||||
</ol>{:else}<EmptyState
|
||||
title="No history found"
|
||||
@@ -788,15 +853,6 @@
|
||||
.private-note {
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
.evidence-list {
|
||||
padding-left: var(--space-5);
|
||||
}
|
||||
.evidence-list li {
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
.evidence-list small {
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
.audit-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
|
||||
Reference in New Issue
Block a user