Files
leocrm/frontend/src/hooks/useWorkspace.ts
T
Agent Zero 310a9f0542 Phase 6: Workspaces — Widget CRUD, Manager-Check, Cross-Tenant, Zustand Store, Settings Route
Backend:
- Widget CRUD: get_widgets, create_widget, update_widget, delete_widget
- Manager role check: is_workspace_manager
- Cross-tenant validation: verify_user_same_tenant (UserTenant)
- Default workspace seeding: seed_default_workspace with 12 standard modules
- Set user default workspace: set_user_default_workspace
- Fix create_workspace default uniqueness (unset others before insert)
- Widget CRUD routes: GET/POST/PUT/DELETE /{workspace_id}/widgets
- Set-default route: POST /{workspace_id}/set-default
- Cross-tenant validation in assign_user route

Frontend:
- workspaceStore (Zustand): central state with sessionStorage persistence
- API client interceptor: X-Workspace-ID header on all requests
- useWorkspace hook refactored to use workspaceStore
- Widget API hooks: useWorkspaceWidgets, useCreateWorkspaceWidget, etc.
- useSetDefaultWorkspace hook
- Settings route: /settings/workspaces with WorkspaceManagerPage
- Settings nav item for Workspaces

Tests:
- 25 backend tests (CRUD, modules, widgets, users, manager, seeding, context, isolation)
- 12 frontend tests (workspaceStore state, visibility, persistence, reset)
- 48/48 backend tests passing
- 12/12 frontend tests passing
2026-08-03 03:39:27 +02:00

77 lines
2.9 KiB
TypeScript

import { useEffect, useCallback } from 'react';
import { useMyWorkspaces, useWorkspaceContext } from '@/api/hooks/workspaces';
import { useWorkspaceStore, type WorkspaceInfo, type WorkspaceContextState } from '@/store/workspaceStore';
import { useAuthStore } from '@/store/authStore';
/**
* Manages the active workspace for the current browser tab.
*
* Uses the central workspaceStore (Zustand) for state management.
* sessionStorage provides per-tab persistence.
* X-Workspace-ID header is sent automatically by the API client interceptor.
*
* Workspaces are UI/navigation context only — they never affect permissions.
*/
export function useWorkspace() {
const activeWorkspaceId = useWorkspaceStore(s => s.activeWorkspaceId);
const context = useWorkspaceStore(s => s.context);
const myWorkspacesFromStore = useWorkspaceStore(s => s.myWorkspaces);
const setActiveWorkspace = useWorkspaceStore(s => s.setActiveWorkspace);
const setContext = useWorkspaceStore(s => s.setContext);
const setMyWorkspaces = useWorkspaceStore(s => s.setMyWorkspaces);
const isModuleVisibleFromStore = useWorkspaceStore(s => s.isModuleVisible);
const user = useAuthStore.getState().user;
const isSystemAdmin = user?.is_system_admin;
// Fetch my workspaces and sync to store
const { data: myWorkspacesData } = useMyWorkspaces();
const { data: contextData } = useWorkspaceContext(activeWorkspaceId);
// Sync fetched workspaces to store
useEffect(() => {
if (myWorkspacesData?.items) {
setMyWorkspaces(myWorkspacesData.items as WorkspaceInfo[]);
}
}, [myWorkspacesData, setMyWorkspaces]);
// Sync fetched context to store
useEffect(() => {
if (contextData !== undefined) {
setContext(contextData as WorkspaceContextState | null);
}
}, [contextData, setContext]);
// Auto-select default workspace if none selected
useEffect(() => {
if (!activeWorkspaceId && myWorkspacesData?.items?.length) {
const defaultWs = myWorkspacesData.items.find(w => w.is_user_default) || myWorkspacesData.items[0];
if (defaultWs) {
setActiveWorkspace(defaultWs.id);
}
}
}, [activeWorkspaceId, myWorkspacesData, setActiveWorkspace]);
// Switch workspace (per-tab)
const switchWorkspace = useCallback((workspaceId: string | null) => {
setActiveWorkspace(workspaceId);
}, [setActiveWorkspace]);
// Check if a module is visible in the current workspace
const isModuleVisible = useCallback((moduleKey: string): boolean => {
return isModuleVisibleFromStore(moduleKey, isSystemAdmin);
}, [isModuleVisibleFromStore, isSystemAdmin]);
const visibleModuleKeys = useWorkspaceStore(s => s.visibleModuleKeys());
return {
activeWorkspaceId,
activeWorkspace: context,
myWorkspaces: myWorkspacesFromStore,
switchWorkspace,
isModuleVisible,
visibleModuleKeys,
hasWorkspaces: myWorkspacesFromStore.length > 0,
};
}