forked from github/plane
refactor: views & filter (#490)
* fix: not saving filters on views detail page * refactor: using issues endpoint to get issues in views detail page feat: showing toast alert on saving view
This commit is contained in:
parent
9a97c97336
commit
a830808f9d
@ -34,7 +34,7 @@ import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "consta
|
|||||||
|
|
||||||
export const IssuesFilterView: React.FC = () => {
|
export const IssuesFilterView: React.FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId, viewId } = router.query;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
issueView,
|
issueView,
|
||||||
@ -102,13 +102,16 @@ export const IssuesFilterView: React.FC = () => {
|
|||||||
<MultiLevelDropdown
|
<MultiLevelDropdown
|
||||||
label="Filters"
|
label="Filters"
|
||||||
onSelect={(option) => {
|
onSelect={(option) => {
|
||||||
setFilters({
|
setFilters(
|
||||||
|
{
|
||||||
...filters,
|
...filters,
|
||||||
[option.key]: [
|
[option.key]: [
|
||||||
...((filters?.[option.key as keyof typeof filters] as any[]) ?? []),
|
...((filters?.[option.key as keyof typeof filters] as any[]) ?? []),
|
||||||
option.value,
|
option.value,
|
||||||
],
|
],
|
||||||
});
|
},
|
||||||
|
!Boolean(viewId)
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
direction="left"
|
direction="left"
|
||||||
options={[
|
options={[
|
||||||
|
@ -12,6 +12,7 @@ import stateService from "services/state.service";
|
|||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
// hooks
|
// hooks
|
||||||
|
import useToast from "hooks/use-toast";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
// components
|
// components
|
||||||
import { AllLists, AllBoards } from "components/core";
|
import { AllLists, AllBoards } from "components/core";
|
||||||
@ -81,6 +82,8 @@ export const IssuesView: React.FC<Props> = ({ type = "issue", openIssuesListModa
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
||||||
|
|
||||||
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
groupedByIssues,
|
groupedByIssues,
|
||||||
issueView,
|
issueView,
|
||||||
@ -443,9 +446,12 @@ export const IssuesView: React.FC<Props> = ({ type = "issue", openIssuesListModa
|
|||||||
<span
|
<span
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setFilters({
|
setFilters(
|
||||||
|
{
|
||||||
state: filters.state?.filter((s: any) => s !== stateId),
|
state: filters.state?.filter((s: any) => s !== stateId),
|
||||||
})
|
},
|
||||||
|
!Boolean(viewId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<XMarkIcon className="h-3 w-3" />
|
<XMarkIcon className="h-3 w-3" />
|
||||||
@ -474,9 +480,14 @@ export const IssuesView: React.FC<Props> = ({ type = "issue", openIssuesListModa
|
|||||||
<span
|
<span
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setFilters({
|
setFilters(
|
||||||
priority: filters.priority?.filter((p: any) => p !== priority),
|
{
|
||||||
})
|
priority: filters.priority?.filter(
|
||||||
|
(p: any) => p !== priority
|
||||||
|
),
|
||||||
|
},
|
||||||
|
!Boolean(viewId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<XMarkIcon className="h-3 w-3" />
|
<XMarkIcon className="h-3 w-3" />
|
||||||
@ -497,11 +508,14 @@ export const IssuesView: React.FC<Props> = ({ type = "issue", openIssuesListModa
|
|||||||
<span
|
<span
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setFilters({
|
setFilters(
|
||||||
|
{
|
||||||
assignees: filters.assignees?.filter(
|
assignees: filters.assignees?.filter(
|
||||||
(p: any) => p !== memberId
|
(p: any) => p !== memberId
|
||||||
),
|
),
|
||||||
})
|
},
|
||||||
|
!Boolean(viewId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<XMarkIcon className="h-3 w-3" />
|
<XMarkIcon className="h-3 w-3" />
|
||||||
@ -519,18 +533,24 @@ export const IssuesView: React.FC<Props> = ({ type = "issue", openIssuesListModa
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{Object.keys(filters).length > 0 &&
|
{Object.keys(filters).length > 0 && nullFilters.length !== Object.keys(filters).length && (
|
||||||
nullFilters.length !== Object.keys(filters).length &&
|
|
||||||
!viewId && (
|
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
|
if (viewId) {
|
||||||
|
setFilters({}, true);
|
||||||
|
setToastAlert({
|
||||||
|
title: "View updated",
|
||||||
|
message: "Your view has been updated",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
} else
|
||||||
setCreateViewModal({
|
setCreateViewModal({
|
||||||
query: filters,
|
query: filters,
|
||||||
})
|
});
|
||||||
}
|
}}
|
||||||
className="flex items-center gap-2 text-sm"
|
className="flex items-center gap-2 text-sm"
|
||||||
>
|
>
|
||||||
<PlusIcon className="h-4 w-4" />
|
{!viewId && <PlusIcon className="h-4 w-4" />}
|
||||||
Save view
|
Save view
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
)}
|
)}
|
||||||
|
@ -44,7 +44,7 @@ type ReducerActionType = {
|
|||||||
type ContextType = IssueViewProps & {
|
type ContextType = IssueViewProps & {
|
||||||
setGroupByProperty: (property: "state" | "priority" | "labels" | null) => void;
|
setGroupByProperty: (property: "state" | "priority" | "labels" | null) => void;
|
||||||
setOrderBy: (property: "created_at" | "updated_at" | "priority" | "sort_order") => void;
|
setOrderBy: (property: "created_at" | "updated_at" | "priority" | "sort_order") => void;
|
||||||
setFilters: (filters: Partial<IIssueFilterOptions>) => void;
|
setFilters: (filters: Partial<IIssueFilterOptions>, saveToServer?: boolean) => void;
|
||||||
resetFilterToDefault: () => void;
|
resetFilterToDefault: () => void;
|
||||||
setNewFilterDefaultView: () => void;
|
setNewFilterDefaultView: () => void;
|
||||||
setIssueViewToKanban: () => void;
|
setIssueViewToKanban: () => void;
|
||||||
@ -335,7 +335,7 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> =
|
|||||||
);
|
);
|
||||||
|
|
||||||
const setFilters = useCallback(
|
const setFilters = useCallback(
|
||||||
(property: Partial<IIssueFilterOptions>) => {
|
(property: Partial<IIssueFilterOptions>, saveToServer = true) => {
|
||||||
Object.keys(property).forEach((key) => {
|
Object.keys(property).forEach((key) => {
|
||||||
if (property[key as keyof typeof property]?.length === 0) {
|
if (property[key as keyof typeof property]?.length === 0) {
|
||||||
property[key as keyof typeof property] = null;
|
property[key as keyof typeof property] = null;
|
||||||
@ -380,13 +380,14 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> =
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, false);
|
}, false);
|
||||||
|
if (saveToServer)
|
||||||
sendFilterDataToServer(workspaceSlug as string, projectId as string, viewId as string, {
|
sendFilterDataToServer(workspaceSlug as string, projectId as string, viewId as string, {
|
||||||
query_data: {
|
query_data: {
|
||||||
...state.filters,
|
...state.filters,
|
||||||
...property,
|
...property,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else
|
} else if (saveToServer)
|
||||||
saveDataToServer(workspaceSlug as string, projectId as string, {
|
saveDataToServer(workspaceSlug as string, projectId as string, {
|
||||||
...state,
|
...state,
|
||||||
filters: {
|
filters: {
|
||||||
|
@ -38,7 +38,7 @@ const useIssuesView = () => {
|
|||||||
} = useContext(issueViewContext);
|
} = useContext(issueViewContext);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||||
|
|
||||||
const params: any = {
|
const params: any = {
|
||||||
order_by: orderBy,
|
order_by: orderBy,
|
||||||
@ -66,14 +66,6 @@ const useIssuesView = () => {
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: viewIssues } = useSWR(
|
|
||||||
workspaceSlug && projectId && viewId ? VIEW_ISSUES(viewId as string) : null,
|
|
||||||
workspaceSlug && projectId && viewId
|
|
||||||
? () =>
|
|
||||||
viewsService.getViewIssues(workspaceSlug as string, projectId as string, viewId as string)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: cycleIssues } = useSWR(
|
const { data: cycleIssues } = useSWR(
|
||||||
workspaceSlug && projectId && cycleId && params
|
workspaceSlug && projectId && cycleId && params
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params)
|
? CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params)
|
||||||
@ -109,11 +101,11 @@ const useIssuesView = () => {
|
|||||||
[key: string]: IIssue[];
|
[key: string]: IIssue[];
|
||||||
}
|
}
|
||||||
| undefined = useMemo(() => {
|
| undefined = useMemo(() => {
|
||||||
const issuesToGroup = viewIssues ?? cycleIssues ?? moduleIssues ?? projectIssues;
|
const issuesToGroup = cycleIssues ?? moduleIssues ?? projectIssues;
|
||||||
|
|
||||||
if (Array.isArray(issuesToGroup)) return { allIssues: issuesToGroup };
|
if (Array.isArray(issuesToGroup)) return { allIssues: issuesToGroup };
|
||||||
else return issuesToGroup;
|
else return issuesToGroup;
|
||||||
}, [projectIssues, cycleIssues, moduleIssues, viewIssues]);
|
}, [projectIssues, cycleIssues, moduleIssues]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
groupedByIssues,
|
groupedByIssues,
|
||||||
|
Loading…
Reference in New Issue
Block a user