feat(stats): voice-time and message leaderboards per space
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
Voice stays get their own table rather than joining the audit log: that table records points in time, a call is an interval, and pairing join/leave point events would leave every query guessing at joins whose leave never arrived. Sessions are opened and closed inside joinRoom/leaveCurrentRoom rather than at the seven call sites that reach them, so no path can be missed, and destroyRoom closes them too — it bypasses leaveCurrentRoom and would otherwise leak open rows. A restart leaves sessions open with an unknowable end time. They are closed at startedAt, discarding that time rather than inventing it: crediting the gap would hand someone hours they never spent, and the numbers are the point. Mirrors the existing users.status sweep on boot. Only closed sessions count, so a figure does not move on every refresh. Bars scale to the leader, not the total — with five people every share of a total looks identical. Statistics are readable by any member, since they are the group's own numbers; the audit log, which names who did what, stays on MANAGE_SPACE.
This commit is contained in:
@@ -8,6 +8,7 @@ import { api } from '../../api/client';
|
||||
import { hasPermissionBit, PermissionBits } from '../../utils/permissions';
|
||||
import { OverviewPanel } from './spaceSettingsPanels/OverviewPanel';
|
||||
import { AuditLogPanel } from './spaceSettingsPanels/AuditLogPanel';
|
||||
import { StatsPanel } from './spaceSettingsPanels/StatsPanel';
|
||||
import { useT } from '../../i18n';
|
||||
import { MembersPanel } from './spaceSettingsPanels/MembersPanel';
|
||||
import { RolesPanel } from './spaceSettingsPanels/RolesPanel';
|
||||
@@ -269,7 +270,7 @@ export function SpaceSettingsModal() {
|
||||
const spacePermissions = useSpaceStore((s) => s.spacePermissions);
|
||||
|
||||
const t = useT();
|
||||
const [tab, setTab] = useState<'overview' | 'discovery' | 'members' | 'roles' | 'bans' | 'audit'>('overview');
|
||||
const [tab, setTab] = useState<'overview' | 'discovery' | 'members' | 'roles' | 'bans' | 'audit' | 'stats'>('overview');
|
||||
const [mobileView, setMobileView] = useState<'tabs' | 'content'>('tabs');
|
||||
|
||||
const isOpen = activeModal === 'spaceSettings';
|
||||
@@ -337,6 +338,7 @@ export function SpaceSettingsModal() {
|
||||
{canManageSpace && (
|
||||
<button onClick={() => handleTabClick('audit')} className={tabClass('audit')}>{t('audit.title')}</button>
|
||||
)}
|
||||
<button onClick={() => handleTabClick('stats')} className={tabClass('stats')}>{t('stats.title')}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -375,6 +377,7 @@ export function SpaceSettingsModal() {
|
||||
{canManageSpace && (
|
||||
<button onClick={() => handleTabClick('audit')} className={tabClass('audit')}>{t('audit.title')}</button>
|
||||
)}
|
||||
<button onClick={() => handleTabClick('stats')} className={tabClass('stats')}>{t('stats.title')}</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -402,6 +405,7 @@ export function SpaceSettingsModal() {
|
||||
{tab === 'roles' && canManageRoles && <RolesPanel spaceId={currentSpaceId} />}
|
||||
{tab === 'bans' && canBanMembers && <BansPanel spaceId={currentSpaceId} />}
|
||||
{tab === 'audit' && canManageSpace && <AuditLogPanel spaceId={currentSpaceId} />}
|
||||
{tab === 'stats' && <StatsPanel spaceId={currentSpaceId} />}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user