forked from github/plane
Merge branch 'stage-release' of https://github.com/makeplane/plane
This commit is contained in:
commit
afcf1083ff
@ -4,3 +4,4 @@ export const SET_ISSUE_VIEW = "SET_ISSUE_VIEW";
|
||||
export const SET_GROUP_BY_PROPERTY = "SET_GROUP_BY_PROPERTY";
|
||||
export const SET_ORDER_BY_PROPERTY = "SET_ORDER_BY_PROPERTY";
|
||||
export const SET_FILTER_ISSUES = "SET_FILTER_ISSUES";
|
||||
export const RESET_TO_DEFAULT = "RESET_TO_DEFAULT";
|
||||
|
@ -9,13 +9,14 @@ import {
|
||||
SET_GROUP_BY_PROPERTY,
|
||||
SET_ORDER_BY_PROPERTY,
|
||||
SET_FILTER_ISSUES,
|
||||
RESET_TO_DEFAULT,
|
||||
} from "constants/theme.context.constants";
|
||||
// components
|
||||
import ToastAlert from "components/toast-alert";
|
||||
// hooks
|
||||
import useUser from "lib/hooks/useUser";
|
||||
// constants
|
||||
import { PROJECT_MEMBERS, USER_PROJECT_VIEW } from "constants/fetch-keys";
|
||||
import { USER_PROJECT_VIEW } from "constants/fetch-keys";
|
||||
// services
|
||||
import projectService from "lib/services/project.service";
|
||||
|
||||
@ -31,7 +32,8 @@ type ReducerActionType = {
|
||||
| typeof SET_ISSUE_VIEW
|
||||
| typeof SET_ORDER_BY_PROPERTY
|
||||
| typeof SET_FILTER_ISSUES
|
||||
| typeof SET_GROUP_BY_PROPERTY;
|
||||
| typeof SET_GROUP_BY_PROPERTY
|
||||
| typeof RESET_TO_DEFAULT;
|
||||
payload?: Partial<Theme>;
|
||||
};
|
||||
|
||||
@ -42,10 +44,13 @@ type ContextType = {
|
||||
groupByProperty: NestedKeyOf<IIssue> | null;
|
||||
filterIssue: "activeIssue" | "backlogIssue" | null;
|
||||
toggleCollapsed: () => void;
|
||||
setIssueView: (display: "list" | "kanban") => void;
|
||||
setGroupByProperty: (property: NestedKeyOf<IIssue> | null) => void;
|
||||
setOrderBy: (property: NestedKeyOf<IIssue> | null) => void;
|
||||
setFilterIssue: (property: "activeIssue" | "backlogIssue" | null) => void;
|
||||
resetFilterToDefault: () => void;
|
||||
setNewFilterDefaultView: () => void;
|
||||
setIssueViewToKanban: () => void;
|
||||
setIssueViewToList: () => void;
|
||||
};
|
||||
|
||||
type StateType = Theme;
|
||||
@ -68,10 +73,12 @@ export const reducer: ReducerFunctionType = (state, action) => {
|
||||
...state,
|
||||
collapsed: !state.collapsed,
|
||||
};
|
||||
localStorage.setItem("collapsed", JSON.stringify(newState.collapsed));
|
||||
return newState;
|
||||
case REHYDRATE_THEME: {
|
||||
const newState = payload;
|
||||
return { ...initialState, ...newState };
|
||||
let collapsed: any = localStorage.getItem("collapsed");
|
||||
collapsed = collapsed ? JSON.parse(collapsed) : false;
|
||||
return { ...initialState, ...payload, collapsed };
|
||||
}
|
||||
case SET_ISSUE_VIEW: {
|
||||
const newState = {
|
||||
@ -113,6 +120,12 @@ export const reducer: ReducerFunctionType = (state, action) => {
|
||||
...newState,
|
||||
};
|
||||
}
|
||||
case RESET_TO_DEFAULT: {
|
||||
return {
|
||||
...initialState,
|
||||
...payload,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
@ -120,18 +133,27 @@ export const reducer: ReducerFunctionType = (state, action) => {
|
||||
};
|
||||
|
||||
const saveDataToServer = async (workspaceSlug: string, projectID: string, state: any) => {
|
||||
await projectService.setProjectView(workspaceSlug, projectID, state);
|
||||
await projectService.setProjectView(workspaceSlug, projectID, {
|
||||
view_props: state,
|
||||
});
|
||||
};
|
||||
|
||||
const setNewDefault = async (workspaceSlug: string, projectID: string, state: any) => {
|
||||
await projectService.setProjectView(workspaceSlug, projectID, {
|
||||
view_props: state,
|
||||
default_props: state,
|
||||
});
|
||||
};
|
||||
|
||||
export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
|
||||
const { activeProject, activeWorkspace, user } = useUser();
|
||||
const { activeProject, activeWorkspace } = useUser();
|
||||
|
||||
const { data: projectMember } = useSWR(
|
||||
activeWorkspace && activeProject ? PROJECT_MEMBERS(activeProject.id) : null,
|
||||
const { data: myViewProps, mutate: mutateMyViewProps } = useSWR(
|
||||
activeWorkspace && activeProject ? USER_PROJECT_VIEW(activeProject.id) : null,
|
||||
activeWorkspace && activeProject
|
||||
? () => projectService.projectMembers(activeWorkspace.slug, activeProject.id)
|
||||
? () => projectService.projectMemberMe(activeWorkspace.slug, activeProject.id)
|
||||
: null
|
||||
);
|
||||
|
||||
@ -141,23 +163,47 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setIssueView = useCallback(
|
||||
(display: "list" | "kanban") => {
|
||||
dispatch({
|
||||
type: SET_ISSUE_VIEW,
|
||||
payload: {
|
||||
issueView: display,
|
||||
},
|
||||
});
|
||||
const setIssueViewToKanban = useCallback(() => {
|
||||
dispatch({
|
||||
type: SET_ISSUE_VIEW,
|
||||
payload: {
|
||||
issueView: "kanban",
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: SET_GROUP_BY_PROPERTY,
|
||||
payload: {
|
||||
groupByProperty: "state_detail.name",
|
||||
},
|
||||
});
|
||||
if (!activeWorkspace || !activeProject) return;
|
||||
saveDataToServer(activeWorkspace.slug, activeProject.id, {
|
||||
...state,
|
||||
issueView: "kanban",
|
||||
groupByProperty: "state_detail.name",
|
||||
});
|
||||
}, [activeWorkspace, activeProject, state]);
|
||||
|
||||
if (!activeWorkspace || !activeProject) return;
|
||||
saveDataToServer(activeWorkspace.slug, activeProject.id, {
|
||||
...state,
|
||||
issueView: display,
|
||||
});
|
||||
},
|
||||
[activeProject, activeWorkspace, state]
|
||||
);
|
||||
const setIssueViewToList = useCallback(() => {
|
||||
dispatch({
|
||||
type: SET_ISSUE_VIEW,
|
||||
payload: {
|
||||
issueView: "list",
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: SET_GROUP_BY_PROPERTY,
|
||||
payload: {
|
||||
groupByProperty: null,
|
||||
},
|
||||
});
|
||||
if (!activeWorkspace || !activeProject) return;
|
||||
saveDataToServer(activeWorkspace.slug, activeProject.id, {
|
||||
...state,
|
||||
issueView: "list",
|
||||
groupByProperty: null,
|
||||
});
|
||||
}, [activeWorkspace, activeProject, state]);
|
||||
|
||||
const setGroupByProperty = useCallback(
|
||||
(property: NestedKeyOf<IIssue> | null) => {
|
||||
@ -191,6 +237,7 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
},
|
||||
[activeProject, activeWorkspace, state]
|
||||
);
|
||||
|
||||
const setFilterIssue = useCallback(
|
||||
(property: "activeIssue" | "backlogIssue" | null) => {
|
||||
dispatch({
|
||||
@ -209,12 +256,30 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
[activeProject, activeWorkspace, state]
|
||||
);
|
||||
|
||||
const setNewDefaultView = useCallback(() => {
|
||||
if (!activeWorkspace || !activeProject) return;
|
||||
setNewDefault(activeWorkspace.slug, activeProject.id, state);
|
||||
}, [activeProject, activeWorkspace, state]);
|
||||
|
||||
const resetToDefault = useCallback(() => {
|
||||
dispatch({
|
||||
type: RESET_TO_DEFAULT,
|
||||
payload: myViewProps?.default_props,
|
||||
});
|
||||
if (!activeWorkspace || !activeProject) return;
|
||||
saveDataToServer(activeWorkspace.slug, activeProject.id, myViewProps?.default_props).then(
|
||||
() => {
|
||||
mutateMyViewProps();
|
||||
}
|
||||
);
|
||||
}, [activeProject, activeWorkspace, myViewProps, mutateMyViewProps]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: REHYDRATE_THEME,
|
||||
payload: projectMember?.find((member) => member.member.id === user?.id)?.view_props,
|
||||
payload: myViewProps?.view_props,
|
||||
});
|
||||
}, [projectMember, user]);
|
||||
}, [myViewProps]);
|
||||
|
||||
return (
|
||||
<themeContext.Provider
|
||||
@ -222,13 +287,16 @@ export const ThemeContextProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
collapsed: state.collapsed,
|
||||
toggleCollapsed,
|
||||
issueView: state.issueView,
|
||||
setIssueView,
|
||||
groupByProperty: state.groupByProperty,
|
||||
setGroupByProperty,
|
||||
orderBy: state.orderBy,
|
||||
setOrderBy,
|
||||
filterIssue: state.filterIssue,
|
||||
setFilterIssue,
|
||||
resetFilterToDefault: resetToDefault,
|
||||
setNewFilterDefaultView: setNewDefaultView,
|
||||
setIssueViewToKanban,
|
||||
setIssueViewToList,
|
||||
}}
|
||||
>
|
||||
<ToastAlert />
|
||||
|
@ -10,13 +10,16 @@ import type { IIssue } from "types";
|
||||
const useIssuesFilter = (projectIssues: IIssue[]) => {
|
||||
const {
|
||||
issueView,
|
||||
setIssueView,
|
||||
groupByProperty,
|
||||
setGroupByProperty,
|
||||
orderBy,
|
||||
setOrderBy,
|
||||
filterIssue,
|
||||
setFilterIssue,
|
||||
resetFilterToDefault,
|
||||
setNewFilterDefaultView,
|
||||
setIssueViewToKanban,
|
||||
setIssueViewToList,
|
||||
} = useTheme();
|
||||
|
||||
const { states } = useUser();
|
||||
@ -88,13 +91,16 @@ const useIssuesFilter = (projectIssues: IIssue[]) => {
|
||||
return {
|
||||
groupedByIssues,
|
||||
issueView,
|
||||
setIssueView,
|
||||
groupByProperty,
|
||||
setGroupByProperty,
|
||||
orderBy,
|
||||
setOrderBy,
|
||||
filterIssue,
|
||||
setFilterIssue,
|
||||
resetFilterToDefault,
|
||||
setNewFilterDefaultView,
|
||||
setIssueViewToKanban,
|
||||
setIssueViewToList,
|
||||
} as const;
|
||||
};
|
||||
|
||||
|
@ -230,7 +230,10 @@ class ProjectServices extends APIService {
|
||||
async setProjectView(
|
||||
workspacSlug: string,
|
||||
projectId: string,
|
||||
data: ProjectViewTheme
|
||||
data: {
|
||||
view_props?: ProjectViewTheme;
|
||||
default_props?: ProjectViewTheme;
|
||||
}
|
||||
): Promise<any> {
|
||||
await this.post(PROJECT_VIEW_ENDPOINT(workspacSlug, projectId), data)
|
||||
.then((response) => {
|
||||
|
@ -126,7 +126,6 @@ const SingleCycle: React.FC = () => {
|
||||
|
||||
const {
|
||||
issueView,
|
||||
setIssueView,
|
||||
groupByProperty,
|
||||
setGroupByProperty,
|
||||
groupedByIssues,
|
||||
@ -134,6 +133,8 @@ const SingleCycle: React.FC = () => {
|
||||
setFilterIssue,
|
||||
orderBy,
|
||||
filterIssue,
|
||||
setIssueViewToKanban,
|
||||
setIssueViewToList,
|
||||
} = useIssuesFilter(cycleIssuesArray ?? []);
|
||||
|
||||
const openCreateIssueModal = (
|
||||
@ -269,10 +270,7 @@ const SingleCycle: React.FC = () => {
|
||||
className={`h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300 outline-none ${
|
||||
issueView === "list" ? "bg-gray-200" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setIssueView("list");
|
||||
setGroupByProperty(null);
|
||||
}}
|
||||
onClick={() => setIssueViewToList()}
|
||||
>
|
||||
<ListBulletIcon className="h-4 w-4" />
|
||||
</button>
|
||||
@ -281,10 +279,7 @@ const SingleCycle: React.FC = () => {
|
||||
className={`h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300 outline-none ${
|
||||
issueView === "kanban" ? "bg-gray-200" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setIssueView("kanban");
|
||||
setGroupByProperty("state_detail.name");
|
||||
}}
|
||||
onClick={() => setIssueViewToKanban()}
|
||||
>
|
||||
<Squares2X2Icon className="h-4 w-4" />
|
||||
</button>
|
||||
|
@ -134,7 +134,6 @@ const ProjectIssues: NextPage = () => {
|
||||
|
||||
const {
|
||||
issueView,
|
||||
setIssueView,
|
||||
groupByProperty,
|
||||
setGroupByProperty,
|
||||
groupedByIssues,
|
||||
@ -142,6 +141,10 @@ const ProjectIssues: NextPage = () => {
|
||||
setFilterIssue,
|
||||
orderBy,
|
||||
filterIssue,
|
||||
resetFilterToDefault,
|
||||
setNewFilterDefaultView,
|
||||
setIssueViewToKanban,
|
||||
setIssueViewToList,
|
||||
} = useIssuesFilter(projectIssues?.results.filter((p) => p.parent === null) ?? []);
|
||||
|
||||
useEffect(() => {
|
||||
@ -169,10 +172,7 @@ const ProjectIssues: NextPage = () => {
|
||||
className={`h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300 outline-none ${
|
||||
issueView === "list" ? "bg-gray-200" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setIssueView("list");
|
||||
setGroupByProperty(null);
|
||||
}}
|
||||
onClick={() => setIssueViewToList()}
|
||||
>
|
||||
<ListBulletIcon className="h-4 w-4" />
|
||||
</button>
|
||||
@ -181,10 +181,7 @@ const ProjectIssues: NextPage = () => {
|
||||
className={`h-7 w-7 p-1 grid place-items-center rounded hover:bg-gray-200 duration-300 outline-none ${
|
||||
issueView === "kanban" ? "bg-gray-200" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setIssueView("kanban");
|
||||
setGroupByProperty("state_detail.name");
|
||||
}}
|
||||
onClick={() => setIssueViewToKanban()}
|
||||
>
|
||||
<Squares2X2Icon className="h-4 w-4" />
|
||||
</button>
|
||||
@ -292,6 +289,23 @@ const ProjectIssues: NextPage = () => {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-b-2"></div>
|
||||
<div className="relative flex justify-end gap-x-3">
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs"
|
||||
onClick={() => resetFilterToDefault()}
|
||||
>
|
||||
Reset to default
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs font-medium text-theme"
|
||||
onClick={() => setNewFilterDefaultView()}
|
||||
>
|
||||
Set as default
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
|
2
apps/app/types/projects.d.ts
vendored
2
apps/app/types/projects.d.ts
vendored
@ -31,7 +31,9 @@ export interface IProjectMember {
|
||||
workspace: IWorkspace;
|
||||
comment: string;
|
||||
role: 5 | 10 | 15 | 20;
|
||||
|
||||
view_props: ProjectViewTheme;
|
||||
default_props: ProjectViewTheme;
|
||||
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
|
Loading…
Reference in New Issue
Block a user