From 46ca27cf2822f6f2f928354126aaa7696392c8be Mon Sep 17 00:00:00 2001 From: xarmian Date: Wed, 20 May 2026 18:55:49 -0400 Subject: [PATCH] feat(web): admin modal Workspaces tab (TASK-1552) (#607) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New UserWorkspacesTab.svelte consumes GET /api/v1/admin/users/{id}/detail (T1545) and renders the per-workspace breakdown: name + slug (link to the workspace), role badge, collections count (excludes system — playbooks + conventions), items open / total, members count, storage in human-readable units, last-activity relative time. Lazy load — fetches only on first activation of the Workspaces tab, not on modal open. Refetches if the modal swaps to a different user without closing. Defensive against in-flight user swaps (commits the result only if user.id hasn't changed underneath). Empty state for users with no workspaces. Retry on error. Server caps at 50; UI caps visible at 20 with "Show all N" affordance for users who own a long tail of workspaces. Wiring: replaces the Workspaces placeholder in UserModal.svelte; passes `active={activeTab === 'workspaces'}` so the component can gate its fetch. Part of PLAN-1542. --- web/src/lib/components/admin/UserModal.svelte | 3 +- .../components/admin/UserWorkspacesTab.svelte | 251 ++++++++++++++++++ 2 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 web/src/lib/components/admin/UserWorkspacesTab.svelte diff --git a/web/src/lib/components/admin/UserModal.svelte b/web/src/lib/components/admin/UserModal.svelte index c80b37b1..77531da2 100644 --- a/web/src/lib/components/admin/UserModal.svelte +++ b/web/src/lib/components/admin/UserModal.svelte @@ -19,6 +19,7 @@ import { tick } from 'svelte'; import type { AdminUser } from '$lib/stores/admin.svelte'; import UserSettingsForm from './UserSettingsForm.svelte'; + import UserWorkspacesTab from './UserWorkspacesTab.svelte'; export type UserModalTab = 'overview' | 'workspaces' | 'activity' | 'settings'; @@ -223,7 +224,7 @@ {#if t.key === 'overview'}

Overview tab content arrives in TASK-1553.

{:else if t.key === 'workspaces'} -

Workspaces tab content arrives in TASK-1552.

+ {:else if t.key === 'activity'}

Activity tab content arrives in TASK-1554.

{:else if t.key === 'settings'} diff --git a/web/src/lib/components/admin/UserWorkspacesTab.svelte b/web/src/lib/components/admin/UserWorkspacesTab.svelte new file mode 100644 index 00000000..143376ab --- /dev/null +++ b/web/src/lib/components/admin/UserWorkspacesTab.svelte @@ -0,0 +1,251 @@ + + + +{#if loading} +
Loading workspaces…
+{:else if loadError} +
{loadError} + +
+{:else if workspaces.length === 0} +
This user has no workspaces.
+{:else} +
+ + + + + + + + + + + + + + {#each visible as ws (ws.workspace_id)} + + + + + + + + + + {/each} + +
WorkspaceRoleCollectionsItems (open / total)MembersStorageLast activity
+ + {ws.workspace_name} + +
{ws.workspace_slug}
+
{ws.role}{ws.collections_count}{ws.items_open} / {ws.items_total}{ws.members_count}{formatStorageBytes(ws.storage_bytes)}{relativeTime(ws.last_activity_at)}
+
+ {#if overflow > 0 && !showAll} + + {/if} +{/if} + +