Show oversight queue counts
This commit is contained in:
@@ -735,6 +735,13 @@ grid, because the arrow will wrap to another row. The count includes textual
|
|||||||
assistive labeling and strengthens its border and contrast on link hover or
|
assistive labeling and strengthens its border and contrast on link hover or
|
||||||
active state.
|
active state.
|
||||||
|
|
||||||
|
The same compact count cell identifies actionable work in System Oversight
|
||||||
|
navigation. Show it only for queues with unresolved items, such as pending data
|
||||||
|
proposals or avatar reviews, and calculate it within the viewer's permissions.
|
||||||
|
Do not add zero badges or counts to informational registries such as Audit
|
||||||
|
History and System Metrics. Badge text must expose an `awaiting review` label to
|
||||||
|
assistive technology; color supplements that meaning rather than replacing it.
|
||||||
|
|
||||||
Pending records may be used as dependencies in later proposals by any
|
Pending records may be used as dependencies in later proposals by any
|
||||||
contributor. Render these choices in the ordinary assignment control with a
|
contributor. Render these choices in the ordinary assignment control with a
|
||||||
restrained orange border/background and the explicit text `PENDING` plus the
|
restrained orange border/background and the explicit text `PENDING` plus the
|
||||||
|
|||||||
@@ -12,14 +12,24 @@ export const load: LayoutServerLoad = async ({ cookies }) => {
|
|||||||
const canSeeData = permissions.includes('*') || ['data.view','data.manage','data.propose','data.approve'].some((permission) => permissions.includes(permission));
|
const canSeeData = permissions.includes('*') || ['data.view','data.manage','data.propose','data.approve'].some((permission) => permissions.includes(permission));
|
||||||
const canManageData = permissions.includes('*') || permissions.includes('data.manage');
|
const canManageData = permissions.includes('*') || permissions.includes('data.manage');
|
||||||
const canProposeData = permissions.includes('*') || permissions.includes('data.propose');
|
const canProposeData = permissions.includes('*') || permissions.includes('data.propose');
|
||||||
|
const canApproveData = permissions.includes('*') || permissions.includes('data.approve');
|
||||||
|
const canReviewAvatars = permissions.includes('*') || permissions.includes('avatars.review');
|
||||||
const contributorMode = !canManageData && canProposeData ? await getContributorMode(user.id) : null;
|
const contributorMode = !canManageData && canProposeData ? await getContributorMode(user.id) : null;
|
||||||
|
const database = canSeeData || canReviewAvatars ? await getDatabase() : null;
|
||||||
const pendingRows = canSeeData
|
const pendingRows = canSeeData
|
||||||
? await (await (await getDatabase()).prepare("SELECT module,COUNT(*) AS count FROM data_change_proposals WHERE status='pending' GROUP BY module")).all()
|
? await (await database!.prepare("SELECT module,COUNT(*) AS count FROM data_change_proposals WHERE status='pending' GROUP BY module")).all()
|
||||||
: [];
|
: [];
|
||||||
|
const pendingProposalTotal = pendingRows.reduce((total, row) => total + Number(row.count), 0);
|
||||||
|
const pendingAvatarReviews = canReviewAvatars
|
||||||
|
? Number((await (await database!.prepare("SELECT COUNT(*) AS count FROM avatar_reviews WHERE status='pending'")).get())?.count ?? 0)
|
||||||
|
: 0;
|
||||||
return {
|
return {
|
||||||
permissions,
|
permissions,
|
||||||
contributorMode,
|
contributorMode,
|
||||||
pendingProposals: Object.fromEntries(pendingRows.map((row) => [String(row.module), Number(row.count)])),
|
pendingProposals: Object.fromEntries(pendingRows.map((row) => [String(row.module), Number(row.count)])),
|
||||||
pendingProposalTotal: pendingRows.reduce((total, row) => total + Number(row.count), 0)
|
oversightReviewCounts: {
|
||||||
|
data: canApproveData ? pendingProposalTotal : 0,
|
||||||
|
avatars: pendingAvatarReviews
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,9 +23,9 @@
|
|||||||
{ href: '/isac/roles', label: 'Roles', code: 'ROLE', permission: 'users.roles' }
|
{ href: '/isac/roles', label: 'Roles', code: 'ROLE', permission: 'users.roles' }
|
||||||
];
|
];
|
||||||
const visibleUserSections = $derived(userSections.filter((section) => can(section.permission)));
|
const visibleUserSections = $derived(userSections.filter((section) => can(section.permission)));
|
||||||
const oversightSections = [
|
const oversightSections: Array<{ href: string; label: string; code: string; permission: string; reviewKey?: 'data' | 'avatars' }> = [
|
||||||
{ href: '/isac/data-moderation', label: 'Data Oversight', code: 'OVST', permission: 'data.approve' },
|
{ href: '/isac/data-moderation', label: 'Data Oversight', code: 'OVST', permission: 'data.approve', reviewKey: 'data' },
|
||||||
{ href: '/isac/avatar-oversight', label: 'Avatar Oversight', code: 'AVTR', permission: 'avatars.review' },
|
{ href: '/isac/avatar-oversight', label: 'Avatar Oversight', code: 'AVTR', permission: 'avatars.review', reviewKey: 'avatars' },
|
||||||
{ href: '/isac/audit', label: 'Audit history', code: 'AUDT', permission: 'audit.view' },
|
{ href: '/isac/audit', label: 'Audit history', code: 'AUDT', permission: 'audit.view' },
|
||||||
{ href: '/isac/metrics', label: 'System metrics', code: 'MTRX', permission: 'metrics.view' }
|
{ href: '/isac/metrics', label: 'System metrics', code: 'MTRX', permission: 'metrics.view' }
|
||||||
];
|
];
|
||||||
@@ -55,7 +55,10 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if visibleOversightSections.length}
|
{#if visibleOversightSections.length}
|
||||||
<div class="sidebar-label section-label"><span>SYSTEM OVERSIGHT</span><b>{String(visibleOversightSections.length).padStart(2, '0')} MODULE{visibleOversightSections.length === 1 ? '' : 'S'}</b></div>
|
<div class="sidebar-label section-label"><span>SYSTEM OVERSIGHT</span><b>{String(visibleOversightSections.length).padStart(2, '0')} MODULE{visibleOversightSections.length === 1 ? '' : 'S'}</b></div>
|
||||||
<nav aria-label="System oversight">{#each visibleOversightSections as section}<a class:active={page.url.pathname.startsWith(section.href)} href={section.href}><small>{section.code}</small><span>{section.label}</span><b>›</b></a>{/each}</nav>
|
<nav aria-label="System oversight">{#each visibleOversightSections as section}
|
||||||
|
{@const reviewCount = section.reviewKey ? Number(data.oversightReviewCounts[section.reviewKey] ?? 0) : 0}
|
||||||
|
<a class:active={page.url.pathname.startsWith(section.href)} class:has-pending={reviewCount > 0} href={section.href}><small>{section.code}</small><span>{section.label}</span>{#if reviewCount}<em aria-label={`${reviewCount} item${reviewCount === 1 ? '' : 's'} awaiting review`} title="Awaiting review">{reviewCount}</em>{/if}<b>›</b></a>
|
||||||
|
{/each}</nav>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="sidebar-foot"><span>ISAC NODE</span><strong>NYC-01</strong><small>Access policy synchronized</small></div>
|
<div class="sidebar-foot"><span>ISAC NODE</span><strong>NYC-01</strong><small>Access policy synchronized</small></div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
Reference in New Issue
Block a user