chore: navigation preference enhancements (#8468)
This commit is contained in:
parent
8d479ac24c
commit
866338289e
13 changed files with 165 additions and 44 deletions
|
|
@ -30,7 +30,7 @@ export const ProjectWorkItemDetailsHeader = observer(function ProjectWorkItemDet
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{projectPreferences.navigationMode === "horizontal" && (
|
{projectPreferences.navigationMode === "TABBED" && (
|
||||||
<div className="z-20">
|
<div className="z-20">
|
||||||
<Row className="h-header flex gap-2 w-full items-center border-b border-subtle bg-surface-1">
|
<Row className="h-header flex gap-2 w-full items-center border-b border-subtle bg-surface-1">
|
||||||
<div className="flex items-center gap-2 divide-x divide-subtle h-full w-full">
|
<div className="flex items-center gap-2 divide-x divide-subtle h-full w-full">
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ function ProjectLayout({ params }: Route.ComponentProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{projectPreferences.navigationMode === "horizontal" && (
|
{projectPreferences.navigationMode === "TABBED" && (
|
||||||
<div className="z-20">
|
<div className="z-20">
|
||||||
<Row className="h-header flex gap-2 w-full items-center border-b border-subtle bg-surface-1">
|
<Row className="h-header flex gap-2 w-full items-center border-b border-subtle bg-surface-1">
|
||||||
<div className="flex items-center gap-2 divide-x divide-subtle h-full w-full">
|
<div className="flex items-center gap-2 divide-x divide-subtle h-full w-full">
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,6 @@ export function CommonProjectBreadcrumbs(props: TCommonProjectBreadcrumbProps) {
|
||||||
// preferences
|
// preferences
|
||||||
const { preferences: projectPreferences } = useProjectNavigationPreferences();
|
const { preferences: projectPreferences } = useProjectNavigationPreferences();
|
||||||
|
|
||||||
if (projectPreferences.navigationMode === "horizontal") return null;
|
if (projectPreferences.navigationMode === "TABBED") return null;
|
||||||
return <ProjectBreadcrumb workspaceSlug={workspaceSlug} projectId={projectId} />;
|
return <ProjectBreadcrumb workspaceSlug={workspaceSlug} projectId={projectId} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export const ExtendedAppHeader = observer(function ExtendedAppHeader(props: { he
|
||||||
// store hooks
|
// store hooks
|
||||||
const { sidebarCollapsed } = useAppTheme();
|
const { sidebarCollapsed } = useAppTheme();
|
||||||
// derived values
|
// derived values
|
||||||
const shouldShowSidebarToggleButton = projectPreferences.navigationMode === "accordion" || (!projectId && !workItem);
|
const shouldShowSidebarToggleButton = projectPreferences.navigationMode === "ACCORDION" || (!projectId && !workItem);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -265,9 +265,9 @@ export const CustomizeNavigationDialog = observer(function CustomizeNavigationDi
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="navigation-mode"
|
name="navigation-mode"
|
||||||
value="accordion"
|
value="ACCORDION"
|
||||||
checked={projectPreferences.navigationMode === "accordion"}
|
checked={projectPreferences.navigationMode === "ACCORDION"}
|
||||||
onChange={() => updateNavigationMode("accordion")}
|
onChange={() => updateNavigationMode("ACCORDION")}
|
||||||
className="size-4 text-accent-primary focus:ring-accent-strong mt-1"
|
className="size-4 text-accent-primary focus:ring-accent-strong mt-1"
|
||||||
/>
|
/>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
|
|
@ -282,9 +282,9 @@ export const CustomizeNavigationDialog = observer(function CustomizeNavigationDi
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="navigation-mode"
|
name="navigation-mode"
|
||||||
value="horizontal"
|
value="TABBED"
|
||||||
checked={projectPreferences.navigationMode === "horizontal"}
|
checked={projectPreferences.navigationMode === "TABBED"}
|
||||||
onChange={() => updateNavigationMode("horizontal")}
|
onChange={() => updateNavigationMode("TABBED")}
|
||||||
className="size-4 text-accent-primary focus:ring-accent-strong mt-1"
|
className="size-4 text-accent-primary focus:ring-accent-strong mt-1"
|
||||||
/>
|
/>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ export const SidebarProjectsListItem = observer(function SidebarProjectsListItem
|
||||||
if (!project) return null;
|
if (!project) return null;
|
||||||
|
|
||||||
const handleItemClick = () => {
|
const handleItemClick = () => {
|
||||||
if (projectPreferences.navigationMode === "accordion") {
|
if (projectPreferences.navigationMode === "ACCORDION") {
|
||||||
setIsProjectListOpen(!isProjectListOpen);
|
setIsProjectListOpen(!isProjectListOpen);
|
||||||
} else {
|
} else {
|
||||||
router.push(defaultTabUrl);
|
router.push(defaultTabUrl);
|
||||||
|
|
@ -266,9 +266,9 @@ export const SidebarProjectsListItem = observer(function SidebarProjectsListItem
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAccordionMode = projectPreferences.navigationMode === "accordion";
|
const isAccordionMode = projectPreferences.navigationMode === "ACCORDION";
|
||||||
|
|
||||||
const shouldHighlightProject = URLProjectId === project?.id && projectPreferences.navigationMode !== "accordion";
|
const shouldHighlightProject = URLProjectId === project?.id && projectPreferences.navigationMode !== "ACCORDION";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,9 @@ export const WORKSPACE_STATES = (workspaceSlug: string) => `WORKSPACE_STATES_${w
|
||||||
export const WORKSPACE_SIDEBAR_PREFERENCES = (workspaceSlug: string) =>
|
export const WORKSPACE_SIDEBAR_PREFERENCES = (workspaceSlug: string) =>
|
||||||
`WORKSPACE_SIDEBAR_PREFERENCES_${workspaceSlug.toUpperCase()}`;
|
`WORKSPACE_SIDEBAR_PREFERENCES_${workspaceSlug.toUpperCase()}`;
|
||||||
|
|
||||||
|
export const WORKSPACE_PROJECT_NAVIGATION_PREFERENCES = (workspaceSlug: string) =>
|
||||||
|
`WORKSPACE_PROJECT_NAVIGATION_PREFERENCES_${workspaceSlug.toUpperCase()}`;
|
||||||
|
|
||||||
export const PROJECT_GITHUB_REPOSITORY = (projectId: string) => `PROJECT_GITHUB_REPOSITORY_${projectId.toUpperCase()}`;
|
export const PROJECT_GITHUB_REPOSITORY = (projectId: string) => `PROJECT_GITHUB_REPOSITORY_${projectId.toUpperCase()}`;
|
||||||
|
|
||||||
// cycles
|
// cycles
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import {
|
||||||
import { useWorkspace } from "./store/use-workspace";
|
import { useWorkspace } from "./store/use-workspace";
|
||||||
import useLocalStorage from "./use-local-storage";
|
import useLocalStorage from "./use-local-storage";
|
||||||
|
|
||||||
const PROJECT_PREFERENCES_KEY = "navigation_preferences_projects";
|
|
||||||
const APP_RAIL_PREFERENCES_KEY = "app_rail_preferences";
|
const APP_RAIL_PREFERENCES_KEY = "app_rail_preferences";
|
||||||
|
|
||||||
export const usePersonalNavigationPreferences = () => {
|
export const usePersonalNavigationPreferences = () => {
|
||||||
|
|
@ -105,49 +104,73 @@ export const usePersonalNavigationPreferences = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useProjectNavigationPreferences = () => {
|
export const useProjectNavigationPreferences = () => {
|
||||||
const { storedValue, setValue } = useLocalStorage<TProjectNavigationPreferences>(
|
const { workspaceSlug } = useParams();
|
||||||
PROJECT_PREFERENCES_KEY,
|
const { getProjectNavigationPreferences, updateProjectNavigationPreferences } = useWorkspace();
|
||||||
DEFAULT_PROJECT_PREFERENCES
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// Get preferences from the store
|
||||||
|
const storePreferences = getProjectNavigationPreferences(workspaceSlug?.toString() || "");
|
||||||
|
|
||||||
|
// Computed preferences with fallback logic: API → defaults
|
||||||
|
const preferences: TProjectNavigationPreferences = useMemo(() => {
|
||||||
|
// 1. Try API data first
|
||||||
|
if (
|
||||||
|
storePreferences &&
|
||||||
|
(storePreferences.navigation_control_preference || storePreferences.navigation_project_limit !== undefined)
|
||||||
|
) {
|
||||||
|
const limit = storePreferences.navigation_project_limit ?? DEFAULT_PROJECT_PREFERENCES.limitedProjectsCount;
|
||||||
|
|
||||||
|
return {
|
||||||
|
navigationMode: storePreferences.navigation_control_preference || DEFAULT_PROJECT_PREFERENCES.navigationMode,
|
||||||
|
limitedProjectsCount: limit > 0 ? limit : DEFAULT_PROJECT_PREFERENCES.limitedProjectsCount,
|
||||||
|
showLimitedProjects: limit > 0, // Derived: 0 = false, >0 = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fall back to defaults
|
||||||
|
return DEFAULT_PROJECT_PREFERENCES;
|
||||||
|
}, [storePreferences]);
|
||||||
|
|
||||||
|
// Update navigation mode
|
||||||
const updateNavigationMode = useCallback(
|
const updateNavigationMode = useCallback(
|
||||||
(mode: TProjectNavigationMode) => {
|
async (mode: TProjectNavigationMode) => {
|
||||||
const currentPreferences = storedValue || DEFAULT_PROJECT_PREFERENCES;
|
if (!workspaceSlug) return;
|
||||||
setValue({
|
|
||||||
navigationMode: mode,
|
await updateProjectNavigationPreferences(workspaceSlug.toString(), {
|
||||||
showLimitedProjects: currentPreferences.showLimitedProjects,
|
navigation_control_preference: mode,
|
||||||
limitedProjectsCount: currentPreferences.limitedProjectsCount,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[storedValue, setValue]
|
[workspaceSlug, updateProjectNavigationPreferences]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Update show limited projects
|
||||||
const updateShowLimitedProjects = useCallback(
|
const updateShowLimitedProjects = useCallback(
|
||||||
(show: boolean) => {
|
async (show: boolean) => {
|
||||||
const currentPreferences = storedValue || DEFAULT_PROJECT_PREFERENCES;
|
if (!workspaceSlug) return;
|
||||||
setValue({
|
|
||||||
navigationMode: currentPreferences.navigationMode,
|
// When toggling off, set to 0; when toggling on, use current count or default
|
||||||
showLimitedProjects: show,
|
const newLimit = show ? preferences.limitedProjectsCount || DEFAULT_PROJECT_PREFERENCES.limitedProjectsCount : 0;
|
||||||
limitedProjectsCount: currentPreferences.limitedProjectsCount,
|
|
||||||
|
await updateProjectNavigationPreferences(workspaceSlug.toString(), {
|
||||||
|
navigation_project_limit: newLimit,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[storedValue, setValue]
|
[workspaceSlug, updateProjectNavigationPreferences, preferences.limitedProjectsCount]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Update limited projects count
|
||||||
const updateLimitedProjectsCount = useCallback(
|
const updateLimitedProjectsCount = useCallback(
|
||||||
(count: number) => {
|
async (count: number) => {
|
||||||
const currentPreferences = storedValue || DEFAULT_PROJECT_PREFERENCES;
|
if (!workspaceSlug) return;
|
||||||
setValue({
|
|
||||||
navigationMode: currentPreferences.navigationMode,
|
await updateProjectNavigationPreferences(workspaceSlug.toString(), {
|
||||||
showLimitedProjects: currentPreferences.showLimitedProjects,
|
navigation_project_limit: count,
|
||||||
limitedProjectsCount: count,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[storedValue, setValue]
|
[workspaceSlug, updateProjectNavigationPreferences]
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
preferences: storedValue || DEFAULT_PROJECT_PREFERENCES,
|
preferences,
|
||||||
updateNavigationMode,
|
updateNavigationMode,
|
||||||
updateShowLimitedProjects,
|
updateShowLimitedProjects,
|
||||||
updateLimitedProjectsCount,
|
updateLimitedProjectsCount,
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import {
|
||||||
WORKSPACE_FAVORITE,
|
WORKSPACE_FAVORITE,
|
||||||
WORKSPACE_STATES,
|
WORKSPACE_STATES,
|
||||||
WORKSPACE_SIDEBAR_PREFERENCES,
|
WORKSPACE_SIDEBAR_PREFERENCES,
|
||||||
|
WORKSPACE_PROJECT_NAVIGATION_PREFERENCES,
|
||||||
} from "@/constants/fetch-keys";
|
} from "@/constants/fetch-keys";
|
||||||
// hooks
|
// hooks
|
||||||
import { useFavorite } from "@/hooks/store/use-favorite";
|
import { useFavorite } from "@/hooks/store/use-favorite";
|
||||||
|
|
@ -50,7 +51,7 @@ export const WorkspaceAuthWrapper = observer(function WorkspaceAuthWrapper(props
|
||||||
const {
|
const {
|
||||||
workspace: { fetchWorkspaceMembers },
|
workspace: { fetchWorkspaceMembers },
|
||||||
} = useMember();
|
} = useMember();
|
||||||
const { workspaces, fetchSidebarNavigationPreferences } = useWorkspace();
|
const { workspaces, fetchSidebarNavigationPreferences, fetchProjectNavigationPreferences } = useWorkspace();
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
const { loader, workspaceInfoBySlug, fetchUserWorkspaceInfo, fetchUserProjectPermissions, allowPermissions } =
|
const { loader, workspaceInfoBySlug, fetchUserWorkspaceInfo, fetchUserProjectPermissions, allowPermissions } =
|
||||||
useUserPermissions();
|
useUserPermissions();
|
||||||
|
|
@ -113,6 +114,13 @@ export const WorkspaceAuthWrapper = observer(function WorkspaceAuthWrapper(props
|
||||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// fetch workspace project navigation preferences
|
||||||
|
useSWR(
|
||||||
|
workspaceSlug ? WORKSPACE_PROJECT_NAVIGATION_PREFERENCES(workspaceSlug.toString()) : null,
|
||||||
|
workspaceSlug ? () => fetchProjectNavigationPreferences(workspaceSlug.toString()) : null,
|
||||||
|
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||||
|
);
|
||||||
|
|
||||||
const handleSignOut = async () => {
|
const handleSignOut = async () => {
|
||||||
await signOut().catch(() =>
|
await signOut().catch(() =>
|
||||||
setToast({
|
setToast({
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import type {
|
||||||
TActivityEntityData,
|
TActivityEntityData,
|
||||||
IWorkspaceSidebarNavigationItem,
|
IWorkspaceSidebarNavigationItem,
|
||||||
IWorkspaceSidebarNavigation,
|
IWorkspaceSidebarNavigation,
|
||||||
|
IWorkspaceUserPropertiesResponse,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
// services
|
// services
|
||||||
import { APIService } from "@/services/api.service";
|
import { APIService } from "@/services/api.service";
|
||||||
|
|
@ -397,4 +398,23 @@ export class WorkspaceService extends APIService {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchWorkspaceFilters(workspaceSlug: string): Promise<IWorkspaceUserPropertiesResponse> {
|
||||||
|
return this.get(`/api/workspaces/${workspaceSlug}/user-properties/`)
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async patchWorkspaceFilters(
|
||||||
|
workspaceSlug: string,
|
||||||
|
data: Partial<IWorkspaceUserPropertiesResponse>
|
||||||
|
): Promise<IWorkspaceUserPropertiesResponse> {
|
||||||
|
return this.patch(`/api/workspaces/${workspaceSlug}/user-properties/`, data)
|
||||||
|
.then((response) => response?.data)
|
||||||
|
.catch((error) => {
|
||||||
|
throw error?.response?.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,12 @@ import { clone, set } from "lodash-es";
|
||||||
import { action, computed, observable, makeObservable, runInAction } from "mobx";
|
import { action, computed, observable, makeObservable, runInAction } from "mobx";
|
||||||
// types
|
// types
|
||||||
import { computedFn } from "mobx-utils";
|
import { computedFn } from "mobx-utils";
|
||||||
import type { IWorkspaceSidebarNavigationItem, IWorkspace, IWorkspaceSidebarNavigation } from "@plane/types";
|
import type {
|
||||||
|
IWorkspaceSidebarNavigationItem,
|
||||||
|
IWorkspace,
|
||||||
|
IWorkspaceSidebarNavigation,
|
||||||
|
IWorkspaceUserPropertiesResponse,
|
||||||
|
} from "@plane/types";
|
||||||
// services
|
// services
|
||||||
import { WorkspaceService } from "@/plane-web/services";
|
import { WorkspaceService } from "@/plane-web/services";
|
||||||
// store
|
// store
|
||||||
|
|
@ -23,6 +28,7 @@ export interface IWorkspaceRootStore {
|
||||||
currentWorkspace: IWorkspace | null;
|
currentWorkspace: IWorkspace | null;
|
||||||
workspacesCreatedByCurrentUser: IWorkspace[] | null;
|
workspacesCreatedByCurrentUser: IWorkspace[] | null;
|
||||||
navigationPreferencesMap: Record<string, IWorkspaceSidebarNavigation>;
|
navigationPreferencesMap: Record<string, IWorkspaceSidebarNavigation>;
|
||||||
|
projectNavigationPreferencesMap: Record<string, IWorkspaceUserPropertiesResponse>;
|
||||||
getWorkspaceRedirectionUrl: () => string;
|
getWorkspaceRedirectionUrl: () => string;
|
||||||
// computed actions
|
// computed actions
|
||||||
getWorkspaceBySlug: (workspaceSlug: string) => IWorkspace | null;
|
getWorkspaceBySlug: (workspaceSlug: string) => IWorkspace | null;
|
||||||
|
|
@ -45,6 +51,12 @@ export interface IWorkspaceRootStore {
|
||||||
data: Array<{ key: string; is_pinned: boolean; sort_order: number }>
|
data: Array<{ key: string; is_pinned: boolean; sort_order: number }>
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
getNavigationPreferences: (workspaceSlug: string) => IWorkspaceSidebarNavigation | undefined;
|
getNavigationPreferences: (workspaceSlug: string) => IWorkspaceSidebarNavigation | undefined;
|
||||||
|
getProjectNavigationPreferences: (workspaceSlug: string) => IWorkspaceUserPropertiesResponse | undefined;
|
||||||
|
fetchProjectNavigationPreferences: (workspaceSlug: string) => Promise<void>;
|
||||||
|
updateProjectNavigationPreferences: (
|
||||||
|
workspaceSlug: string,
|
||||||
|
data: Partial<IWorkspaceUserPropertiesResponse>
|
||||||
|
) => Promise<void>;
|
||||||
mutateWorkspaceMembersActivity: (workspaceSlug: string) => Promise<void>;
|
mutateWorkspaceMembersActivity: (workspaceSlug: string) => Promise<void>;
|
||||||
// sub-stores
|
// sub-stores
|
||||||
webhook: IWebhookStore;
|
webhook: IWebhookStore;
|
||||||
|
|
@ -57,6 +69,7 @@ export abstract class BaseWorkspaceRootStore implements IWorkspaceRootStore {
|
||||||
// observables
|
// observables
|
||||||
workspaces: Record<string, IWorkspace> = {};
|
workspaces: Record<string, IWorkspace> = {};
|
||||||
navigationPreferencesMap: Record<string, IWorkspaceSidebarNavigation> = {};
|
navigationPreferencesMap: Record<string, IWorkspaceSidebarNavigation> = {};
|
||||||
|
projectNavigationPreferencesMap: Record<string, IWorkspaceUserPropertiesResponse> = {};
|
||||||
// services
|
// services
|
||||||
workspaceService;
|
workspaceService;
|
||||||
// root store
|
// root store
|
||||||
|
|
@ -73,6 +86,7 @@ export abstract class BaseWorkspaceRootStore implements IWorkspaceRootStore {
|
||||||
// observables
|
// observables
|
||||||
workspaces: observable,
|
workspaces: observable,
|
||||||
navigationPreferencesMap: observable,
|
navigationPreferencesMap: observable,
|
||||||
|
projectNavigationPreferencesMap: observable,
|
||||||
// computed
|
// computed
|
||||||
currentWorkspace: computed,
|
currentWorkspace: computed,
|
||||||
workspacesCreatedByCurrentUser: computed,
|
workspacesCreatedByCurrentUser: computed,
|
||||||
|
|
@ -88,6 +102,8 @@ export abstract class BaseWorkspaceRootStore implements IWorkspaceRootStore {
|
||||||
fetchSidebarNavigationPreferences: action,
|
fetchSidebarNavigationPreferences: action,
|
||||||
updateSidebarPreference: action,
|
updateSidebarPreference: action,
|
||||||
updateBulkSidebarPreferences: action,
|
updateBulkSidebarPreferences: action,
|
||||||
|
fetchProjectNavigationPreferences: action,
|
||||||
|
updateProjectNavigationPreferences: action,
|
||||||
});
|
});
|
||||||
|
|
||||||
// services
|
// services
|
||||||
|
|
@ -315,6 +331,51 @@ export abstract class BaseWorkspaceRootStore implements IWorkspaceRootStore {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getProjectNavigationPreferences = computedFn(
|
||||||
|
(workspaceSlug: string): IWorkspaceUserPropertiesResponse | undefined =>
|
||||||
|
this.projectNavigationPreferencesMap[workspaceSlug]
|
||||||
|
);
|
||||||
|
|
||||||
|
fetchProjectNavigationPreferences = async (workspaceSlug: string) => {
|
||||||
|
try {
|
||||||
|
const response = await this.workspaceService.fetchWorkspaceFilters(workspaceSlug);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.projectNavigationPreferencesMap[workspaceSlug] = response;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch project navigation preferences:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateProjectNavigationPreferences = async (
|
||||||
|
workspaceSlug: string,
|
||||||
|
data: Partial<IWorkspaceUserPropertiesResponse>
|
||||||
|
) => {
|
||||||
|
const beforeUpdateData = clone(this.projectNavigationPreferencesMap[workspaceSlug]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Optimistically update store
|
||||||
|
runInAction(() => {
|
||||||
|
this.projectNavigationPreferencesMap[workspaceSlug] = {
|
||||||
|
...this.projectNavigationPreferencesMap[workspaceSlug],
|
||||||
|
...data,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Call API to persist changes
|
||||||
|
await this.workspaceService.patchWorkspaceFilters(workspaceSlug, data);
|
||||||
|
} catch (error) {
|
||||||
|
// Rollback on failure
|
||||||
|
runInAction(() => {
|
||||||
|
this.projectNavigationPreferencesMap[workspaceSlug] = beforeUpdateData;
|
||||||
|
});
|
||||||
|
console.error("Failed to update project navigation preferences:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mutate workspace members activity
|
* Mutate workspace members activity
|
||||||
* @param workspaceSlug
|
* @param workspaceSlug
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export interface TPersonalNavigationItemState {
|
||||||
sort_order: number;
|
sort_order: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TProjectNavigationMode = "accordion" | "horizontal";
|
export type TProjectNavigationMode = "ACCORDION" | "TABBED";
|
||||||
|
|
||||||
export interface TProjectDisplaySettings {
|
export interface TProjectDisplaySettings {
|
||||||
navigationMode: TProjectNavigationMode;
|
navigationMode: TProjectNavigationMode;
|
||||||
|
|
@ -54,7 +54,7 @@ export const DEFAULT_PERSONAL_PREFERENCES: TPersonalNavigationPreferences = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_PROJECT_PREFERENCES: TProjectNavigationPreferences = {
|
export const DEFAULT_PROJECT_PREFERENCES: TProjectNavigationPreferences = {
|
||||||
navigationMode: "accordion",
|
navigationMode: "ACCORDION",
|
||||||
showLimitedProjects: false,
|
showLimitedProjects: false,
|
||||||
limitedProjectsCount: 10,
|
limitedProjectsCount: 10,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,12 @@ export interface IIssueFiltersResponse {
|
||||||
display_properties: IIssueDisplayProperties;
|
display_properties: IIssueDisplayProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IWorkspaceUserPropertiesResponse extends IIssueFiltersResponse {
|
||||||
|
navigation_project_limit?: number;
|
||||||
|
navigation_control_preference?: "ACCORDION" | "TABBED";
|
||||||
|
// Note: show_limited_projects is derived from navigation_project_limit (0 = false, >0 = true)
|
||||||
|
}
|
||||||
|
|
||||||
export interface IWorkspaceIssueFilterOptions {
|
export interface IWorkspaceIssueFilterOptions {
|
||||||
assignees?: string[] | null;
|
assignees?: string[] | null;
|
||||||
created_by?: string[] | null;
|
created_by?: string[] | null;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue