forked from github/plane
fix: enable global/ all issues (#3405)
* fix global issues and views * remove separate layouts for specific views * add permissions to views * fix global issues filters --------- Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
This commit is contained in:
parent
c9337d4a41
commit
ea3a0362b0
@ -12,7 +12,12 @@ import { Breadcrumbs, Button, LayersIcon, PhotoFilterIcon, Tooltip } from "@plan
|
|||||||
// icons
|
// icons
|
||||||
import { List, PlusIcon, Sheet } from "lucide-react";
|
import { List, PlusIcon, Sheet } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
|
import {
|
||||||
|
IIssueDisplayFilterOptions,
|
||||||
|
IIssueDisplayProperties,
|
||||||
|
IIssueFilterOptions,
|
||||||
|
TStaticViewTypes,
|
||||||
|
} from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
import { EIssueFilterType, EIssuesStoreType, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||||
@ -35,7 +40,7 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
|
|||||||
const { workspaceSlug, globalViewId } = router.query;
|
const { workspaceSlug, globalViewId } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
issuesFilter: { issueFilters, updateFilters },
|
issuesFilter: { filters, updateFilters },
|
||||||
} = useIssues(EIssuesStoreType.GLOBAL);
|
} = useIssues(EIssuesStoreType.GLOBAL);
|
||||||
const {
|
const {
|
||||||
membership: { currentWorkspaceRole },
|
membership: { currentWorkspaceRole },
|
||||||
@ -47,6 +52,8 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
|
|||||||
workspace: { workspaceMemberIds },
|
workspace: { workspaceMemberIds },
|
||||||
} = useMember();
|
} = useMember();
|
||||||
|
|
||||||
|
const issueFilters = globalViewId ? filters[globalViewId.toString()] : undefined;
|
||||||
|
|
||||||
const handleFiltersUpdate = useCallback(
|
const handleFiltersUpdate = useCallback(
|
||||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||||
if (!workspaceSlug || !globalViewId) return;
|
if (!workspaceSlug || !globalViewId) return;
|
||||||
|
@ -25,13 +25,14 @@ type Props = {
|
|||||||
handleRemoveFilter: (key: keyof IIssueFilterOptions, value: string | null) => void;
|
handleRemoveFilter: (key: keyof IIssueFilterOptions, value: string | null) => void;
|
||||||
labels?: IIssueLabel[] | undefined;
|
labels?: IIssueLabel[] | undefined;
|
||||||
states?: IState[] | undefined;
|
states?: IState[] | undefined;
|
||||||
|
alwaysAllowEditing?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const membersFilters = ["assignees", "mentions", "created_by", "subscriber"];
|
const membersFilters = ["assignees", "mentions", "created_by", "subscriber"];
|
||||||
const dateFilters = ["start_date", "target_date"];
|
const dateFilters = ["start_date", "target_date"];
|
||||||
|
|
||||||
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
||||||
const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, states } = props;
|
const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, states, alwaysAllowEditing } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
membership: { currentProjectRole },
|
membership: { currentProjectRole },
|
||||||
@ -41,7 +42,7 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
if (Object.keys(appliedFilters).length === 0) return null;
|
if (Object.keys(appliedFilters).length === 0) return null;
|
||||||
|
|
||||||
const isEditingAllowed = currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
const isEditingAllowed = alwaysAllowEditing || (currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-stretch gap-2 bg-custom-background-100">
|
<div className="flex flex-wrap items-stretch gap-2 bg-custom-background-100">
|
||||||
|
@ -15,13 +15,13 @@ export const AppliedMembersFilters: React.FC<Props> = observer((props) => {
|
|||||||
const { handleRemove, values, editable } = props;
|
const { handleRemove, values, editable } = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
project: { getProjectMemberDetails },
|
workspace: { getWorkspaceMemberDetails },
|
||||||
} = useMember();
|
} = useMember();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{values.map((memberId) => {
|
{values.map((memberId) => {
|
||||||
const memberDetails = getProjectMemberDetails(memberId)?.member;
|
const memberDetails = getWorkspaceMemberDetails(memberId)?.member;
|
||||||
|
|
||||||
if (!memberDetails) return null;
|
if (!memberDetails) return null;
|
||||||
|
|
||||||
|
@ -1,32 +1,48 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import isEqual from "lodash/isEqual";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssues, useLabel } from "hooks/store";
|
import { useGlobalView, useIssues, useLabel, useUser } from "hooks/store";
|
||||||
|
//ui
|
||||||
|
import { Button } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { AppliedFiltersList } from "components/issues";
|
import { AppliedFiltersList } from "components/issues";
|
||||||
// types
|
// types
|
||||||
import { IIssueFilterOptions } from "@plane/types";
|
import { IIssueFilterOptions, TStaticViewTypes } from "@plane/types";
|
||||||
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||||
|
import { DEFAULT_GLOBAL_VIEWS_LIST, EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
|
||||||
export const GlobalViewsAppliedFiltersRoot = observer(() => {
|
type Props = {
|
||||||
|
globalViewId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GlobalViewsAppliedFiltersRoot = observer((props: Props) => {
|
||||||
|
const { globalViewId } = props;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, globalViewId } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
issuesFilter: { issueFilters, updateFilters },
|
issuesFilter: { filters, updateFilters },
|
||||||
} = useIssues(EIssuesStoreType.GLOBAL);
|
} = useIssues(EIssuesStoreType.GLOBAL);
|
||||||
const {
|
const {
|
||||||
workspace: { workspaceLabels },
|
workspace: { workspaceLabels },
|
||||||
} = useLabel();
|
} = useLabel();
|
||||||
|
const { globalViewMap, updateGlobalView } = useGlobalView();
|
||||||
|
const {
|
||||||
|
membership: { currentWorkspaceRole },
|
||||||
|
} = useUser();
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const userFilters = issueFilters?.filters;
|
const userFilters = filters?.[globalViewId]?.filters;
|
||||||
|
const viewDetails = globalViewMap[globalViewId];
|
||||||
|
|
||||||
// filters whose value not null or empty array
|
// filters whose value not null or empty array
|
||||||
const appliedFilters: IIssueFilterOptions = {};
|
let appliedFilters: IIssueFilterOptions | undefined = undefined;
|
||||||
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
|
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
if (Array.isArray(value) && value.length === 0) return;
|
if (Array.isArray(value) && value.length === 0) return;
|
||||||
|
if (!appliedFilters) appliedFilters = {};
|
||||||
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -70,29 +86,24 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// const handleUpdateView = () => {
|
const handleUpdateView = () => {
|
||||||
// if (!workspaceSlug || !globalViewId || !viewDetails) return;
|
if (!workspaceSlug || !globalViewId) return;
|
||||||
|
|
||||||
// globalViewsStore.updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), {
|
updateGlobalView(workspaceSlug.toString(), globalViewId.toString(), {
|
||||||
// query_data: {
|
filters: {
|
||||||
// ...viewDetails.query_data,
|
...(appliedFilters ?? {}),
|
||||||
// filters: {
|
},
|
||||||
// ...(storedFilters ?? {}),
|
});
|
||||||
// },
|
};
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
// update stored filters when view details are fetched
|
const areFiltersEqual = isEqual(appliedFilters, viewDetails?.filters);
|
||||||
// useEffect(() => {
|
|
||||||
// if (!globalViewId || !viewDetails) return;
|
|
||||||
|
|
||||||
// if (!globalViewFiltersStore.storedFilters[globalViewId.toString()])
|
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
|
||||||
// globalViewFiltersStore.updateStoredFilters(globalViewId.toString(), viewDetails?.query_data?.filters ?? {});
|
|
||||||
// }, [globalViewId, globalViewFiltersStore, viewDetails]);
|
const isDefaultView = DEFAULT_GLOBAL_VIEWS_LIST.map((view) => view.key).includes(globalViewId as TStaticViewTypes);
|
||||||
|
|
||||||
// return if no filters are applied
|
// return if no filters are applied
|
||||||
if (Object.keys(appliedFilters).length === 0) return null;
|
if (!appliedFilters && areFiltersEqual) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-start justify-between gap-4 p-4">
|
<div className="flex items-start justify-between gap-4 p-4">
|
||||||
@ -101,13 +112,17 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => {
|
|||||||
appliedFilters={appliedFilters ?? {}}
|
appliedFilters={appliedFilters ?? {}}
|
||||||
handleClearAllFilters={handleClearAllFilters}
|
handleClearAllFilters={handleClearAllFilters}
|
||||||
handleRemoveFilter={handleRemoveFilter}
|
handleRemoveFilter={handleRemoveFilter}
|
||||||
|
alwaysAllowEditing
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* {storedFilters && viewDetails && areFiltersDifferent(storedFilters, viewDetails.query_data.filters ?? {}) && (
|
{!isDefaultView && !areFiltersEqual && isAuthorizedUser && (
|
||||||
<Button variant="primary" onClick={handleUpdateView}>
|
<>
|
||||||
Update view
|
<div />
|
||||||
</Button>
|
<Button variant="primary" onClick={handleUpdateView}>
|
||||||
)} */}
|
Update view
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import isEqual from "lodash/isEqual";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssues, useLabel, useProjectState, useProjectView } from "hooks/store";
|
import { useIssues, useLabel, useProjectState, useProjectView } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { AppliedFiltersList } from "components/issues";
|
import { AppliedFiltersList } from "components/issues";
|
||||||
// ui
|
// ui
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// helpers
|
|
||||||
import { areFiltersDifferent } from "helpers/filter.helper";
|
|
||||||
// types
|
// types
|
||||||
import { IIssueFilterOptions } from "@plane/types";
|
import { IIssueFilterOptions } from "@plane/types";
|
||||||
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||||
@ -33,10 +32,11 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
|||||||
const viewDetails = viewId ? viewMap[viewId.toString()] : null;
|
const viewDetails = viewId ? viewMap[viewId.toString()] : null;
|
||||||
const userFilters = issueFilters?.filters;
|
const userFilters = issueFilters?.filters;
|
||||||
// filters whose value not null or empty array
|
// filters whose value not null or empty array
|
||||||
const appliedFilters: IIssueFilterOptions = {};
|
let appliedFilters: IIssueFilterOptions | undefined = undefined;
|
||||||
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
|
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
if (Array.isArray(value) && value.length === 0) return;
|
if (Array.isArray(value) && value.length === 0) return;
|
||||||
|
if (!appliedFilters) appliedFilters = {};
|
||||||
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
appliedFilters[key as keyof IIssueFilterOptions] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -78,9 +78,9 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
|||||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { ...newFilters }, viewId);
|
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { ...newFilters }, viewId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const areFiltersEqual = isEqual(appliedFilters, viewDetails?.filters);
|
||||||
// return if no filters are applied
|
// return if no filters are applied
|
||||||
if (Object.keys(appliedFilters).length === 0 && !areFiltersDifferent(appliedFilters, viewDetails?.filters ?? {}))
|
if (!appliedFilters && areFiltersEqual) return null;
|
||||||
return null;
|
|
||||||
|
|
||||||
const handleUpdateView = () => {
|
const handleUpdateView = () => {
|
||||||
if (!workspaceSlug || !projectId || !viewId || !viewDetails) return;
|
if (!workspaceSlug || !projectId || !viewId || !viewDetails) return;
|
||||||
@ -95,19 +95,23 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between gap-4 p-4">
|
<div className="flex items-center justify-between gap-4 p-4">
|
||||||
<AppliedFiltersList
|
<AppliedFiltersList
|
||||||
appliedFilters={appliedFilters}
|
appliedFilters={appliedFilters ?? {}}
|
||||||
handleClearAllFilters={handleClearAllFilters}
|
handleClearAllFilters={handleClearAllFilters}
|
||||||
handleRemoveFilter={handleRemoveFilter}
|
handleRemoveFilter={handleRemoveFilter}
|
||||||
labels={projectLabels ?? []}
|
labels={projectLabels ?? []}
|
||||||
states={projectStates}
|
states={projectStates}
|
||||||
|
alwaysAllowEditing
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{viewDetails?.filters && areFiltersDifferent(appliedFilters, viewDetails?.filters ?? {}) && (
|
{!areFiltersEqual && (
|
||||||
<div className="flex flex-shrink-0 items-center justify-center">
|
<>
|
||||||
<Button variant="primary" size="sm" onClick={handleUpdateView}>
|
<div />
|
||||||
Update view
|
<div className="flex flex-shrink-0 items-center justify-center">
|
||||||
</Button>
|
<Button variant="primary" size="sm" onClick={handleUpdateView}>
|
||||||
</div>
|
Update view
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -16,43 +16,43 @@ import { EIssueActions } from "../types";
|
|||||||
import { EUserProjectRoles } from "constants/project";
|
import { EUserProjectRoles } from "constants/project";
|
||||||
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||||
|
|
||||||
type Props = {
|
|
||||||
type?: TStaticViewTypes | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AllIssueLayoutRoot: React.FC<Props> = observer((props) => {
|
|
||||||
const { type = null } = props;
|
export const AllIssueLayoutRoot: React.FC = observer(() => {
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, globalViewId } = router.query as { workspaceSlug: string; globalViewId: string };
|
const { workspaceSlug, globalViewId } = router.query;
|
||||||
|
|
||||||
// store
|
// store
|
||||||
const {
|
const {
|
||||||
issuesFilter: { issueFilters, fetchFilters, updateFilters },
|
issuesFilter: { filters, fetchFilters, updateFilters },
|
||||||
issues: { loader, groupedIssueIds, fetchIssues, updateIssue, removeIssue },
|
issues: { loader, groupedIssueIds, fetchIssues, updateIssue, removeIssue },
|
||||||
issueMap,
|
|
||||||
} = useIssues(EIssuesStoreType.GLOBAL);
|
} = useIssues(EIssuesStoreType.GLOBAL);
|
||||||
|
|
||||||
|
const { dataViewId, issueIds } = groupedIssueIds;
|
||||||
const {
|
const {
|
||||||
membership: { currentWorkspaceAllProjectsRole },
|
membership: { currentWorkspaceAllProjectsRole },
|
||||||
} = useUser();
|
} = useUser();
|
||||||
const { fetchAllGlobalViews } = useGlobalView();
|
const { fetchAllGlobalViews } = useGlobalView();
|
||||||
// derived values
|
// derived values
|
||||||
const currentIssueView = type ?? globalViewId;
|
|
||||||
|
|
||||||
useSWR(workspaceSlug ? `WORKSPACE_GLOBAL_VIEWS${workspaceSlug}` : null, async () => {
|
useSWR(workspaceSlug ? `WORKSPACE_GLOBAL_VIEWS${workspaceSlug}` : null, async () => {
|
||||||
if (workspaceSlug) {
|
if (workspaceSlug) {
|
||||||
await fetchAllGlobalViews(workspaceSlug);
|
await fetchAllGlobalViews(workspaceSlug.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
useSWR(
|
useSWR(
|
||||||
workspaceSlug && currentIssueView ? `WORKSPACE_GLOBAL_VIEW_ISSUES_${workspaceSlug}_${currentIssueView}` : null,
|
workspaceSlug && globalViewId ? `WORKSPACE_GLOBAL_VIEW_ISSUES_${workspaceSlug}_${globalViewId}` : null,
|
||||||
async () => {
|
async () => {
|
||||||
if (workspaceSlug && currentIssueView) {
|
if (workspaceSlug && globalViewId) {
|
||||||
await fetchAllGlobalViews(workspaceSlug);
|
await fetchAllGlobalViews(workspaceSlug.toString());
|
||||||
await fetchFilters(workspaceSlug, currentIssueView);
|
await fetchFilters(workspaceSlug.toString(), globalViewId.toString());
|
||||||
await fetchIssues(workspaceSlug, currentIssueView, groupedIssueIds ? "mutation" : "init-loader");
|
await fetchIssues(
|
||||||
|
workspaceSlug.toString(),
|
||||||
|
globalViewId.toString(),
|
||||||
|
groupedIssueIds ? "mutation" : "init-loader"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -65,22 +65,21 @@ export const AllIssueLayoutRoot: React.FC<Props> = observer((props) => {
|
|||||||
return !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
return !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||||
};
|
};
|
||||||
|
|
||||||
const issueIds = (groupedIssueIds ?? []) as TUnGroupedIssues;
|
const issueFilters = globalViewId ? filters?.[globalViewId.toString()] : undefined;
|
||||||
const issuesArray = issueIds?.filter((id: string) => id && issueMap?.[id]).map((id: string) => issueMap?.[id]);
|
|
||||||
|
|
||||||
const issueActions = useMemo(
|
const issueActions = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||||
const projectId = issue.project_id;
|
const projectId = issue.project_id;
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId || !globalViewId) return;
|
||||||
|
|
||||||
await updateIssue(workspaceSlug, projectId, issue.id, issue, currentIssueView);
|
await updateIssue(workspaceSlug.toString(), projectId, issue.id, issue, globalViewId.toString());
|
||||||
},
|
},
|
||||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||||
const projectId = issue.project_id;
|
const projectId = issue.project_id;
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId || !globalViewId) return;
|
||||||
|
|
||||||
await removeIssue(workspaceSlug, projectId, issue.id, currentIssueView);
|
await removeIssue(workspaceSlug.toString(), projectId, issue.id, globalViewId.toString());
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@ -100,22 +99,22 @@ export const AllIssueLayoutRoot: React.FC<Props> = observer((props) => {
|
|||||||
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
updateFilters(workspaceSlug, undefined, EIssueFilterType.DISPLAY_FILTERS, { ...updatedDisplayFilter });
|
updateFilters(workspaceSlug.toString(), undefined, EIssueFilterType.DISPLAY_FILTERS, { ...updatedDisplayFilter });
|
||||||
},
|
},
|
||||||
[updateFilters, workspaceSlug]
|
[updateFilters, workspaceSlug]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
||||||
{globalViewId != currentIssueView && (loader === "init-loader" || !groupedIssueIds) ? (
|
{!globalViewId || globalViewId !== dataViewId || loader === "init-loader" || !issueIds ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<GlobalViewsAppliedFiltersRoot />
|
<GlobalViewsAppliedFiltersRoot globalViewId={globalViewId} />
|
||||||
|
|
||||||
{(groupedIssueIds ?? {}).length == 0 ? (
|
{(issueIds ?? {}).length == 0 ? (
|
||||||
<>{/* <GlobalViewEmptyState /> */}</>
|
<>{/* <GlobalViewEmptyState /> */}</>
|
||||||
) : (
|
) : (
|
||||||
<div className="relative h-full w-full overflow-auto">
|
<div className="relative h-full w-full overflow-auto">
|
||||||
@ -123,7 +122,7 @@ export const AllIssueLayoutRoot: React.FC<Props> = observer((props) => {
|
|||||||
displayProperties={issueFilters?.displayProperties ?? {}}
|
displayProperties={issueFilters?.displayProperties ?? {}}
|
||||||
displayFilters={issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
|
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
|
||||||
issues={issuesArray}
|
issueIds={issueIds}
|
||||||
quickActions={(issue) => (
|
quickActions={(issue) => (
|
||||||
<AllIssueQuickActions
|
<AllIssueQuickActions
|
||||||
issue={issue}
|
issue={issue}
|
||||||
@ -133,7 +132,7 @@ export const AllIssueLayoutRoot: React.FC<Props> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
canEditProperties={canEditProperties}
|
canEditProperties={canEditProperties}
|
||||||
viewId={currentIssueView}
|
viewId={globalViewId}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -30,7 +30,7 @@ export const ProjectViewLayoutRoot: React.FC = observer(() => {
|
|||||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
||||||
|
|
||||||
useSWR(
|
useSWR(
|
||||||
workspaceSlug && projectId && viewId ? `PROJECT_VIEW_ISSUES_${workspaceSlug}_${projectId}` : null,
|
workspaceSlug && projectId && viewId ? `PROJECT_VIEW_ISSUES_${workspaceSlug}_${projectId}_${viewId}` : null,
|
||||||
async () => {
|
async () => {
|
||||||
if (workspaceSlug && projectId && viewId) {
|
if (workspaceSlug && projectId && viewId) {
|
||||||
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString(), viewId.toString());
|
await issuesFilter?.fetchFilters(workspaceSlug.toString(), projectId.toString(), viewId.toString());
|
||||||
|
@ -58,8 +58,6 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
|||||||
|
|
||||||
const issueIds = (issueStore.groupedIssueIds ?? []) as TUnGroupedIssues;
|
const issueIds = (issueStore.groupedIssueIds ?? []) as TUnGroupedIssues;
|
||||||
|
|
||||||
const issues = issueIds?.filter((id) => id && issueMap?.[id]).map((id) => issueMap?.[id]);
|
|
||||||
|
|
||||||
const handleIssues = useCallback(
|
const handleIssues = useCallback(
|
||||||
async (issue: TIssue, action: EIssueActions) => {
|
async (issue: TIssue, action: EIssueActions) => {
|
||||||
if (issueActions[action]) {
|
if (issueActions[action]) {
|
||||||
@ -109,7 +107,7 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
|||||||
displayProperties={issueFiltersStore.issueFilters?.displayProperties ?? {}}
|
displayProperties={issueFiltersStore.issueFilters?.displayProperties ?? {}}
|
||||||
displayFilters={issueFiltersStore.issueFilters?.displayFilters ?? {}}
|
displayFilters={issueFiltersStore.issueFilters?.displayFilters ?? {}}
|
||||||
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
|
handleDisplayFilterUpdate={handleDisplayFiltersUpdate}
|
||||||
issues={issues}
|
issueIds={issueIds}
|
||||||
quickActions={renderQuickActions}
|
quickActions={renderQuickActions}
|
||||||
handleIssues={handleIssues}
|
handleIssues={handleIssues}
|
||||||
canEditProperties={canEditProperties}
|
canEditProperties={canEditProperties}
|
||||||
|
@ -10,7 +10,7 @@ type Props = {
|
|||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties;
|
||||||
displayFilters: IIssueDisplayFilterOptions;
|
displayFilters: IIssueDisplayFilterOptions;
|
||||||
handleDisplayFilterUpdate: (data: Partial<IIssueDisplayFilterOptions>) => void;
|
handleDisplayFilterUpdate: (data: Partial<IIssueDisplayFilterOptions>) => void;
|
||||||
issues: TIssue[];
|
issueIds: string[];
|
||||||
isEstimateEnabled: boolean;
|
isEstimateEnabled: boolean;
|
||||||
quickActions: (
|
quickActions: (
|
||||||
issue: TIssue,
|
issue: TIssue,
|
||||||
@ -27,7 +27,7 @@ export const SpreadsheetTable = observer((props: Props) => {
|
|||||||
displayProperties,
|
displayProperties,
|
||||||
displayFilters,
|
displayFilters,
|
||||||
handleDisplayFilterUpdate,
|
handleDisplayFilterUpdate,
|
||||||
issues,
|
issueIds,
|
||||||
isEstimateEnabled,
|
isEstimateEnabled,
|
||||||
portalElement,
|
portalElement,
|
||||||
quickActions,
|
quickActions,
|
||||||
@ -44,7 +44,7 @@ export const SpreadsheetTable = observer((props: Props) => {
|
|||||||
isEstimateEnabled={isEstimateEnabled}
|
isEstimateEnabled={isEstimateEnabled}
|
||||||
/>
|
/>
|
||||||
<tbody>
|
<tbody>
|
||||||
{issues.map(({ id }) => (
|
{issueIds.map((id) => (
|
||||||
<SpreadsheetIssueRow
|
<SpreadsheetIssueRow
|
||||||
key={id}
|
key={id}
|
||||||
issueId={id}
|
issueId={id}
|
||||||
|
@ -14,7 +14,7 @@ type Props = {
|
|||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties;
|
||||||
displayFilters: IIssueDisplayFilterOptions;
|
displayFilters: IIssueDisplayFilterOptions;
|
||||||
handleDisplayFilterUpdate: (data: Partial<IIssueDisplayFilterOptions>) => void;
|
handleDisplayFilterUpdate: (data: Partial<IIssueDisplayFilterOptions>) => void;
|
||||||
issues: TIssue[] | undefined;
|
issueIds: string[] | undefined;
|
||||||
quickActions: (
|
quickActions: (
|
||||||
issue: TIssue,
|
issue: TIssue,
|
||||||
customActionButton?: React.ReactElement,
|
customActionButton?: React.ReactElement,
|
||||||
@ -39,7 +39,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
|||||||
displayProperties,
|
displayProperties,
|
||||||
displayFilters,
|
displayFilters,
|
||||||
handleDisplayFilterUpdate,
|
handleDisplayFilterUpdate,
|
||||||
issues,
|
issueIds,
|
||||||
quickActions,
|
quickActions,
|
||||||
handleIssues,
|
handleIssues,
|
||||||
quickAddCallback,
|
quickAddCallback,
|
||||||
@ -91,7 +91,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!issues || issues.length === 0)
|
if (!issueIds || issueIds.length === 0)
|
||||||
return (
|
return (
|
||||||
<div className="grid h-full w-full place-items-center">
|
<div className="grid h-full w-full place-items-center">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
@ -106,7 +106,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
|||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
displayFilters={displayFilters}
|
displayFilters={displayFilters}
|
||||||
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
|
||||||
issues={issues}
|
issueIds={issueIds}
|
||||||
isEstimateEnabled={isEstimateEnabled}
|
isEstimateEnabled={isEstimateEnabled}
|
||||||
portalElement={portalRef}
|
portalElement={portalRef}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
|
@ -61,12 +61,12 @@ export const WorkspaceViewForm: React.FC<Props> = observer((props) => {
|
|||||||
});
|
});
|
||||||
}, [data, preLoadedData, reset]);
|
}, [data, preLoadedData, reset]);
|
||||||
|
|
||||||
const selectedFilters = watch("query_data")?.filters;
|
const selectedFilters = watch("filters");
|
||||||
|
|
||||||
const clearAllFilters = () => {
|
const clearAllFilters = () => {
|
||||||
if (!selectedFilters) return;
|
if (!selectedFilters) return;
|
||||||
|
|
||||||
setValue("query_data.filters", {});
|
setValue("filters", {});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -120,7 +120,7 @@ export const WorkspaceViewForm: React.FC<Props> = observer((props) => {
|
|||||||
<div>
|
<div>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="query_data.filters"
|
name="filters"
|
||||||
render={({ field: { onChange, value: filters } }) => (
|
render={({ field: { onChange, value: filters } }) => (
|
||||||
<FiltersDropdown title="Filters">
|
<FiltersDropdown title="Filters">
|
||||||
<FilterSelection
|
<FilterSelection
|
||||||
|
@ -4,11 +4,11 @@ import Link from "next/link";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
// store hooks
|
// store hooks
|
||||||
import { useGlobalView } from "hooks/store";
|
import { useGlobalView, useUser } from "hooks/store";
|
||||||
// components
|
// components
|
||||||
import { CreateUpdateWorkspaceViewModal } from "components/workspace";
|
import { CreateUpdateWorkspaceViewModal } from "components/workspace";
|
||||||
// constants
|
// constants
|
||||||
import { DEFAULT_GLOBAL_VIEWS_LIST } from "constants/workspace";
|
import { DEFAULT_GLOBAL_VIEWS_LIST, EUserWorkspaceRoles } from "constants/workspace";
|
||||||
|
|
||||||
const ViewTab = observer((props: { viewId: string }) => {
|
const ViewTab = observer((props: { viewId: string }) => {
|
||||||
const { viewId } = props;
|
const { viewId } = props;
|
||||||
@ -46,6 +46,9 @@ export const GlobalViewsHeader: React.FC = observer(() => {
|
|||||||
const { workspaceSlug, globalViewId } = router.query;
|
const { workspaceSlug, globalViewId } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { currentWorkspaceViews } = useGlobalView();
|
const { currentWorkspaceViews } = useGlobalView();
|
||||||
|
const {
|
||||||
|
membership: { currentWorkspaceRole },
|
||||||
|
} = useUser();
|
||||||
|
|
||||||
// bring the active view to the centre of the header
|
// bring the active view to the centre of the header
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -56,7 +59,8 @@ export const GlobalViewsHeader: React.FC = observer(() => {
|
|||||||
if (activeTabElement) activeTabElement.scrollIntoView({ behavior: "smooth", inline: "center" });
|
if (activeTabElement) activeTabElement.scrollIntoView({ behavior: "smooth", inline: "center" });
|
||||||
}, [globalViewId]);
|
}, [globalViewId]);
|
||||||
|
|
||||||
const isTabSelected = (tabKey: string) => router.pathname.includes(tabKey);
|
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CreateUpdateWorkspaceViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} />
|
<CreateUpdateWorkspaceViewModal isOpen={createViewModal} onClose={() => setCreateViewModal(false)} />
|
||||||
@ -66,7 +70,7 @@ export const GlobalViewsHeader: React.FC = observer(() => {
|
|||||||
<Link key={tab.key} href={`/${workspaceSlug}/workspace-views/${tab.key}`}>
|
<Link key={tab.key} href={`/${workspaceSlug}/workspace-views/${tab.key}`}>
|
||||||
<span
|
<span
|
||||||
className={`flex min-w-min flex-shrink-0 whitespace-nowrap border-b-2 p-3 text-sm font-medium outline-none ${
|
className={`flex min-w-min flex-shrink-0 whitespace-nowrap border-b-2 p-3 text-sm font-medium outline-none ${
|
||||||
isTabSelected(tab.key)
|
tab.key === globalViewId
|
||||||
? "border-custom-primary-100 text-custom-primary-100"
|
? "border-custom-primary-100 text-custom-primary-100"
|
||||||
: "border-transparent hover:border-custom-border-200 hover:text-custom-text-400"
|
: "border-transparent hover:border-custom-border-200 hover:text-custom-text-400"
|
||||||
}`}
|
}`}
|
||||||
@ -81,13 +85,15 @@ export const GlobalViewsHeader: React.FC = observer(() => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
{isAuthorizedUser && (
|
||||||
type="button"
|
<button
|
||||||
className="sticky -right-4 flex w-12 flex-shrink-0 items-center justify-center border-transparent bg-custom-background-100 py-3 hover:border-custom-border-200 hover:text-custom-text-400"
|
type="button"
|
||||||
onClick={() => setCreateViewModal(true)}
|
className="sticky -right-4 flex w-12 flex-shrink-0 items-center justify-center border-transparent bg-custom-background-100 py-3 hover:border-custom-border-200 hover:text-custom-text-400"
|
||||||
>
|
onClick={() => setCreateViewModal(true)}
|
||||||
<Plus className="h-4 w-4 text-custom-primary-200" />
|
>
|
||||||
</button>
|
<Plus className="h-4 w-4 text-custom-primary-200" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -36,8 +36,8 @@ export const CreateUpdateWorkspaceViewModal: React.FC<Props> = observer((props)
|
|||||||
|
|
||||||
const payloadData: Partial<IWorkspaceView> = {
|
const payloadData: Partial<IWorkspaceView> = {
|
||||||
...payload,
|
...payload,
|
||||||
query: {
|
filters: {
|
||||||
...payload.query_data?.filters,
|
...payload?.filters,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ export const CreateUpdateWorkspaceViewModal: React.FC<Props> = observer((props)
|
|||||||
const payloadData: Partial<IWorkspaceView> = {
|
const payloadData: Partial<IWorkspaceView> = {
|
||||||
...payload,
|
...payload,
|
||||||
query: {
|
query: {
|
||||||
...payload.query_data?.filters,
|
...payload?.filters,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ export const GlobalViewListItem: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
if (!view) return null;
|
if (!view) return null;
|
||||||
|
|
||||||
const totalFilters = calculateTotalFilters(view.query_data.filters ?? {});
|
const totalFilters = calculateTotalFilters(view.filters ?? {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -14,19 +14,3 @@ export const calculateTotalFilters = (filters: IIssueFilterOptions): number =>
|
|||||||
.reduce((curr, prev) => curr + prev, 0)
|
.reduce((curr, prev) => curr + prev, 0)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
// check if there is any difference between the saved filters and the current filters
|
|
||||||
export const areFiltersDifferent = (filtersSet1: IIssueFilterOptions, filtersSet2: IIssueFilterOptions) => {
|
|
||||||
for (const [key, value] of Object.entries(filtersSet1) as [keyof IIssueFilterOptions, string[] | null][]) {
|
|
||||||
if (value) {
|
|
||||||
if (Array.isArray(value) && Array.isArray(filtersSet2[key])) {
|
|
||||||
if (value.length !== filtersSet2[key]?.length) return true;
|
|
||||||
|
|
||||||
for (let i = 0; i < value.length; i++) {
|
|
||||||
if (!filtersSet2[key]?.includes(value[i])) return true;
|
|
||||||
}
|
|
||||||
} else if (value !== filtersSet2[key]) return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import { ReactElement } from "react";
|
|
||||||
// components
|
|
||||||
import { GlobalViewsHeader } from "components/workspace";
|
|
||||||
import { GlobalIssuesHeader } from "components/headers";
|
|
||||||
import { AllIssueLayoutRoot } from "components/issues/issue-layouts";
|
|
||||||
// layouts
|
|
||||||
import { AppLayout } from "layouts/app-layout";
|
|
||||||
// types
|
|
||||||
import { NextPageWithLayout } from "lib/types";
|
|
||||||
|
|
||||||
const GlobalViewAllIssuesPage: NextPageWithLayout = () => (
|
|
||||||
<div className="h-full overflow-hidden bg-custom-background-100">
|
|
||||||
<div className="flex h-full w-full flex-col border-b border-custom-border-300">
|
|
||||||
<GlobalViewsHeader />
|
|
||||||
<AllIssueLayoutRoot type="all-issues" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
GlobalViewAllIssuesPage.getLayout = function getLayout(page: ReactElement) {
|
|
||||||
return <AppLayout header={<GlobalIssuesHeader activeLayout="spreadsheet" />}>{page}</AppLayout>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GlobalViewAllIssuesPage;
|
|
@ -1,24 +0,0 @@
|
|||||||
import { ReactElement } from "react";
|
|
||||||
// components
|
|
||||||
import { GlobalViewsHeader } from "components/workspace";
|
|
||||||
import { GlobalIssuesHeader } from "components/headers";
|
|
||||||
import { AllIssueLayoutRoot } from "components/issues/issue-layouts";
|
|
||||||
// layouts
|
|
||||||
import { AppLayout } from "layouts/app-layout";
|
|
||||||
// types
|
|
||||||
import { NextPageWithLayout } from "lib/types";
|
|
||||||
|
|
||||||
const GlobalViewAssignedIssuesPage: NextPageWithLayout = () => (
|
|
||||||
<div className="h-full overflow-hidden bg-custom-background-100">
|
|
||||||
<div className="flex h-full w-full flex-col border-b border-custom-border-300">
|
|
||||||
<GlobalViewsHeader />
|
|
||||||
<AllIssueLayoutRoot type="assigned" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
GlobalViewAssignedIssuesPage.getLayout = function getLayout(page: ReactElement) {
|
|
||||||
return <AppLayout header={<GlobalIssuesHeader activeLayout="spreadsheet" />}>{page}</AppLayout>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GlobalViewAssignedIssuesPage;
|
|
@ -1,24 +0,0 @@
|
|||||||
import { ReactElement } from "react";
|
|
||||||
// components
|
|
||||||
import { GlobalViewsHeader } from "components/workspace";
|
|
||||||
import { GlobalIssuesHeader } from "components/headers";
|
|
||||||
import { AllIssueLayoutRoot } from "components/issues";
|
|
||||||
// layouts
|
|
||||||
import { AppLayout } from "layouts/app-layout";
|
|
||||||
// types
|
|
||||||
import { NextPageWithLayout } from "lib/types";
|
|
||||||
|
|
||||||
const GlobalViewCreatedIssuesPage: NextPageWithLayout = () => (
|
|
||||||
<div className="h-full overflow-hidden bg-custom-background-100">
|
|
||||||
<div className="flex h-full w-full flex-col border-b border-custom-border-300">
|
|
||||||
<GlobalViewsHeader />
|
|
||||||
<AllIssueLayoutRoot type="created" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
GlobalViewCreatedIssuesPage.getLayout = function getLayout(page: ReactElement) {
|
|
||||||
return <AppLayout header={<GlobalIssuesHeader activeLayout="spreadsheet" />}>{page}</AppLayout>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GlobalViewCreatedIssuesPage;
|
|
@ -1,24 +0,0 @@
|
|||||||
import { ReactElement } from "react";
|
|
||||||
// layouts
|
|
||||||
import { AppLayout } from "layouts/app-layout";
|
|
||||||
// components
|
|
||||||
import { GlobalViewsHeader } from "components/workspace";
|
|
||||||
import { GlobalIssuesHeader } from "components/headers";
|
|
||||||
import { AllIssueLayoutRoot } from "components/issues";
|
|
||||||
// types
|
|
||||||
import { NextPageWithLayout } from "lib/types";
|
|
||||||
|
|
||||||
const GlobalViewSubscribedIssuesPage: NextPageWithLayout = () => (
|
|
||||||
<div className="h-full overflow-hidden bg-custom-background-100">
|
|
||||||
<div className="flex h-full w-full flex-col border-b border-custom-border-300">
|
|
||||||
<GlobalViewsHeader />
|
|
||||||
<AllIssueLayoutRoot type="subscribed" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
GlobalViewSubscribedIssuesPage.getLayout = function getLayout(page: ReactElement) {
|
|
||||||
return <AppLayout header={<GlobalIssuesHeader activeLayout="spreadsheet" />}>{page}</AppLayout>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GlobalViewSubscribedIssuesPage;
|
|
@ -8,6 +8,7 @@ import {
|
|||||||
IIssueFiltersResponse,
|
IIssueFiltersResponse,
|
||||||
TIssueKanbanFilters,
|
TIssueKanbanFilters,
|
||||||
TIssueParams,
|
TIssueParams,
|
||||||
|
TStaticViewTypes,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { isNil } from "constants/common";
|
import { isNil } from "constants/common";
|
||||||
@ -109,6 +110,39 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
|
|||||||
subscriber: filters?.subscriber || null,
|
subscriber: filters?.subscriber || null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This PR is to get the filters of the fixed global views
|
||||||
|
* @param currentUserId current logged in user id
|
||||||
|
* @param type fixed view type
|
||||||
|
* @returns filterOptions based on views
|
||||||
|
*/
|
||||||
|
getComputedFiltersBasedOnViews = (currentUserId: string | undefined, type: TStaticViewTypes) => {
|
||||||
|
const noFilters = this.computedFilters({});
|
||||||
|
|
||||||
|
if (!currentUserId) return noFilters;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "assigned":
|
||||||
|
return {
|
||||||
|
...noFilters,
|
||||||
|
assignees: [currentUserId],
|
||||||
|
};
|
||||||
|
case "created":
|
||||||
|
return {
|
||||||
|
...noFilters,
|
||||||
|
created_by: [currentUserId],
|
||||||
|
};
|
||||||
|
case "subscribed":
|
||||||
|
return {
|
||||||
|
...noFilters,
|
||||||
|
subscriber: [currentUserId],
|
||||||
|
};
|
||||||
|
case "all-issues":
|
||||||
|
default:
|
||||||
|
return noFilters;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description This method is used to apply the display filters on the issues
|
* @description This method is used to apply the display filters on the issues
|
||||||
* @param {IIssueDisplayFilterOptions} displayFilters
|
* @param {IIssueDisplayFilterOptions} displayFilters
|
||||||
|
@ -160,9 +160,6 @@ export class ProjectViewIssuesFilter extends IssueFilterHelperStore implements I
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.rootIssueStore.projectViewIssues.fetchIssues(workspaceSlug, projectId, "mutation", viewId);
|
this.rootIssueStore.projectViewIssues.fetchIssues(workspaceSlug, projectId, "mutation", viewId);
|
||||||
await this.issueFilterService.patchView(workspaceSlug, projectId, viewId, {
|
|
||||||
filters: _filters.filters,
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case EIssueFilterType.DISPLAY_FILTERS:
|
case EIssueFilterType.DISPLAY_FILTERS:
|
||||||
const updatedDisplayFilters = filters as IIssueDisplayFilterOptions;
|
const updatedDisplayFilters = filters as IIssueDisplayFilterOptions;
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
TIssueKanbanFilters,
|
TIssueKanbanFilters,
|
||||||
IIssueFilters,
|
IIssueFilters,
|
||||||
TIssueParams,
|
TIssueParams,
|
||||||
|
TStaticViewTypes,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||||
@ -27,7 +28,7 @@ export interface IWorkspaceIssuesFilter {
|
|||||||
// computed
|
// computed
|
||||||
issueFilters: IIssueFilters | undefined;
|
issueFilters: IIssueFilters | undefined;
|
||||||
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined;
|
appliedFilters: Partial<Record<TIssueParams, string | boolean>> | undefined;
|
||||||
// action
|
// fetch action
|
||||||
fetchFilters: (workspaceSlug: string, viewId: string) => Promise<void>;
|
fetchFilters: (workspaceSlug: string, viewId: string) => Promise<void>;
|
||||||
updateFilters: (
|
updateFilters: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
@ -36,6 +37,9 @@ export interface IWorkspaceIssuesFilter {
|
|||||||
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters,
|
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters,
|
||||||
viewId?: string | undefined
|
viewId?: string | undefined
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
//helper action
|
||||||
|
getIssueFilters: (viewId: string | undefined) => IIssueFilters | undefined;
|
||||||
|
getAppliedFilters: (viewId: string) => Partial<Record<TIssueParams, string | boolean>> | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWorkspaceIssuesFilter {
|
export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWorkspaceIssuesFilter {
|
||||||
@ -54,9 +58,12 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
|
|||||||
// computed
|
// computed
|
||||||
issueFilters: computed,
|
issueFilters: computed,
|
||||||
appliedFilters: computed,
|
appliedFilters: computed,
|
||||||
// actions
|
// fetch actions
|
||||||
fetchFilters: action,
|
fetchFilters: action,
|
||||||
updateFilters: action,
|
updateFilters: action,
|
||||||
|
// helper actions
|
||||||
|
getIssueFilters: action,
|
||||||
|
getAppliedFilters: action,
|
||||||
});
|
});
|
||||||
// root store
|
// root store
|
||||||
this.rootIssueStore = _rootStore;
|
this.rootIssueStore = _rootStore;
|
||||||
@ -64,8 +71,7 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
|
|||||||
this.issueFilterService = new WorkspaceService();
|
this.issueFilterService = new WorkspaceService();
|
||||||
}
|
}
|
||||||
|
|
||||||
get issueFilters() {
|
getIssueFilters = (viewId: string | undefined) => {
|
||||||
const viewId = this.rootIssueStore.globalViewId;
|
|
||||||
if (!viewId) return undefined;
|
if (!viewId) return undefined;
|
||||||
|
|
||||||
const displayFilters = this.filters[viewId] || undefined;
|
const displayFilters = this.filters[viewId] || undefined;
|
||||||
@ -74,10 +80,12 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
|
|||||||
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters);
|
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters);
|
||||||
|
|
||||||
return _filters;
|
return _filters;
|
||||||
}
|
};
|
||||||
|
|
||||||
get appliedFilters() {
|
getAppliedFilters = (viewId: string | undefined) => {
|
||||||
const userFilters = this.issueFilters;
|
if (!viewId) return undefined;
|
||||||
|
|
||||||
|
const userFilters = this.getIssueFilters(viewId);
|
||||||
if (!userFilters) return undefined;
|
if (!userFilters) return undefined;
|
||||||
|
|
||||||
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
|
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
|
||||||
@ -92,6 +100,16 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
|
|||||||
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
if (userFilters?.displayFilters?.layout === "gantt_chart") filteredRouteParams.start_target_date = true;
|
||||||
|
|
||||||
return filteredRouteParams;
|
return filteredRouteParams;
|
||||||
|
};
|
||||||
|
|
||||||
|
get issueFilters() {
|
||||||
|
const viewId = this.rootIssueStore.globalViewId;
|
||||||
|
return this.getIssueFilters(viewId);
|
||||||
|
}
|
||||||
|
|
||||||
|
get appliedFilters() {
|
||||||
|
const viewId = this.rootIssueStore.globalViewId;
|
||||||
|
return this.getAppliedFilters(viewId);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchFilters = async (workspaceSlug: string, viewId: TWorkspaceFilters) => {
|
fetchFilters = async (workspaceSlug: string, viewId: TWorkspaceFilters) => {
|
||||||
@ -105,15 +123,17 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
|
|||||||
};
|
};
|
||||||
|
|
||||||
const _filters = this.handleIssuesLocalFilters.get(EIssuesStoreType.GLOBAL, workspaceSlug, undefined, viewId);
|
const _filters = this.handleIssuesLocalFilters.get(EIssuesStoreType.GLOBAL, workspaceSlug, undefined, viewId);
|
||||||
filters = this.computedFilters(_filters?.filters);
|
displayFilters = this.computedDisplayFilters(_filters?.display_filters);
|
||||||
displayFilters = this.computedDisplayFilters(_filters?.displayFilters);
|
displayProperties = this.computedDisplayProperties(_filters?.display_properties);
|
||||||
displayProperties = this.computedDisplayProperties(_filters?.displayProperties);
|
|
||||||
kanbanFilters = {
|
kanbanFilters = {
|
||||||
group_by: _filters?.kanbanFilters?.group_by || [],
|
group_by: _filters?.kanban_filters?.group_by || [],
|
||||||
sub_group_by: _filters?.kanbanFilters?.sub_group_by || [],
|
sub_group_by: _filters?.kanban_filters?.sub_group_by || [],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!["all-issues", "assigned", "created", "subscribed"].includes(viewId)) {
|
if (["all-issues", "assigned", "created", "subscribed"].includes(viewId)) {
|
||||||
|
const currentUserId = this.rootIssueStore.currentUserId;
|
||||||
|
filters = this.getComputedFiltersBasedOnViews(currentUserId, viewId as TStaticViewTypes);
|
||||||
|
} else {
|
||||||
const _filters = await this.issueFilterService.getViewDetails(workspaceSlug, viewId);
|
const _filters = await this.issueFilterService.getViewDetails(workspaceSlug, viewId);
|
||||||
filters = this.computedFilters(_filters?.filters);
|
filters = this.computedFilters(_filters?.filters);
|
||||||
displayFilters = this.computedDisplayFilters(_filters?.display_filters);
|
displayFilters = this.computedDisplayFilters(_filters?.display_filters);
|
||||||
@ -160,16 +180,7 @@ export class WorkspaceIssuesFilter extends IssueFilterHelperStore implements IWo
|
|||||||
set(this.filters, [viewId, "filters", _key], updatedFilters[_key as keyof IIssueFilterOptions]);
|
set(this.filters, [viewId, "filters", _key], updatedFilters[_key as keyof IIssueFilterOptions]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rootIssueStore.workspaceIssues.fetchIssues(workspaceSlug, viewId, "mutation");
|
this.rootIssueStore.workspaceIssues.fetchIssues(workspaceSlug, viewId, "mutation");
|
||||||
if (["all-issues", "assigned", "created", "subscribed"].includes(viewId))
|
|
||||||
this.handleIssuesLocalFilters.set(EIssuesStoreType.GLOBAL, type, workspaceSlug, undefined, viewId, {
|
|
||||||
filters: _filters.filters,
|
|
||||||
});
|
|
||||||
else
|
|
||||||
await this.issueFilterService.updateView(workspaceSlug, viewId, {
|
|
||||||
filters: _filters.filters,
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case EIssueFilterType.DISPLAY_FILTERS:
|
case EIssueFilterType.DISPLAY_FILTERS:
|
||||||
const updatedDisplayFilters = filters as IIssueDisplayFilterOptions;
|
const updatedDisplayFilters = filters as IIssueDisplayFilterOptions;
|
||||||
|
@ -15,7 +15,7 @@ export interface IWorkspaceIssues {
|
|||||||
issues: { [viewId: string]: string[] };
|
issues: { [viewId: string]: string[] };
|
||||||
viewFlags: ViewFlags;
|
viewFlags: ViewFlags;
|
||||||
// computed
|
// computed
|
||||||
groupedIssueIds: TUnGroupedIssues | undefined;
|
groupedIssueIds: { dataViewId: string; issueIds: TUnGroupedIssues | undefined };
|
||||||
// actions
|
// actions
|
||||||
fetchIssues: (workspaceSlug: string, viewId: string, loadType: TLoader) => Promise<TIssue[]>;
|
fetchIssues: (workspaceSlug: string, viewId: string, loadType: TLoader) => Promise<TIssue[]>;
|
||||||
createIssue: (
|
createIssue: (
|
||||||
@ -59,7 +59,7 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue
|
|||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// observable
|
// observable
|
||||||
loader: observable.ref,
|
loader: observable.ref,
|
||||||
issues: observable.ref,
|
issues: observable,
|
||||||
// computed
|
// computed
|
||||||
groupedIssueIds: computed,
|
groupedIssueIds: computed,
|
||||||
// action
|
// action
|
||||||
@ -77,30 +77,32 @@ export class WorkspaceIssues extends IssueHelperStore implements IWorkspaceIssue
|
|||||||
|
|
||||||
get groupedIssueIds() {
|
get groupedIssueIds() {
|
||||||
const viewId = this.rootIssueStore.globalViewId;
|
const viewId = this.rootIssueStore.globalViewId;
|
||||||
if (!viewId) return undefined;
|
if (!viewId) return { dataViewId: "", issueIds: undefined };
|
||||||
|
|
||||||
const displayFilters = this.rootIssueStore?.workspaceIssuesFilter?.issueFilters?.displayFilters;
|
const displayFilters = this.rootIssueStore?.workspaceIssuesFilter?.filters?.[viewId]?.displayFilters;
|
||||||
if (!displayFilters) return undefined;
|
if (!displayFilters) return { dataViewId: viewId, issueIds: undefined };
|
||||||
|
|
||||||
const orderBy = displayFilters?.order_by;
|
const orderBy = displayFilters?.order_by;
|
||||||
|
|
||||||
const viewIssueIds = this.issues[viewId] ?? [];
|
const viewIssueIds = this.issues[viewId];
|
||||||
|
|
||||||
|
if (!viewIssueIds) return { dataViewId: viewId, issueIds: undefined };
|
||||||
|
|
||||||
const _issues = this.rootStore.issues.getIssuesByIds(viewIssueIds);
|
const _issues = this.rootStore.issues.getIssuesByIds(viewIssueIds);
|
||||||
if (!_issues) return undefined;
|
if (!_issues) return { dataViewId: viewId, issueIds: [] };
|
||||||
|
|
||||||
let issues: TIssue | TUnGroupedIssues | undefined = undefined;
|
let issueIds: TIssue | TUnGroupedIssues | undefined = undefined;
|
||||||
|
|
||||||
issues = this.unGroupedIssues(orderBy ?? "-created_at", _issues);
|
issueIds = this.unGroupedIssues(orderBy ?? "-created_at", _issues);
|
||||||
|
|
||||||
return issues;
|
return { dataViewId: viewId, issueIds };
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchIssues = async (workspaceSlug: string, viewId: string, loadType: TLoader = "init-loader") => {
|
fetchIssues = async (workspaceSlug: string, viewId: string, loadType: TLoader = "init-loader") => {
|
||||||
try {
|
try {
|
||||||
this.loader = loadType;
|
this.loader = loadType;
|
||||||
|
|
||||||
const params = this.rootIssueStore?.workspaceIssuesFilter?.appliedFilters;
|
const params = this.rootIssueStore?.workspaceIssuesFilter?.getAppliedFilters(viewId);
|
||||||
const response = await this.workspaceService.getViewIssues(workspaceSlug, params);
|
const response = await this.workspaceService.getViewIssues(workspaceSlug, params);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
Loading…
Reference in New Issue
Block a user