83 lines
3.2 KiB
TypeScript
83 lines
3.2 KiB
TypeScript
import { useEffect, useCallback, useMemo } 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]);
|
|
|
|
// NOTE: derive from the stable `context` reference via useMemo —
|
|
// `s.visibleModuleKeys()` would create a NEW Set on every getSnapshot
|
|
// call, destabilizing useSyncExternalStore (same root cause as the
|
|
// Sidebar moduleMenuOrder infinite re-render loop).
|
|
const visibleModuleKeys = useMemo(() => new Set(
|
|
(context?.modules ?? []).filter(m => m.is_visible !== false).map(m => m.module_key),
|
|
), [context]);
|
|
|
|
return {
|
|
activeWorkspaceId,
|
|
activeWorkspace: context,
|
|
myWorkspaces: myWorkspacesFromStore,
|
|
switchWorkspace,
|
|
isModuleVisible,
|
|
visibleModuleKeys,
|
|
hasWorkspaces: myWorkspacesFromStore.length > 0,
|
|
};
|
|
}
|