[WEB-1319] fix: handled issue filters mutation and updated the useParams with useSearchParams (#4473)
* chore: updated issue filters in space * chore: persisting the query params even when we switch layouts --------- Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
parent
8ecc461fb1
commit
2bf2e98b00
37 changed files with 594 additions and 493 deletions
|
|
@ -1,11 +1,11 @@
|
|||
"use client";
|
||||
|
||||
// types
|
||||
import { issuePriorityFilter } from "@/constants/data";
|
||||
import { TIssuePriorityKey } from "types/issue";
|
||||
import { issuePriorityFilter } from "@/constants/issue";
|
||||
import { TIssueFilterPriority } from "@/types/issue";
|
||||
// constants
|
||||
|
||||
export const IssueBlockPriority = ({ priority }: { priority: TIssuePriorityKey | null }) => {
|
||||
export const IssueBlockPriority = ({ priority }: { priority: TIssueFilterPriority | null }) => {
|
||||
const priority_detail = priority != null ? issuePriorityFilter(priority) : null;
|
||||
|
||||
if (priority_detail === null) return <></>;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// ui
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// constants
|
||||
import { issueGroupFilter } from "@/constants/data";
|
||||
import { issueGroupFilter } from "@/constants/issue";
|
||||
|
||||
export const IssueBlockState = ({ state }: any) => {
|
||||
const stateGroup = issueGroupFilter(state.group);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ type IssueKanBanBlockProps = {
|
|||
|
||||
export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId, params, issue } = props;
|
||||
const { board, priorities, states, labels } = params;
|
||||
const { board, priority, states, labels } = params;
|
||||
// store
|
||||
const { project } = useProject();
|
||||
const { setPeekId } = useIssueDetails();
|
||||
|
|
@ -33,7 +33,7 @@ export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => {
|
|||
setPeekId(issue.id);
|
||||
const params: any = { board: board, peekId: issue.id };
|
||||
if (states && states.length > 0) params.states = states;
|
||||
if (priorities && priorities.length > 0) params.priorities = priorities;
|
||||
if (priority && priority.length > 0) params.priority = priority;
|
||||
if (labels && labels.length > 0) params.labels = labels;
|
||||
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||
// ui
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// constants
|
||||
import { issueGroupFilter } from "@/constants/data";
|
||||
import { issueGroupFilter } from "@/constants/issue";
|
||||
// mobx hook
|
||||
// import { useIssue } from "@/hooks/store";
|
||||
// interfaces
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ type IssueListBlockProps = {
|
|||
|
||||
export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issue } = props;
|
||||
const { board, states, priorities, labels } = useParams<any>();
|
||||
const { board, states, priority, labels } = useParams<any>();
|
||||
const searchParams = useSearchParams();
|
||||
// store
|
||||
const { project } = useProject();
|
||||
|
|
@ -33,7 +33,7 @@ export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
|
|||
setPeekId(issue.id);
|
||||
const params: any = { board: board, peekId: issue.id };
|
||||
if (states && states.length > 0) params.states = states;
|
||||
if (priorities && priorities.length > 0) params.priorities = priorities;
|
||||
if (priority && priority.length > 0) params.priority = priority;
|
||||
if (labels && labels.length > 0) params.labels = labels;
|
||||
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
||||
// router.push(`/${workspace_slug?.toString()}/${project_slug}?board=${board?.toString()}&peekId=${issue.id}`);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { observer } from "mobx-react-lite";
|
|||
// ui
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// constants
|
||||
import { issueGroupFilter } from "@/constants/data";
|
||||
import { issueGroupFilter } from "@/constants/issue";
|
||||
// mobx hook
|
||||
// import { useIssue } from "@/hooks/store";
|
||||
// types
|
||||
|
|
|
|||
|
|
@ -1,30 +1,34 @@
|
|||
"use client";
|
||||
|
||||
// icons
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { X } from "lucide-react";
|
||||
// types
|
||||
import { IIssueLabel, IIssueState, IIssueFilterOptions } from "@/types/issue";
|
||||
import { IIssueLabel, IIssueState, TFilters } from "@/types/issue";
|
||||
// components
|
||||
import { AppliedPriorityFilters } from "./priority";
|
||||
import { AppliedStateFilters } from "./state";
|
||||
|
||||
type Props = {
|
||||
appliedFilters: IIssueFilterOptions;
|
||||
appliedFilters: TFilters;
|
||||
handleRemoveAllFilters: () => void;
|
||||
handleRemoveFilter: (key: keyof IIssueFilterOptions, value: string | null) => void;
|
||||
handleRemoveFilter: (key: keyof TFilters, value: string | null) => void;
|
||||
labels?: IIssueLabel[] | undefined;
|
||||
states?: IIssueState[] | undefined;
|
||||
};
|
||||
|
||||
export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, " ");
|
||||
|
||||
export const AppliedFiltersList: React.FC<Props> = (props) => {
|
||||
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
||||
const { appliedFilters = {}, handleRemoveAllFilters, handleRemoveFilter, states } = props;
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-stretch gap-2 bg-custom-background-100">
|
||||
{Object.entries(appliedFilters).map(([key, value]) => {
|
||||
const filterKey = key as keyof IIssueFilterOptions;
|
||||
const filterKey = key as keyof TFilters;
|
||||
const filterValue = value as TFilters[keyof TFilters];
|
||||
|
||||
if (!value) return;
|
||||
if (!filterValue) return;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -34,7 +38,10 @@ export const AppliedFiltersList: React.FC<Props> = (props) => {
|
|||
<span className="text-xs text-custom-text-300">{replaceUnderscoreIfSnakeCase(filterKey)}</span>
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
{filterKey === "priority" && (
|
||||
<AppliedPriorityFilters handleRemove={(val) => handleRemoveFilter("priority", val)} values={value} />
|
||||
<AppliedPriorityFilters
|
||||
handleRemove={(val) => handleRemoveFilter("priority", val)}
|
||||
values={filterValue ?? []}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* {filterKey === "labels" && labels && (
|
||||
|
|
@ -49,7 +56,7 @@ export const AppliedFiltersList: React.FC<Props> = (props) => {
|
|||
<AppliedStateFilters
|
||||
handleRemove={(val) => handleRemoveFilter("state", val)}
|
||||
states={states}
|
||||
values={value}
|
||||
values={filterValue ?? []}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
@ -74,4 +81,4 @@ export const AppliedFiltersList: React.FC<Props> = (props) => {
|
|||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { X } from "lucide-react";
|
||||
// types
|
||||
import { IIssueLabel } from "types/issue";
|
||||
import { IIssueLabel } from "@/types/issue";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (val: string) => void;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { X } from "lucide-react";
|
||||
import { PriorityIcon } from "@plane/ui";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,33 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useCallback } from "react";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/navigation";
|
||||
// hooks
|
||||
import { useIssue, useProject, useIssueFilter } from "@/hooks/store";
|
||||
import { useIssue, useIssueFilter } from "@/hooks/store";
|
||||
// store
|
||||
import { IIssueFilterOptions } from "@/types/issue";
|
||||
import { TIssueQueryFilters } from "@/types/issue";
|
||||
// components
|
||||
import { AppliedFiltersList } from "./filters-list";
|
||||
|
||||
// TODO: fix component types
|
||||
export const IssueAppliedFilters: FC = observer((props: any) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = props;
|
||||
const { states, labels } = useIssue();
|
||||
const { activeLayout } = useProject();
|
||||
const { issueFilters, updateFilters } = useIssueFilter();
|
||||
type TIssueAppliedFilters = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const IssueAppliedFilters: FC<TIssueAppliedFilters> = observer((props) => {
|
||||
const router = useRouter();
|
||||
// props
|
||||
const { workspaceSlug, projectId } = props;
|
||||
// hooks
|
||||
const { issueFilters, initIssueFilters, updateIssueFilters } = useIssueFilter();
|
||||
const { states, labels } = useIssue();
|
||||
|
||||
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
||||
const userFilters = issueFilters?.filters || {};
|
||||
|
||||
const appliedFilters: any = {};
|
||||
|
||||
Object.entries(userFilters).forEach(([key, value]) => {
|
||||
if (!value) return;
|
||||
if (Array.isArray(value) && value.length === 0) return;
|
||||
|
|
@ -29,48 +35,50 @@ export const IssueAppliedFilters: FC = observer((props: any) => {
|
|||
});
|
||||
|
||||
const updateRouteParams = useCallback(
|
||||
(key: keyof IIssueFilterOptions | null, value: string[] | null, clearFields: boolean = false) => {
|
||||
const state = key === "state" ? value || [] : issueFilters?.filters?.state ?? [];
|
||||
const priority = key === "priority" ? value || [] : issueFilters?.filters?.priority ?? [];
|
||||
const labels = key === "labels" ? value || [] : issueFilters?.filters?.labels ?? [];
|
||||
(key: keyof TIssueQueryFilters, value: string[]) => {
|
||||
const state = key === "state" ? value : issueFilters?.filters?.state ?? [];
|
||||
const priority = key === "priority" ? value : issueFilters?.filters?.priority ?? [];
|
||||
const labels = key === "labels" ? value : issueFilters?.filters?.labels ?? [];
|
||||
|
||||
let params: any = { board: activeLayout || "list" };
|
||||
if (!clearFields) {
|
||||
if (priority.length > 0) params = { ...params, priorities: priority.join(",") };
|
||||
if (state.length > 0) params = { ...params, states: state.join(",") };
|
||||
if (labels.length > 0) params = { ...params, labels: labels.join(",") };
|
||||
}
|
||||
console.log("params", params);
|
||||
// TODO: fix this redirection
|
||||
// router.push({ pathname: `/${workspaceSlug}/${projectId}`, query: { ...params } }, undefined, { shallow: true });
|
||||
if (priority.length > 0) params = { ...params, priority: priority.join(",") };
|
||||
if (state.length > 0) params = { ...params, states: state.join(",") };
|
||||
if (labels.length > 0) params = { ...params, labels: labels.join(",") };
|
||||
params = new URLSearchParams(params).toString();
|
||||
|
||||
router.push(`/${workspaceSlug}/${projectId}?${params}`);
|
||||
},
|
||||
[workspaceSlug, projectId, activeLayout, issueFilters, router]
|
||||
);
|
||||
|
||||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||
if (!projectId) return;
|
||||
if (!value) {
|
||||
updateFilters(projectId, { [key]: null });
|
||||
return;
|
||||
}
|
||||
const handleFilters = useCallback(
|
||||
(key: keyof TIssueQueryFilters, value: string | null) => {
|
||||
if (!projectId) return;
|
||||
|
||||
let newValues = issueFilters?.filters?.[key] ?? [];
|
||||
newValues = newValues.filter((val) => val !== value);
|
||||
let newValues = cloneDeep(issueFilters?.filters?.[key]) ?? [];
|
||||
|
||||
updateFilters(projectId, { [key]: newValues });
|
||||
updateRouteParams(key, newValues);
|
||||
};
|
||||
if (value === null) newValues = [];
|
||||
else if (newValues.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
|
||||
updateIssueFilters(projectId, "filters", key, newValues);
|
||||
updateRouteParams(key, newValues);
|
||||
},
|
||||
[projectId, issueFilters, updateIssueFilters, updateRouteParams]
|
||||
);
|
||||
|
||||
const handleRemoveAllFilters = () => {
|
||||
if (!projectId) return;
|
||||
|
||||
const newFilters: IIssueFilterOptions = {};
|
||||
Object.keys(userFilters).forEach((key) => {
|
||||
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||
initIssueFilters(projectId, {
|
||||
display_filters: { layout: activeLayout || "list" },
|
||||
filters: {
|
||||
state: [],
|
||||
priority: [],
|
||||
labels: [],
|
||||
},
|
||||
});
|
||||
|
||||
updateFilters(projectId, { ...newFilters });
|
||||
updateRouteParams(null, null, true);
|
||||
router.push(`/${workspaceSlug}/${projectId}?${`board=${activeLayout || "list"}`}`);
|
||||
};
|
||||
|
||||
if (Object.keys(appliedFilters).length === 0) return null;
|
||||
|
|
@ -79,7 +87,7 @@ export const IssueAppliedFilters: FC = observer((props: any) => {
|
|||
<div className="border-b border-custom-border-200 p-5 py-3">
|
||||
<AppliedFiltersList
|
||||
appliedFilters={appliedFilters || {}}
|
||||
handleRemoveFilter={handleRemoveFilter as any}
|
||||
handleRemoveFilter={handleFilters as any}
|
||||
handleRemoveAllFilters={handleRemoveAllFilters}
|
||||
labels={labels ?? []}
|
||||
states={states ?? []}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { X } from "lucide-react";
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
// icons
|
||||
import { IIssueState } from "types/issue";
|
||||
// types
|
||||
import { IIssueState } from "@/types/issue";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (val: string) => void;
|
||||
|
|
@ -10,7 +12,7 @@ type Props = {
|
|||
values: string[];
|
||||
};
|
||||
|
||||
export const AppliedStateFilters: React.FC<Props> = (props) => {
|
||||
export const AppliedStateFilters: React.FC<Props> = observer((props) => {
|
||||
const { handleRemove, states, values } = props;
|
||||
|
||||
return (
|
||||
|
|
@ -36,4 +38,4 @@ export const AppliedStateFilters: React.FC<Props> = (props) => {
|
|||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import React, { Fragment, useState } from "react";
|
||||
import { Placement } from "@popperjs/core";
|
||||
import { usePopper } from "react-popper";
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
// lucide icons
|
||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
// lucide icons
|
||||
import { Check } from "lucide-react";
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import React, { useState } from "react";
|
||||
"use client";
|
||||
|
||||
// components
|
||||
// ui
|
||||
import React, { useState } from "react";
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "@/components/issues/filters/helpers";
|
||||
// types
|
||||
import { IIssueLabel } from "types/issue";
|
||||
import { FilterHeader, FilterOption } from "./helpers";
|
||||
import { IIssueLabel } from "@/types/issue";
|
||||
|
||||
const LabelIcons = ({ color }: { color: string }) => (
|
||||
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: color }} />
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// ui
|
||||
import { PriorityIcon } from "@plane/ui";
|
||||
// components
|
||||
import { issuePriorityFilters } from "@/constants/data";
|
||||
import { issuePriorityFilters } from "@/constants/issue";
|
||||
import { FilterHeader, FilterOption } from "./helpers";
|
||||
// constants
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useCallback } from "react";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||
// hooks
|
||||
import { useIssue, useIssueFilter, useProject } from "@/hooks/store";
|
||||
import { useIssue, useIssueFilter } from "@/hooks/store";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "@/types/issue";
|
||||
import { TIssueQueryFilters } from "@/types/issue";
|
||||
// components
|
||||
import { FiltersDropdown } from "./helpers/dropdown";
|
||||
import { FilterSelection } from "./selection";
|
||||
|
|
@ -17,48 +20,44 @@ type IssueFiltersDropdownProps = {
|
|||
};
|
||||
|
||||
export const IssueFiltersDropdown: FC<IssueFiltersDropdownProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId } = props;
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
// store hooks
|
||||
const { activeLayout } = useProject();
|
||||
const { workspaceSlug, projectId } = props;
|
||||
// hooks
|
||||
const { issueFilters, updateIssueFilters } = useIssueFilter();
|
||||
const { states, labels } = useIssue();
|
||||
const { issueFilters, updateFilters } = useIssueFilter();
|
||||
|
||||
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
||||
|
||||
const updateRouteParams = useCallback(
|
||||
(key: keyof IIssueFilterOptions, value: string[]) => {
|
||||
(key: keyof TIssueQueryFilters, value: string[]) => {
|
||||
const state = key === "state" ? value : issueFilters?.filters?.state ?? [];
|
||||
const priority = key === "priority" ? value : issueFilters?.filters?.priority ?? [];
|
||||
const labels = key === "labels" ? value : issueFilters?.filters?.labels ?? [];
|
||||
|
||||
let params: any = { board: activeLayout || "list" };
|
||||
if (priority.length > 0) params = { ...params, priorities: priority.join(",") };
|
||||
if (state.length > 0) params = { ...params, states: state.join(",") };
|
||||
if (priority.length > 0) params = { ...params, priority: priority.join(",") };
|
||||
if (state.length > 0) params = { ...params, state: state.join(",") };
|
||||
if (labels.length > 0) params = { ...params, labels: labels.join(",") };
|
||||
console.log("params", params);
|
||||
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
||||
params = new URLSearchParams(params).toString();
|
||||
|
||||
router.push(`/${workspaceSlug}/${projectId}?${params}`);
|
||||
},
|
||||
[workspaceSlug, projectId, activeLayout, issueFilters, router]
|
||||
);
|
||||
|
||||
const handleFilters = useCallback(
|
||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
||||
if (!projectId) return;
|
||||
const newValues = issueFilters?.filters?.[key] ?? [];
|
||||
(key: keyof TIssueQueryFilters, value: string) => {
|
||||
if (!projectId || !value) return;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((val) => {
|
||||
if (!newValues.includes(val)) newValues.push(val);
|
||||
});
|
||||
} else {
|
||||
if (issueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
else newValues.push(value);
|
||||
}
|
||||
const newValues = cloneDeep(issueFilters?.filters?.[key]) ?? [];
|
||||
|
||||
updateFilters(projectId, { [key]: newValues });
|
||||
if (newValues.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||
else newValues.push(value);
|
||||
|
||||
updateIssueFilters(projectId, "filters", key, newValues);
|
||||
updateRouteParams(key, newValues);
|
||||
},
|
||||
[projectId, issueFilters, updateFilters, updateRouteParams]
|
||||
[projectId, issueFilters, updateIssueFilters, updateRouteParams]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -67,7 +66,7 @@ export const IssueFiltersDropdown: FC<IssueFiltersDropdownProps> = observer((pro
|
|||
<FilterSelection
|
||||
filters={issueFilters?.filters ?? {}}
|
||||
handleFilters={handleFilters as any}
|
||||
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT.issues[activeLayout] : undefined}
|
||||
layoutDisplayFiltersOptions={activeLayout ? ISSUE_DISPLAY_FILTERS_BY_LAYOUT?.[activeLayout]?.filters : []}
|
||||
states={states ?? undefined}
|
||||
labels={labels ?? undefined}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Search, X } from "lucide-react";
|
||||
// types
|
||||
import { IIssueState, IIssueLabel, IIssueFilterOptions } from "@/types/issue";
|
||||
import { ILayoutDisplayFiltersOptions } from "@/types/issue-filters";
|
||||
import { IIssueState, IIssueLabel, IIssueFilterOptions, TIssueFilterKeys } from "@/types/issue";
|
||||
// components
|
||||
import { FilterPriority, FilterState } from "./";
|
||||
|
||||
type Props = {
|
||||
filters: IIssueFilterOptions;
|
||||
handleFilters: (key: keyof IIssueFilterOptions, value: string | string[]) => void;
|
||||
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions | undefined;
|
||||
layoutDisplayFiltersOptions: TIssueFilterKeys[];
|
||||
labels?: IIssueLabel[] | undefined;
|
||||
states?: IIssueState[] | undefined;
|
||||
};
|
||||
|
|
@ -20,7 +21,7 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
|
|||
|
||||
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
||||
|
||||
const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions?.filters.includes(filter);
|
||||
const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions.includes(filter);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
// components
|
||||
// ui
|
||||
import { Loader, StateGroupIcon } from "@plane/ui";
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "@/components/issues/filters/helpers";
|
||||
// types
|
||||
import { IIssueState } from "types/issue";
|
||||
import { FilterHeader, FilterOption } from "./helpers";
|
||||
import { IIssueState } from "@/types/issue";
|
||||
|
||||
type Props = {
|
||||
appliedFilters: string[] | null;
|
||||
|
|
|
|||
|
|
@ -3,49 +3,48 @@
|
|||
import { useEffect, FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Link from "next/link";
|
||||
import { useRouter, useParams, useSearchParams, usePathname } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
import { useRouter, useSearchParams, usePathname } from "next/navigation";
|
||||
// ui
|
||||
import { Avatar, Button } from "@plane/ui";
|
||||
// components
|
||||
import { IssueFiltersDropdown } from "@/components/issues/filters";
|
||||
import { NavbarIssueBoardView } from "@/components/issues/navbar/issue-board-view";
|
||||
import { NavbarTheme } from "@/components/issues/navbar/theme";
|
||||
// hooks
|
||||
import { useProject, useUser, useIssueFilter } from "@/hooks/store";
|
||||
import { useProject, useUser, useIssueFilter, useIssueDetails } from "@/hooks/store";
|
||||
// types
|
||||
import { TIssueBoardKeys } from "@/types/issue";
|
||||
// components
|
||||
import { NavbarIssueBoardView } from "./issue-board-view";
|
||||
import { NavbarTheme } from "./theme";
|
||||
import { TIssueLayout } from "@/types/issue";
|
||||
|
||||
export type NavbarControlsProps = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
projectSettings: any;
|
||||
};
|
||||
|
||||
export const NavbarControls: FC<NavbarControlsProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId, projectSettings } = props;
|
||||
const { views } = projectSettings;
|
||||
// props
|
||||
const { workspaceSlug, projectId } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { board, labels, states, priorities, peekId } = useParams<any>();
|
||||
const searchParams = useSearchParams();
|
||||
const pathName = usePathname();
|
||||
// store
|
||||
const { updateFilters } = useIssueFilter();
|
||||
const { settings, activeLayout, hydrate, setActiveLayout } = useProject();
|
||||
hydrate(projectSettings);
|
||||
|
||||
const { data: user, fetchCurrentUser } = useUser();
|
||||
|
||||
useSWR("CURRENT_USER", () => fetchCurrentUser(), { errorRetryCount: 2 });
|
||||
|
||||
console.log("user", user);
|
||||
const searchParams = useSearchParams();
|
||||
// query params
|
||||
const board = searchParams.get("board") || undefined;
|
||||
const labels = searchParams.get("labels") || undefined;
|
||||
const state = searchParams.get("state") || undefined;
|
||||
const priority = searchParams.get("priority") || undefined;
|
||||
const peekId = searchParams.get("peekId") || undefined;
|
||||
// hooks
|
||||
const { issueFilters, isIssueFiltersUpdated, initIssueFilters } = useIssueFilter();
|
||||
const { settings } = useProject();
|
||||
const { data: user } = useUser();
|
||||
const { setPeekId } = useIssueDetails();
|
||||
// derived values
|
||||
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (workspaceSlug && projectId && settings) {
|
||||
const viewsAcceptable: string[] = [];
|
||||
const currentBoard: TIssueBoardKeys | null = null;
|
||||
let currentBoard: TIssueLayout | null = null;
|
||||
|
||||
if (settings?.views?.list) viewsAcceptable.push("list");
|
||||
if (settings?.views?.kanban) viewsAcceptable.push("kanban");
|
||||
|
|
@ -53,59 +52,66 @@ export const NavbarControls: FC<NavbarControlsProps> = observer((props) => {
|
|||
if (settings?.views?.gantt) viewsAcceptable.push("gantt");
|
||||
if (settings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet");
|
||||
|
||||
// if (board) {
|
||||
// if (viewsAcceptable.includes(board.toString())) {
|
||||
// currentBoard = board.toString() as TIssueBoardKeys;
|
||||
// } else {
|
||||
// if (viewsAcceptable && viewsAcceptable.length > 0) {
|
||||
// currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// if (viewsAcceptable && viewsAcceptable.length > 0) {
|
||||
// currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
|
||||
// }
|
||||
// }
|
||||
if (board) {
|
||||
if (viewsAcceptable.includes(board.toString())) currentBoard = board.toString() as TIssueLayout;
|
||||
else {
|
||||
if (viewsAcceptable && viewsAcceptable.length > 0) currentBoard = viewsAcceptable[0] as TIssueLayout;
|
||||
}
|
||||
} else {
|
||||
if (viewsAcceptable && viewsAcceptable.length > 0) currentBoard = viewsAcceptable[0] as TIssueLayout;
|
||||
}
|
||||
|
||||
if (currentBoard) {
|
||||
if (activeLayout === null || activeLayout !== currentBoard) {
|
||||
let params: any = { board: currentBoard };
|
||||
if (peekId && peekId.length > 0) params = { ...params, peekId: peekId };
|
||||
if (priorities && priorities.length > 0) params = { ...params, priorities: priorities };
|
||||
if (states && states.length > 0) params = { ...params, states: states };
|
||||
if (labels && labels.length > 0) params = { ...params, labels: labels };
|
||||
console.log("params", params);
|
||||
let storeParams: any = {};
|
||||
if (priorities && priorities.length > 0) storeParams = { ...storeParams, priority: priorities.split(",") };
|
||||
if (states && states.length > 0) storeParams = { ...storeParams, state: states.split(",") };
|
||||
if (labels && labels.length > 0) storeParams = { ...storeParams, labels: labels.split(",") };
|
||||
if (activeLayout === undefined || activeLayout !== currentBoard) {
|
||||
let queryParams: any = { board: currentBoard };
|
||||
const params: any = { display_filters: { layout: currentBoard }, filters: {} };
|
||||
|
||||
if (storeParams) updateFilters(projectId, storeParams);
|
||||
setActiveLayout(currentBoard);
|
||||
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
||||
if (peekId && peekId.length > 0) {
|
||||
queryParams = { ...queryParams, peekId: peekId };
|
||||
setPeekId(peekId);
|
||||
}
|
||||
if (priority && priority.length > 0) {
|
||||
queryParams = { ...queryParams, priority: priority };
|
||||
params.filters = { ...params.filters, priority: priority.split(",") };
|
||||
}
|
||||
if (state && state.length > 0) {
|
||||
queryParams = { ...queryParams, state: state };
|
||||
params.filters = { ...params.filters, state: state.split(",") };
|
||||
}
|
||||
if (labels && labels.length > 0) {
|
||||
queryParams = { ...queryParams, labels: labels };
|
||||
params.filters = { ...params.filters, labels: labels.split(",") };
|
||||
}
|
||||
|
||||
if (!isIssueFiltersUpdated(params)) {
|
||||
initIssueFilters(projectId, params);
|
||||
queryParams = new URLSearchParams(queryParams).toString();
|
||||
router.push(`/${workspaceSlug}/${projectId}?${queryParams}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [
|
||||
board,
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
router,
|
||||
updateFilters,
|
||||
board,
|
||||
labels,
|
||||
states,
|
||||
priorities,
|
||||
state,
|
||||
priority,
|
||||
peekId,
|
||||
settings,
|
||||
activeLayout,
|
||||
setActiveLayout,
|
||||
searchParams,
|
||||
router,
|
||||
initIssueFilters,
|
||||
setPeekId,
|
||||
isIssueFiltersUpdated,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* issue views */}
|
||||
<div className="relative flex flex-shrink-0 items-center gap-1 transition-all delay-150 ease-in-out">
|
||||
<NavbarIssueBoardView layouts={views} />
|
||||
<NavbarIssueBoardView workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
</div>
|
||||
|
||||
{/* issue filters */}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,44 @@
|
|||
"use client";
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Briefcase } from "lucide-react";
|
||||
// components
|
||||
import { ProjectLogo } from "@/components/common";
|
||||
import { NavbarControls } from "./controls";
|
||||
import { NavbarControls } from "@/components/issues/navbar/controls";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store";
|
||||
|
||||
type IssueNavbarProps = {
|
||||
projectSettings: any;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
const IssueNavbar: FC<IssueNavbarProps> = (props) => {
|
||||
const { projectSettings, workspaceSlug, projectId } = props;
|
||||
const { project_details } = projectSettings;
|
||||
const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId } = props;
|
||||
// hooks
|
||||
const { project } = useProject();
|
||||
|
||||
return (
|
||||
<div className="relative flex justify-between w-full gap-4 px-5">
|
||||
{/* project detail */}
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
{project_details ? (
|
||||
{project ? (
|
||||
<span className="h-7 w-7 flex-shrink-0 grid place-items-center">
|
||||
<ProjectLogo logo={project_details.logo_props} className="text-lg" />
|
||||
<ProjectLogo logo={project.logo_props} className="text-lg" />
|
||||
</span>
|
||||
) : (
|
||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||
<Briefcase className="h-4 w-4" />
|
||||
</span>
|
||||
)}
|
||||
<div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium">
|
||||
{project_details?.name || `...`}
|
||||
</div>
|
||||
<div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium">{project?.name || `...`}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<NavbarControls workspaceSlug={workspaceSlug} projectId={projectId} projectSettings={projectSettings} />
|
||||
<NavbarControls workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default IssueNavbar;
|
||||
|
|
|
|||
|
|
@ -2,31 +2,53 @@
|
|||
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
// constants
|
||||
import { issueViews } from "@/constants/data";
|
||||
import { issueLayoutViews } from "@/constants/issue";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { useIssueFilter } from "@/hooks/store";
|
||||
// mobx
|
||||
import { TIssueBoardKeys } from "@/types/issue";
|
||||
import { TIssueLayout } from "@/types/issue";
|
||||
|
||||
type NavbarIssueBoardViewProps = {
|
||||
layouts: Record<TIssueBoardKeys, boolean>;
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const NavbarIssueBoardView: FC<NavbarIssueBoardViewProps> = observer((props) => {
|
||||
const { layouts } = props;
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
// query params
|
||||
const labels = searchParams.get("labels") || undefined;
|
||||
const state = searchParams.get("state") || undefined;
|
||||
const priority = searchParams.get("priority") || undefined;
|
||||
const peekId = searchParams.get("peekId") || undefined;
|
||||
// props
|
||||
const { workspaceSlug, projectId } = props;
|
||||
// hooks
|
||||
const { layoutOptions, issueFilters, updateIssueFilters } = useIssueFilter();
|
||||
|
||||
const { activeLayout, setActiveLayout } = useProject();
|
||||
// derived values
|
||||
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
||||
|
||||
const handleCurrentBoardView = (boardView: string) => {
|
||||
setActiveLayout(boardView as TIssueBoardKeys);
|
||||
const handleCurrentBoardView = (boardView: TIssueLayout) => {
|
||||
updateIssueFilters(projectId, "display_filters", "layout", boardView);
|
||||
|
||||
let queryParams: any = { board: boardView };
|
||||
if (peekId && peekId.length > 0) queryParams = { ...queryParams, peekId: peekId };
|
||||
if (priority && priority.length > 0) queryParams = { ...queryParams, priority: priority };
|
||||
if (state && state.length > 0) queryParams = { ...queryParams, state: state };
|
||||
if (labels && labels.length > 0) queryParams = { ...queryParams, labels: labels };
|
||||
queryParams = new URLSearchParams(queryParams).toString();
|
||||
router.push(`/${workspaceSlug}/${projectId}?${queryParams}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{layouts &&
|
||||
Object.keys(layouts).map((layoutKey: string) => {
|
||||
if (layouts[layoutKey as TIssueBoardKeys]) {
|
||||
{issueLayoutViews &&
|
||||
Object.keys(issueLayoutViews).map((key: string) => {
|
||||
const layoutKey = key as TIssueLayout;
|
||||
if (layoutOptions[layoutKey]) {
|
||||
return (
|
||||
<div
|
||||
key={layoutKey}
|
||||
|
|
@ -40,10 +62,10 @@ export const NavbarIssueBoardView: FC<NavbarIssueBoardViewProps> = observer((pro
|
|||
>
|
||||
<span
|
||||
className={`material-symbols-rounded text-[18px] ${
|
||||
issueViews[layoutKey]?.className ? issueViews[layoutKey]?.className : ``
|
||||
issueLayoutViews[layoutKey]?.className ? issueLayoutViews[layoutKey]?.className : ``
|
||||
}`}
|
||||
>
|
||||
{issueViews[layoutKey]?.icon}
|
||||
{issueLayoutViews[layoutKey]?.icon}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { StateGroupIcon } from "@plane/ui";
|
|||
// icons
|
||||
import { Icon } from "@/components/ui";
|
||||
// helpers
|
||||
import { issueGroupFilter, issuePriorityFilter } from "@/constants/data";
|
||||
import { issueGroupFilter, issuePriorityFilter } from "@/constants/issue";
|
||||
import { renderFullDate } from "@/helpers/date-time.helper";
|
||||
import { copyTextToClipboard, addSpaceIfCamelCase } from "@/helpers/string.helper";
|
||||
// types
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overv
|
|||
import { useIssue, useIssueDetails } from "@/hooks/store";
|
||||
|
||||
export const IssuePeekOverview: React.FC = observer((props: any) => {
|
||||
const { workspaceSlug, projectId, peekId, board, priorities, states, labels } = props;
|
||||
const { workspaceSlug, projectId, peekId, board, priority, states, labels } = props;
|
||||
// states
|
||||
const [isSidePeekOpen, setIsSidePeekOpen] = useState(false);
|
||||
const [isModalPeekOpen, setIsModalPeekOpen] = useState(false);
|
||||
|
|
@ -33,7 +33,7 @@ export const IssuePeekOverview: React.FC = observer((props: any) => {
|
|||
|
||||
const params: any = { board: board };
|
||||
if (states && states.length > 0) params.states = states;
|
||||
if (priorities && priorities.length > 0) params.priorities = priorities;
|
||||
if (priority && priority.length > 0) params.priority = priority;
|
||||
if (labels && labels.length > 0) params.labels = labels;
|
||||
// TODO: fix this redirection
|
||||
// router.push( encodeURI(`/${workspaceSlug?.toString()}/${projectId}`, ) { pathname: `/${workspaceSlug?.toString()}/${projectId}`, query: { ...params } });
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { FC, useEffect } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import Image from "next/image";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { IssueCalendarView } from "@/components/issues/board-views/calendar";
|
||||
|
|
@ -14,33 +14,44 @@ import { IssueSpreadsheetView } from "@/components/issues/board-views/spreadshee
|
|||
import { IssueAppliedFilters } from "@/components/issues/filters/applied-filters/root";
|
||||
import { IssuePeekOverview } from "@/components/issues/peek-overview";
|
||||
// mobx store
|
||||
import { useIssue, useUser, useProject, useIssueDetails } from "@/hooks/store";
|
||||
import { useIssue, useUser, useIssueDetails, useIssueFilter, useProject } from "@/hooks/store";
|
||||
// assets
|
||||
import SomethingWentWrongImage from "public/something-went-wrong.svg";
|
||||
|
||||
type ProjectDetailsViewProps = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
peekId: string;
|
||||
peekId: string | undefined;
|
||||
};
|
||||
|
||||
export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId, peekId } = props;
|
||||
// router
|
||||
const params = useParams();
|
||||
// store hooks
|
||||
const searchParams = useSearchParams();
|
||||
// query params
|
||||
const states = searchParams.get("states") || undefined;
|
||||
const priority = searchParams.get("priority") || undefined;
|
||||
const labels = searchParams.get("labels") || undefined;
|
||||
|
||||
const { workspaceSlug, projectId, peekId } = props;
|
||||
// hooks
|
||||
const { fetchProjectSettings } = useProject();
|
||||
const { issueFilters } = useIssueFilter();
|
||||
const { loader, issues, error } = useIssue();
|
||||
const { fetchPublicIssues } = useIssue();
|
||||
const { activeLayout } = useProject();
|
||||
// fetching public issues
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? "PROJECT_PUBLIC_ISSUES" : null,
|
||||
workspaceSlug && projectId ? () => fetchPublicIssues(workspaceSlug, projectId, params) : null
|
||||
);
|
||||
// store hooks
|
||||
const issueStore = useIssue();
|
||||
const issueDetailStore = useIssueDetails();
|
||||
const { data: currentUser, fetchCurrentUser } = useUser();
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? "WORKSPACE_PROJECT_SETTINGS" : null,
|
||||
workspaceSlug && projectId ? () => fetchProjectSettings(workspaceSlug, projectId) : null
|
||||
);
|
||||
useSWR(
|
||||
(workspaceSlug && projectId) || states || priority || labels ? "WORKSPACE_PROJECT_PUBLIC_ISSUES" : null,
|
||||
(workspaceSlug && projectId) || states || priority || labels
|
||||
? () => fetchPublicIssues(workspaceSlug, projectId, { states, priority, labels })
|
||||
: null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentUser) {
|
||||
fetchCurrentUser();
|
||||
|
|
@ -53,15 +64,18 @@ export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props)
|
|||
}
|
||||
}, [peekId, issueDetailStore, projectId, workspaceSlug]);
|
||||
|
||||
// derived values
|
||||
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full overflow-hidden">
|
||||
{workspaceSlug && <IssuePeekOverview />}
|
||||
|
||||
{issueStore?.loader && !issueStore.issues ? (
|
||||
{loader && !issues ? (
|
||||
<div className="py-10 text-center text-sm text-custom-text-100">Loading...</div>
|
||||
) : (
|
||||
<>
|
||||
{issueStore?.error ? (
|
||||
{error ? (
|
||||
<div className="grid h-full w-full place-items-center p-6">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto grid h-52 w-52 place-items-center rounded-full bg-custom-background-80">
|
||||
|
|
@ -77,7 +91,7 @@ export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props)
|
|||
activeLayout && (
|
||||
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
||||
{/* applied filters */}
|
||||
<IssueAppliedFilters />
|
||||
<IssueAppliedFilters workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||
|
||||
{activeLayout === "list" && (
|
||||
<div className="relative h-full w-full overflow-y-auto">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue