mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
[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
@ -2,25 +2,24 @@ import Image from "next/image";
|
|||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
import IssueNavbar from "@/components/issues/navbar";
|
import IssueNavbar from "@/components/issues/navbar";
|
||||||
// services
|
|
||||||
import ProjectService from "@/services/project.service";
|
|
||||||
// assets
|
// assets
|
||||||
import planeLogo from "public/plane-logo.svg";
|
import planeLogo from "public/plane-logo.svg";
|
||||||
|
|
||||||
const projectService = new ProjectService();
|
export default async function ProjectLayout({
|
||||||
|
children,
|
||||||
export default async function ProjectLayout({ children, params }: { children: React.ReactNode; params: any }) {
|
params,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
params: { workspace_slug: string; project_id: string };
|
||||||
|
}) {
|
||||||
const { workspace_slug, project_id } = params;
|
const { workspace_slug, project_id } = params;
|
||||||
const projectSettings = await projectService.getProjectSettings(workspace_slug, project_id).catch(() => null);
|
|
||||||
|
|
||||||
if (!projectSettings) {
|
if (!workspace_slug || !project_id) notFound();
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-screen min-h-[500px] w-screen flex-col overflow-hidden">
|
<div className="relative flex h-screen min-h-[500px] w-screen flex-col overflow-hidden">
|
||||||
<div className="relative flex h-[60px] flex-shrink-0 select-none items-center border-b border-custom-border-300 bg-custom-sidebar-background-100">
|
<div className="relative flex h-[60px] flex-shrink-0 select-none items-center border-b border-custom-border-300 bg-custom-sidebar-background-100">
|
||||||
<IssueNavbar projectSettings={projectSettings} workspaceSlug={workspace_slug} projectId={project_id} />
|
<IssueNavbar workspaceSlug={workspace_slug?.toString()} projectId={project_id?.toString()} />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
|
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
|
||||||
<a
|
<a
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
import { ProjectDetailsView } from "@/components/views";
|
import { ProjectDetailsView } from "@/components/views";
|
||||||
|
|
||||||
export default function WorkspaceProjectPage({ params }: { params: any }) {
|
export default function WorkspaceProjectPage({ params }: { params: { workspace_slug: any; project_id: any } }) {
|
||||||
const { workspace_slug, project_id, peekId } = params;
|
const { workspace_slug, project_id } = params;
|
||||||
|
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const peekId = searchParams.get("peekId") || undefined;
|
||||||
|
|
||||||
|
if (!workspace_slug || !project_id) return <></>;
|
||||||
|
|
||||||
return <ProjectDetailsView workspaceSlug={workspace_slug} projectId={project_id} peekId={peekId} />;
|
return <ProjectDetailsView workspaceSlug={workspace_slug} projectId={project_id} peekId={peekId} />;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
// types
|
// types
|
||||||
import { issuePriorityFilter } from "@/constants/data";
|
import { issuePriorityFilter } from "@/constants/issue";
|
||||||
import { TIssuePriorityKey } from "types/issue";
|
import { TIssueFilterPriority } from "@/types/issue";
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
export const IssueBlockPriority = ({ priority }: { priority: TIssuePriorityKey | null }) => {
|
export const IssueBlockPriority = ({ priority }: { priority: TIssueFilterPriority | null }) => {
|
||||||
const priority_detail = priority != null ? issuePriorityFilter(priority) : null;
|
const priority_detail = priority != null ? issuePriorityFilter(priority) : null;
|
||||||
|
|
||||||
if (priority_detail === null) return <></>;
|
if (priority_detail === null) return <></>;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// ui
|
// ui
|
||||||
import { StateGroupIcon } from "@plane/ui";
|
import { StateGroupIcon } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
import { issueGroupFilter } from "@/constants/data";
|
import { issueGroupFilter } from "@/constants/issue";
|
||||||
|
|
||||||
export const IssueBlockState = ({ state }: any) => {
|
export const IssueBlockState = ({ state }: any) => {
|
||||||
const stateGroup = issueGroupFilter(state.group);
|
const stateGroup = issueGroupFilter(state.group);
|
||||||
|
@ -21,7 +21,7 @@ type IssueKanBanBlockProps = {
|
|||||||
|
|
||||||
export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => {
|
export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, params, issue } = props;
|
const { workspaceSlug, projectId, params, issue } = props;
|
||||||
const { board, priorities, states, labels } = params;
|
const { board, priority, states, labels } = params;
|
||||||
// store
|
// store
|
||||||
const { project } = useProject();
|
const { project } = useProject();
|
||||||
const { setPeekId } = useIssueDetails();
|
const { setPeekId } = useIssueDetails();
|
||||||
@ -33,7 +33,7 @@ export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => {
|
|||||||
setPeekId(issue.id);
|
setPeekId(issue.id);
|
||||||
const params: any = { board: board, peekId: issue.id };
|
const params: any = { board: board, peekId: issue.id };
|
||||||
if (states && states.length > 0) params.states = states;
|
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;
|
if (labels && labels.length > 0) params.labels = labels;
|
||||||
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
// ui
|
// ui
|
||||||
import { StateGroupIcon } from "@plane/ui";
|
import { StateGroupIcon } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
import { issueGroupFilter } from "@/constants/data";
|
import { issueGroupFilter } from "@/constants/issue";
|
||||||
// mobx hook
|
// mobx hook
|
||||||
// import { useIssue } from "@/hooks/store";
|
// import { useIssue } from "@/hooks/store";
|
||||||
// interfaces
|
// interfaces
|
||||||
|
@ -21,7 +21,7 @@ type IssueListBlockProps = {
|
|||||||
|
|
||||||
export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
|
export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, issue } = props;
|
const { workspaceSlug, projectId, issue } = props;
|
||||||
const { board, states, priorities, labels } = useParams<any>();
|
const { board, states, priority, labels } = useParams<any>();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
// store
|
// store
|
||||||
const { project } = useProject();
|
const { project } = useProject();
|
||||||
@ -33,7 +33,7 @@ export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
|
|||||||
setPeekId(issue.id);
|
setPeekId(issue.id);
|
||||||
const params: any = { board: board, peekId: issue.id };
|
const params: any = { board: board, peekId: issue.id };
|
||||||
if (states && states.length > 0) params.states = states;
|
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;
|
if (labels && labels.length > 0) params.labels = labels;
|
||||||
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
||||||
// router.push(`/${workspace_slug?.toString()}/${project_slug}?board=${board?.toString()}&peekId=${issue.id}`);
|
// router.push(`/${workspace_slug?.toString()}/${project_slug}?board=${board?.toString()}&peekId=${issue.id}`);
|
||||||
|
@ -3,7 +3,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
// ui
|
// ui
|
||||||
import { StateGroupIcon } from "@plane/ui";
|
import { StateGroupIcon } from "@plane/ui";
|
||||||
// constants
|
// constants
|
||||||
import { issueGroupFilter } from "@/constants/data";
|
import { issueGroupFilter } from "@/constants/issue";
|
||||||
// mobx hook
|
// mobx hook
|
||||||
// import { useIssue } from "@/hooks/store";
|
// import { useIssue } from "@/hooks/store";
|
||||||
// types
|
// types
|
||||||
|
@ -1,30 +1,34 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { IIssueLabel, IIssueState, IIssueFilterOptions } from "@/types/issue";
|
import { IIssueLabel, IIssueState, TFilters } from "@/types/issue";
|
||||||
// components
|
// components
|
||||||
import { AppliedPriorityFilters } from "./priority";
|
import { AppliedPriorityFilters } from "./priority";
|
||||||
import { AppliedStateFilters } from "./state";
|
import { AppliedStateFilters } from "./state";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appliedFilters: IIssueFilterOptions;
|
appliedFilters: TFilters;
|
||||||
handleRemoveAllFilters: () => void;
|
handleRemoveAllFilters: () => void;
|
||||||
handleRemoveFilter: (key: keyof IIssueFilterOptions, value: string | null) => void;
|
handleRemoveFilter: (key: keyof TFilters, value: string | null) => void;
|
||||||
labels?: IIssueLabel[] | undefined;
|
labels?: IIssueLabel[] | undefined;
|
||||||
states?: IIssueState[] | undefined;
|
states?: IIssueState[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, " ");
|
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;
|
const { appliedFilters = {}, handleRemoveAllFilters, handleRemoveFilter, states } = props;
|
||||||
|
|
||||||
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">
|
||||||
{Object.entries(appliedFilters).map(([key, value]) => {
|
{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 (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -34,7 +38,10 @@ export const AppliedFiltersList: React.FC<Props> = (props) => {
|
|||||||
<span className="text-xs text-custom-text-300">{replaceUnderscoreIfSnakeCase(filterKey)}</span>
|
<span className="text-xs text-custom-text-300">{replaceUnderscoreIfSnakeCase(filterKey)}</span>
|
||||||
<div className="flex flex-wrap items-center gap-1">
|
<div className="flex flex-wrap items-center gap-1">
|
||||||
{filterKey === "priority" && (
|
{filterKey === "priority" && (
|
||||||
<AppliedPriorityFilters handleRemove={(val) => handleRemoveFilter("priority", val)} values={value} />
|
<AppliedPriorityFilters
|
||||||
|
handleRemove={(val) => handleRemoveFilter("priority", val)}
|
||||||
|
values={filterValue ?? []}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* {filterKey === "labels" && labels && (
|
{/* {filterKey === "labels" && labels && (
|
||||||
@ -49,7 +56,7 @@ export const AppliedFiltersList: React.FC<Props> = (props) => {
|
|||||||
<AppliedStateFilters
|
<AppliedStateFilters
|
||||||
handleRemove={(val) => handleRemoveFilter("state", val)}
|
handleRemove={(val) => handleRemoveFilter("state", val)}
|
||||||
states={states}
|
states={states}
|
||||||
values={value}
|
values={filterValue ?? []}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -74,4 +81,4 @@ export const AppliedFiltersList: React.FC<Props> = (props) => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { IIssueLabel } from "types/issue";
|
import { IIssueLabel } from "@/types/issue";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handleRemove: (val: string) => void;
|
handleRemove: (val: string) => void;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
import { PriorityIcon } from "@plane/ui";
|
import { PriorityIcon } from "@plane/ui";
|
||||||
|
|
||||||
|
@ -1,27 +1,33 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, useCallback } from "react";
|
import { FC, useCallback } from "react";
|
||||||
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssue, useProject, useIssueFilter } from "@/hooks/store";
|
import { useIssue, useIssueFilter } from "@/hooks/store";
|
||||||
// store
|
// store
|
||||||
import { IIssueFilterOptions } from "@/types/issue";
|
import { TIssueQueryFilters } from "@/types/issue";
|
||||||
// components
|
// components
|
||||||
import { AppliedFiltersList } from "./filters-list";
|
import { AppliedFiltersList } from "./filters-list";
|
||||||
|
|
||||||
// TODO: fix component types
|
type TIssueAppliedFilters = {
|
||||||
export const IssueAppliedFilters: FC = observer((props: any) => {
|
workspaceSlug: string;
|
||||||
const router = useRouter();
|
projectId: string;
|
||||||
const { workspaceSlug, projectId } = props;
|
};
|
||||||
const { states, labels } = useIssue();
|
|
||||||
const { activeLayout } = useProject();
|
|
||||||
const { issueFilters, updateFilters } = useIssueFilter();
|
|
||||||
|
|
||||||
|
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 userFilters = issueFilters?.filters || {};
|
||||||
|
|
||||||
const appliedFilters: any = {};
|
const appliedFilters: any = {};
|
||||||
|
|
||||||
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;
|
||||||
@ -29,48 +35,50 @@ export const IssueAppliedFilters: FC = observer((props: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updateRouteParams = useCallback(
|
const updateRouteParams = useCallback(
|
||||||
(key: keyof IIssueFilterOptions | null, value: string[] | null, clearFields: boolean = false) => {
|
(key: keyof TIssueQueryFilters, value: string[]) => {
|
||||||
const state = key === "state" ? value || [] : issueFilters?.filters?.state ?? [];
|
const state = key === "state" ? value : issueFilters?.filters?.state ?? [];
|
||||||
const priority = key === "priority" ? value || [] : issueFilters?.filters?.priority ?? [];
|
const priority = key === "priority" ? value : issueFilters?.filters?.priority ?? [];
|
||||||
const labels = key === "labels" ? value || [] : issueFilters?.filters?.labels ?? [];
|
const labels = key === "labels" ? value : issueFilters?.filters?.labels ?? [];
|
||||||
|
|
||||||
let params: any = { board: activeLayout || "list" };
|
let params: any = { board: activeLayout || "list" };
|
||||||
if (!clearFields) {
|
if (priority.length > 0) params = { ...params, priority: priority.join(",") };
|
||||||
if (priority.length > 0) params = { ...params, priorities: priority.join(",") };
|
if (state.length > 0) params = { ...params, states: state.join(",") };
|
||||||
if (state.length > 0) params = { ...params, states: state.join(",") };
|
if (labels.length > 0) params = { ...params, labels: labels.join(",") };
|
||||||
if (labels.length > 0) params = { ...params, labels: labels.join(",") };
|
params = new URLSearchParams(params).toString();
|
||||||
}
|
|
||||||
console.log("params", params);
|
router.push(`/${workspaceSlug}/${projectId}?${params}`);
|
||||||
// TODO: fix this redirection
|
|
||||||
// router.push({ pathname: `/${workspaceSlug}/${projectId}`, query: { ...params } }, undefined, { shallow: true });
|
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, activeLayout, issueFilters, router]
|
[workspaceSlug, projectId, activeLayout, issueFilters, router]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
const handleFilters = useCallback(
|
||||||
if (!projectId) return;
|
(key: keyof TIssueQueryFilters, value: string | null) => {
|
||||||
if (!value) {
|
if (!projectId) return;
|
||||||
updateFilters(projectId, { [key]: null });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let newValues = issueFilters?.filters?.[key] ?? [];
|
let newValues = cloneDeep(issueFilters?.filters?.[key]) ?? [];
|
||||||
newValues = newValues.filter((val) => val !== value);
|
|
||||||
|
|
||||||
updateFilters(projectId, { [key]: newValues });
|
if (value === null) newValues = [];
|
||||||
updateRouteParams(key, 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 = () => {
|
const handleRemoveAllFilters = () => {
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
|
|
||||||
const newFilters: IIssueFilterOptions = {};
|
initIssueFilters(projectId, {
|
||||||
Object.keys(userFilters).forEach((key) => {
|
display_filters: { layout: activeLayout || "list" },
|
||||||
newFilters[key as keyof IIssueFilterOptions] = null;
|
filters: {
|
||||||
|
state: [],
|
||||||
|
priority: [],
|
||||||
|
labels: [],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
updateFilters(projectId, { ...newFilters });
|
router.push(`/${workspaceSlug}/${projectId}?${`board=${activeLayout || "list"}`}`);
|
||||||
updateRouteParams(null, null, true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Object.keys(appliedFilters).length === 0) return null;
|
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">
|
<div className="border-b border-custom-border-200 p-5 py-3">
|
||||||
<AppliedFiltersList
|
<AppliedFiltersList
|
||||||
appliedFilters={appliedFilters || {}}
|
appliedFilters={appliedFilters || {}}
|
||||||
handleRemoveFilter={handleRemoveFilter as any}
|
handleRemoveFilter={handleFilters as any}
|
||||||
handleRemoveAllFilters={handleRemoveAllFilters}
|
handleRemoveAllFilters={handleRemoveAllFilters}
|
||||||
labels={labels ?? []}
|
labels={labels ?? []}
|
||||||
states={states ?? []}
|
states={states ?? []}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
import { StateGroupIcon } from "@plane/ui";
|
import { StateGroupIcon } from "@plane/ui";
|
||||||
// icons
|
|
||||||
import { IIssueState } from "types/issue";
|
|
||||||
// types
|
// types
|
||||||
|
import { IIssueState } from "@/types/issue";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handleRemove: (val: string) => void;
|
handleRemove: (val: string) => void;
|
||||||
@ -10,7 +12,7 @@ type Props = {
|
|||||||
values: string[];
|
values: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AppliedStateFilters: React.FC<Props> = (props) => {
|
export const AppliedStateFilters: React.FC<Props> = observer((props) => {
|
||||||
const { handleRemove, states, values } = props;
|
const { handleRemove, states, values } = props;
|
||||||
|
|
||||||
return (
|
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 React, { Fragment, useState } from "react";
|
||||||
import { Placement } from "@popperjs/core";
|
import { Placement } from "@popperjs/core";
|
||||||
import { usePopper } from "react-popper";
|
import { usePopper } from "react-popper";
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
// lucide icons
|
// lucide icons
|
||||||
import { ChevronDown, ChevronUp } from "lucide-react";
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
// lucide icons
|
// lucide icons
|
||||||
import { Check } from "lucide-react";
|
import { Check } from "lucide-react";
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React, { useState } from "react";
|
"use client";
|
||||||
|
|
||||||
// components
|
import React, { useState } from "react";
|
||||||
// ui
|
|
||||||
import { Loader } from "@plane/ui";
|
import { Loader } from "@plane/ui";
|
||||||
|
// components
|
||||||
|
import { FilterHeader, FilterOption } from "@/components/issues/filters/helpers";
|
||||||
// types
|
// types
|
||||||
import { IIssueLabel } from "types/issue";
|
import { IIssueLabel } from "@/types/issue";
|
||||||
import { FilterHeader, FilterOption } from "./helpers";
|
|
||||||
|
|
||||||
const LabelIcons = ({ color }: { color: string }) => (
|
const LabelIcons = ({ color }: { color: string }) => (
|
||||||
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: color }} />
|
<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 React, { useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// ui
|
// ui
|
||||||
import { PriorityIcon } from "@plane/ui";
|
import { PriorityIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { issuePriorityFilters } from "@/constants/data";
|
import { issuePriorityFilters } from "@/constants/issue";
|
||||||
import { FilterHeader, FilterOption } from "./helpers";
|
import { FilterHeader, FilterOption } from "./helpers";
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { FC, useCallback } from "react";
|
import { FC, useCallback } from "react";
|
||||||
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssue, useIssueFilter, useProject } from "@/hooks/store";
|
import { useIssue, useIssueFilter } from "@/hooks/store";
|
||||||
// types
|
// types
|
||||||
import { IIssueFilterOptions } from "@/types/issue";
|
import { TIssueQueryFilters } from "@/types/issue";
|
||||||
// components
|
// components
|
||||||
import { FiltersDropdown } from "./helpers/dropdown";
|
import { FiltersDropdown } from "./helpers/dropdown";
|
||||||
import { FilterSelection } from "./selection";
|
import { FilterSelection } from "./selection";
|
||||||
@ -17,48 +20,44 @@ type IssueFiltersDropdownProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const IssueFiltersDropdown: FC<IssueFiltersDropdownProps> = observer((props) => {
|
export const IssueFiltersDropdown: FC<IssueFiltersDropdownProps> = observer((props) => {
|
||||||
const { workspaceSlug, projectId } = props;
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// store hooks
|
const { workspaceSlug, projectId } = props;
|
||||||
const { activeLayout } = useProject();
|
// hooks
|
||||||
|
const { issueFilters, updateIssueFilters } = useIssueFilter();
|
||||||
const { states, labels } = useIssue();
|
const { states, labels } = useIssue();
|
||||||
const { issueFilters, updateFilters } = useIssueFilter();
|
|
||||||
|
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
||||||
|
|
||||||
const updateRouteParams = useCallback(
|
const updateRouteParams = useCallback(
|
||||||
(key: keyof IIssueFilterOptions, value: string[]) => {
|
(key: keyof TIssueQueryFilters, value: string[]) => {
|
||||||
const state = key === "state" ? value : issueFilters?.filters?.state ?? [];
|
const state = key === "state" ? value : issueFilters?.filters?.state ?? [];
|
||||||
const priority = key === "priority" ? value : issueFilters?.filters?.priority ?? [];
|
const priority = key === "priority" ? value : issueFilters?.filters?.priority ?? [];
|
||||||
const labels = key === "labels" ? value : issueFilters?.filters?.labels ?? [];
|
const labels = key === "labels" ? value : issueFilters?.filters?.labels ?? [];
|
||||||
|
|
||||||
let params: any = { board: activeLayout || "list" };
|
let params: any = { board: activeLayout || "list" };
|
||||||
if (priority.length > 0) params = { ...params, priorities: priority.join(",") };
|
if (priority.length > 0) params = { ...params, priority: priority.join(",") };
|
||||||
if (state.length > 0) params = { ...params, states: state.join(",") };
|
if (state.length > 0) params = { ...params, state: state.join(",") };
|
||||||
if (labels.length > 0) params = { ...params, labels: labels.join(",") };
|
if (labels.length > 0) params = { ...params, labels: labels.join(",") };
|
||||||
console.log("params", params);
|
params = new URLSearchParams(params).toString();
|
||||||
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
|
||||||
|
router.push(`/${workspaceSlug}/${projectId}?${params}`);
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, activeLayout, issueFilters, router]
|
[workspaceSlug, projectId, activeLayout, issueFilters, router]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFilters = useCallback(
|
const handleFilters = useCallback(
|
||||||
(key: keyof IIssueFilterOptions, value: string | string[]) => {
|
(key: keyof TIssueQueryFilters, value: string) => {
|
||||||
if (!projectId) return;
|
if (!projectId || !value) return;
|
||||||
const newValues = issueFilters?.filters?.[key] ?? [];
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
const newValues = cloneDeep(issueFilters?.filters?.[key]) ?? [];
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
updateRouteParams(key, newValues);
|
||||||
},
|
},
|
||||||
[projectId, issueFilters, updateFilters, updateRouteParams]
|
[projectId, issueFilters, updateIssueFilters, updateRouteParams]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -67,7 +66,7 @@ export const IssueFiltersDropdown: FC<IssueFiltersDropdownProps> = observer((pro
|
|||||||
<FilterSelection
|
<FilterSelection
|
||||||
filters={issueFilters?.filters ?? {}}
|
filters={issueFilters?.filters ?? {}}
|
||||||
handleFilters={handleFilters as any}
|
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}
|
states={states ?? undefined}
|
||||||
labels={labels ?? undefined}
|
labels={labels ?? undefined}
|
||||||
/>
|
/>
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Search, X } from "lucide-react";
|
import { Search, X } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { IIssueState, IIssueLabel, IIssueFilterOptions } from "@/types/issue";
|
import { IIssueState, IIssueLabel, IIssueFilterOptions, TIssueFilterKeys } from "@/types/issue";
|
||||||
import { ILayoutDisplayFiltersOptions } from "@/types/issue-filters";
|
|
||||||
// components
|
// components
|
||||||
import { FilterPriority, FilterState } from "./";
|
import { FilterPriority, FilterState } from "./";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
filters: IIssueFilterOptions;
|
filters: IIssueFilterOptions;
|
||||||
handleFilters: (key: keyof IIssueFilterOptions, value: string | string[]) => void;
|
handleFilters: (key: keyof IIssueFilterOptions, value: string | string[]) => void;
|
||||||
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions | undefined;
|
layoutDisplayFiltersOptions: TIssueFilterKeys[];
|
||||||
labels?: IIssueLabel[] | undefined;
|
labels?: IIssueLabel[] | undefined;
|
||||||
states?: IIssueState[] | undefined;
|
states?: IIssueState[] | undefined;
|
||||||
};
|
};
|
||||||
@ -20,7 +21,7 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
||||||
|
|
||||||
const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions?.filters.includes(filter);
|
const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions.includes(filter);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
<div className="flex h-full w-full flex-col overflow-hidden">
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
// components
|
|
||||||
// ui
|
|
||||||
import { Loader, StateGroupIcon } from "@plane/ui";
|
import { Loader, StateGroupIcon } from "@plane/ui";
|
||||||
|
// components
|
||||||
|
import { FilterHeader, FilterOption } from "@/components/issues/filters/helpers";
|
||||||
// types
|
// types
|
||||||
import { IIssueState } from "types/issue";
|
import { IIssueState } from "@/types/issue";
|
||||||
import { FilterHeader, FilterOption } from "./helpers";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appliedFilters: string[] | null;
|
appliedFilters: string[] | null;
|
||||||
|
@ -3,49 +3,48 @@
|
|||||||
import { useEffect, FC } from "react";
|
import { useEffect, FC } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter, useParams, useSearchParams, usePathname } from "next/navigation";
|
import { useRouter, useSearchParams, usePathname } from "next/navigation";
|
||||||
import useSWR from "swr";
|
|
||||||
// ui
|
// ui
|
||||||
import { Avatar, Button } from "@plane/ui";
|
import { Avatar, Button } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { IssueFiltersDropdown } from "@/components/issues/filters";
|
import { IssueFiltersDropdown } from "@/components/issues/filters";
|
||||||
|
import { NavbarIssueBoardView } from "@/components/issues/navbar/issue-board-view";
|
||||||
|
import { NavbarTheme } from "@/components/issues/navbar/theme";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject, useUser, useIssueFilter } from "@/hooks/store";
|
import { useProject, useUser, useIssueFilter, useIssueDetails } from "@/hooks/store";
|
||||||
// types
|
// types
|
||||||
import { TIssueBoardKeys } from "@/types/issue";
|
import { TIssueLayout } from "@/types/issue";
|
||||||
// components
|
|
||||||
import { NavbarIssueBoardView } from "./issue-board-view";
|
|
||||||
import { NavbarTheme } from "./theme";
|
|
||||||
|
|
||||||
export type NavbarControlsProps = {
|
export type NavbarControlsProps = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
projectSettings: any;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NavbarControls: FC<NavbarControlsProps> = observer((props) => {
|
export const NavbarControls: FC<NavbarControlsProps> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, projectSettings } = props;
|
// props
|
||||||
const { views } = projectSettings;
|
const { workspaceSlug, projectId } = props;
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { board, labels, states, priorities, peekId } = useParams<any>();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
const pathName = usePathname();
|
const pathName = usePathname();
|
||||||
// store
|
const searchParams = useSearchParams();
|
||||||
const { updateFilters } = useIssueFilter();
|
// query params
|
||||||
const { settings, activeLayout, hydrate, setActiveLayout } = useProject();
|
const board = searchParams.get("board") || undefined;
|
||||||
hydrate(projectSettings);
|
const labels = searchParams.get("labels") || undefined;
|
||||||
|
const state = searchParams.get("state") || undefined;
|
||||||
const { data: user, fetchCurrentUser } = useUser();
|
const priority = searchParams.get("priority") || undefined;
|
||||||
|
const peekId = searchParams.get("peekId") || undefined;
|
||||||
useSWR("CURRENT_USER", () => fetchCurrentUser(), { errorRetryCount: 2 });
|
// hooks
|
||||||
|
const { issueFilters, isIssueFiltersUpdated, initIssueFilters } = useIssueFilter();
|
||||||
console.log("user", user);
|
const { settings } = useProject();
|
||||||
|
const { data: user } = useUser();
|
||||||
|
const { setPeekId } = useIssueDetails();
|
||||||
|
// derived values
|
||||||
|
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspaceSlug && projectId && settings) {
|
if (workspaceSlug && projectId && settings) {
|
||||||
const viewsAcceptable: string[] = [];
|
const viewsAcceptable: string[] = [];
|
||||||
const currentBoard: TIssueBoardKeys | null = null;
|
let currentBoard: TIssueLayout | null = null;
|
||||||
|
|
||||||
if (settings?.views?.list) viewsAcceptable.push("list");
|
if (settings?.views?.list) viewsAcceptable.push("list");
|
||||||
if (settings?.views?.kanban) viewsAcceptable.push("kanban");
|
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?.gantt) viewsAcceptable.push("gantt");
|
||||||
if (settings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet");
|
if (settings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet");
|
||||||
|
|
||||||
// if (board) {
|
if (board) {
|
||||||
// if (viewsAcceptable.includes(board.toString())) {
|
if (viewsAcceptable.includes(board.toString())) currentBoard = board.toString() as TIssueLayout;
|
||||||
// currentBoard = board.toString() as TIssueBoardKeys;
|
else {
|
||||||
// } else {
|
if (viewsAcceptable && viewsAcceptable.length > 0) currentBoard = viewsAcceptable[0] as TIssueLayout;
|
||||||
// if (viewsAcceptable && viewsAcceptable.length > 0) {
|
}
|
||||||
// currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
|
} else {
|
||||||
// }
|
if (viewsAcceptable && viewsAcceptable.length > 0) currentBoard = viewsAcceptable[0] as TIssueLayout;
|
||||||
// }
|
}
|
||||||
// } else {
|
|
||||||
// if (viewsAcceptable && viewsAcceptable.length > 0) {
|
|
||||||
// currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (currentBoard) {
|
if (currentBoard) {
|
||||||
if (activeLayout === null || activeLayout !== currentBoard) {
|
if (activeLayout === undefined || activeLayout !== currentBoard) {
|
||||||
let params: any = { board: currentBoard };
|
let queryParams: any = { board: currentBoard };
|
||||||
if (peekId && peekId.length > 0) params = { ...params, peekId: peekId };
|
const params: any = { display_filters: { layout: currentBoard }, filters: {} };
|
||||||
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 (storeParams) updateFilters(projectId, storeParams);
|
if (peekId && peekId.length > 0) {
|
||||||
setActiveLayout(currentBoard);
|
queryParams = { ...queryParams, peekId: peekId };
|
||||||
router.push(`/${workspaceSlug}/${projectId}?${searchParams}`);
|
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,
|
workspaceSlug,
|
||||||
projectId,
|
projectId,
|
||||||
router,
|
board,
|
||||||
updateFilters,
|
|
||||||
labels,
|
labels,
|
||||||
states,
|
state,
|
||||||
priorities,
|
priority,
|
||||||
peekId,
|
peekId,
|
||||||
settings,
|
settings,
|
||||||
activeLayout,
|
activeLayout,
|
||||||
setActiveLayout,
|
router,
|
||||||
searchParams,
|
initIssueFilters,
|
||||||
|
setPeekId,
|
||||||
|
isIssueFiltersUpdated,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* issue views */}
|
{/* issue views */}
|
||||||
<div className="relative flex flex-shrink-0 items-center gap-1 transition-all delay-150 ease-in-out">
|
<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>
|
</div>
|
||||||
|
|
||||||
{/* issue filters */}
|
{/* issue filters */}
|
||||||
|
@ -1,43 +1,44 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { Briefcase } from "lucide-react";
|
import { Briefcase } from "lucide-react";
|
||||||
// components
|
// components
|
||||||
import { ProjectLogo } from "@/components/common";
|
import { ProjectLogo } from "@/components/common";
|
||||||
import { NavbarControls } from "./controls";
|
import { NavbarControls } from "@/components/issues/navbar/controls";
|
||||||
|
// hooks
|
||||||
|
import { useProject } from "@/hooks/store";
|
||||||
|
|
||||||
type IssueNavbarProps = {
|
type IssueNavbarProps = {
|
||||||
projectSettings: any;
|
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const IssueNavbar: FC<IssueNavbarProps> = (props) => {
|
const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
|
||||||
const { projectSettings, workspaceSlug, projectId } = props;
|
const { workspaceSlug, projectId } = props;
|
||||||
const { project_details } = projectSettings;
|
// hooks
|
||||||
|
const { project } = useProject();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex justify-between w-full gap-4 px-5">
|
<div className="relative flex justify-between w-full gap-4 px-5">
|
||||||
{/* project detail */}
|
{/* project detail */}
|
||||||
<div className="flex flex-shrink-0 items-center gap-2">
|
<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">
|
<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>
|
||||||
) : (
|
) : (
|
||||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||||
<Briefcase className="h-4 w-4" />
|
<Briefcase className="h-4 w-4" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium">
|
<div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium">{project?.name || `...`}</div>
|
||||||
{project_details?.name || `...`}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-shrink-0 items-center gap-2">
|
<div className="flex flex-shrink-0 items-center gap-2">
|
||||||
<NavbarControls workspaceSlug={workspaceSlug} projectId={projectId} projectSettings={projectSettings} />
|
<NavbarControls workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default IssueNavbar;
|
export default IssueNavbar;
|
||||||
|
@ -2,31 +2,53 @@
|
|||||||
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
// constants
|
// constants
|
||||||
import { issueViews } from "@/constants/data";
|
import { issueLayoutViews } from "@/constants/issue";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProject } from "@/hooks/store";
|
import { useIssueFilter } from "@/hooks/store";
|
||||||
// mobx
|
// mobx
|
||||||
import { TIssueBoardKeys } from "@/types/issue";
|
import { TIssueLayout } from "@/types/issue";
|
||||||
|
|
||||||
type NavbarIssueBoardViewProps = {
|
type NavbarIssueBoardViewProps = {
|
||||||
layouts: Record<TIssueBoardKeys, boolean>;
|
workspaceSlug: string;
|
||||||
|
projectId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NavbarIssueBoardView: FC<NavbarIssueBoardViewProps> = observer((props) => {
|
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) => {
|
const handleCurrentBoardView = (boardView: TIssueLayout) => {
|
||||||
setActiveLayout(boardView as TIssueBoardKeys);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{layouts &&
|
{issueLayoutViews &&
|
||||||
Object.keys(layouts).map((layoutKey: string) => {
|
Object.keys(issueLayoutViews).map((key: string) => {
|
||||||
if (layouts[layoutKey as TIssueBoardKeys]) {
|
const layoutKey = key as TIssueLayout;
|
||||||
|
if (layoutOptions[layoutKey]) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={layoutKey}
|
key={layoutKey}
|
||||||
@ -40,10 +62,10 @@ export const NavbarIssueBoardView: FC<NavbarIssueBoardViewProps> = observer((pro
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`material-symbols-rounded text-[18px] ${
|
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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,7 @@ import { StateGroupIcon } from "@plane/ui";
|
|||||||
// icons
|
// icons
|
||||||
import { Icon } from "@/components/ui";
|
import { Icon } from "@/components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { issueGroupFilter, issuePriorityFilter } from "@/constants/data";
|
import { issueGroupFilter, issuePriorityFilter } from "@/constants/issue";
|
||||||
import { renderFullDate } from "@/helpers/date-time.helper";
|
import { renderFullDate } from "@/helpers/date-time.helper";
|
||||||
import { copyTextToClipboard, addSpaceIfCamelCase } from "@/helpers/string.helper";
|
import { copyTextToClipboard, addSpaceIfCamelCase } from "@/helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
|
@ -10,7 +10,7 @@ import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overv
|
|||||||
import { useIssue, useIssueDetails } from "@/hooks/store";
|
import { useIssue, useIssueDetails } from "@/hooks/store";
|
||||||
|
|
||||||
export const IssuePeekOverview: React.FC = observer((props: any) => {
|
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
|
// states
|
||||||
const [isSidePeekOpen, setIsSidePeekOpen] = useState(false);
|
const [isSidePeekOpen, setIsSidePeekOpen] = useState(false);
|
||||||
const [isModalPeekOpen, setIsModalPeekOpen] = useState(false);
|
const [isModalPeekOpen, setIsModalPeekOpen] = useState(false);
|
||||||
@ -33,7 +33,7 @@ export const IssuePeekOverview: React.FC = observer((props: any) => {
|
|||||||
|
|
||||||
const params: any = { board: board };
|
const params: any = { board: board };
|
||||||
if (states && states.length > 0) params.states = states;
|
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;
|
if (labels && labels.length > 0) params.labels = labels;
|
||||||
// TODO: fix this redirection
|
// TODO: fix this redirection
|
||||||
// router.push( encodeURI(`/${workspaceSlug?.toString()}/${projectId}`, ) { pathname: `/${workspaceSlug?.toString()}/${projectId}`, query: { ...params } });
|
// router.push( encodeURI(`/${workspaceSlug?.toString()}/${projectId}`, ) { pathname: `/${workspaceSlug?.toString()}/${projectId}`, query: { ...params } });
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { FC, useEffect } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
// components
|
// components
|
||||||
import { IssueCalendarView } from "@/components/issues/board-views/calendar";
|
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 { IssueAppliedFilters } from "@/components/issues/filters/applied-filters/root";
|
||||||
import { IssuePeekOverview } from "@/components/issues/peek-overview";
|
import { IssuePeekOverview } from "@/components/issues/peek-overview";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useIssue, useUser, useProject, useIssueDetails } from "@/hooks/store";
|
import { useIssue, useUser, useIssueDetails, useIssueFilter, useProject } from "@/hooks/store";
|
||||||
// assets
|
// assets
|
||||||
import SomethingWentWrongImage from "public/something-went-wrong.svg";
|
import SomethingWentWrongImage from "public/something-went-wrong.svg";
|
||||||
|
|
||||||
type ProjectDetailsViewProps = {
|
type ProjectDetailsViewProps = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
peekId: string;
|
peekId: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props) => {
|
export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, peekId } = props;
|
|
||||||
// router
|
// router
|
||||||
const params = useParams();
|
const searchParams = useSearchParams();
|
||||||
// store hooks
|
// 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 { 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 issueDetailStore = useIssueDetails();
|
||||||
const { data: currentUser, fetchCurrentUser } = useUser();
|
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(() => {
|
useEffect(() => {
|
||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
fetchCurrentUser();
|
fetchCurrentUser();
|
||||||
@ -53,15 +64,18 @@ export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props)
|
|||||||
}
|
}
|
||||||
}, [peekId, issueDetailStore, projectId, workspaceSlug]);
|
}, [peekId, issueDetailStore, projectId, workspaceSlug]);
|
||||||
|
|
||||||
|
// derived values
|
||||||
|
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full w-full overflow-hidden">
|
<div className="relative h-full w-full overflow-hidden">
|
||||||
{workspaceSlug && <IssuePeekOverview />}
|
{workspaceSlug && <IssuePeekOverview />}
|
||||||
|
|
||||||
{issueStore?.loader && !issueStore.issues ? (
|
{loader && !issues ? (
|
||||||
<div className="py-10 text-center text-sm text-custom-text-100">Loading...</div>
|
<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="grid h-full w-full place-items-center p-6">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="mx-auto grid h-52 w-52 place-items-center rounded-full bg-custom-background-80">
|
<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 && (
|
activeLayout && (
|
||||||
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
||||||
{/* applied filters */}
|
{/* applied filters */}
|
||||||
<IssueAppliedFilters />
|
<IssueAppliedFilters workspaceSlug={workspaceSlug} projectId={projectId} />
|
||||||
|
|
||||||
{activeLayout === "list" && (
|
{activeLayout === "list" && (
|
||||||
<div className="relative h-full w-full overflow-y-auto">
|
<div className="relative h-full w-full overflow-y-auto">
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
// interfaces
|
|
||||||
import {
|
|
||||||
// priority
|
|
||||||
TIssuePriorityKey,
|
|
||||||
// state groups
|
|
||||||
TIssueGroupKey,
|
|
||||||
IIssuePriorityFilters,
|
|
||||||
IIssueGroup,
|
|
||||||
} from "types/issue";
|
|
||||||
|
|
||||||
// all issue views
|
|
||||||
export const issueViews: any = {
|
|
||||||
list: {
|
|
||||||
title: "List View",
|
|
||||||
icon: "format_list_bulleted",
|
|
||||||
className: "",
|
|
||||||
},
|
|
||||||
kanban: {
|
|
||||||
title: "Board View",
|
|
||||||
icon: "grid_view",
|
|
||||||
className: "",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// issue priority filters
|
|
||||||
export const issuePriorityFilters: IIssuePriorityFilters[] = [
|
|
||||||
{
|
|
||||||
key: "urgent",
|
|
||||||
title: "Urgent",
|
|
||||||
className: "bg-red-500 border-red-500 text-white",
|
|
||||||
icon: "error",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "high",
|
|
||||||
title: "High",
|
|
||||||
className: "text-orange-500 border-custom-border-300",
|
|
||||||
icon: "signal_cellular_alt",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "medium",
|
|
||||||
title: "Medium",
|
|
||||||
className: "text-yellow-500 border-custom-border-300",
|
|
||||||
icon: "signal_cellular_alt_2_bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "low",
|
|
||||||
title: "Low",
|
|
||||||
className: "text-green-500 border-custom-border-300",
|
|
||||||
icon: "signal_cellular_alt_1_bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "none",
|
|
||||||
title: "None",
|
|
||||||
className: "text-gray-500 border-custom-border-300",
|
|
||||||
icon: "block",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const issuePriorityFilter = (priorityKey: TIssuePriorityKey): IIssuePriorityFilters | null => {
|
|
||||||
const currentIssuePriority: IIssuePriorityFilters | undefined | null =
|
|
||||||
issuePriorityFilters && issuePriorityFilters.length > 0
|
|
||||||
? issuePriorityFilters.find((_priority) => _priority.key === priorityKey)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (currentIssuePriority === undefined || currentIssuePriority === null) return null;
|
|
||||||
return { ...currentIssuePriority };
|
|
||||||
};
|
|
||||||
|
|
||||||
// issue group filters
|
|
||||||
export const issueGroupColors: {
|
|
||||||
[key: string]: string;
|
|
||||||
} = {
|
|
||||||
backlog: "#d9d9d9",
|
|
||||||
unstarted: "#3f76ff",
|
|
||||||
started: "#f59e0b",
|
|
||||||
completed: "#16a34a",
|
|
||||||
cancelled: "#dc2626",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const issueGroups: IIssueGroup[] = [
|
|
||||||
{
|
|
||||||
key: "backlog",
|
|
||||||
title: "Backlog",
|
|
||||||
color: "#d9d9d9",
|
|
||||||
className: `text-[#d9d9d9] bg-[#d9d9d9]/10`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "unstarted",
|
|
||||||
title: "Unstarted",
|
|
||||||
color: "#3f76ff",
|
|
||||||
className: `text-[#3f76ff] bg-[#3f76ff]/10`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "started",
|
|
||||||
title: "Started",
|
|
||||||
color: "#f59e0b",
|
|
||||||
className: `text-[#f59e0b] bg-[#f59e0b]/10`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "completed",
|
|
||||||
title: "Completed",
|
|
||||||
color: "#16a34a",
|
|
||||||
className: `text-[#16a34a] bg-[#16a34a]/10`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "cancelled",
|
|
||||||
title: "Cancelled",
|
|
||||||
color: "#dc2626",
|
|
||||||
className: `text-[#dc2626] bg-[#dc2626]/10`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const issueGroupFilter = (issueKey: TIssueGroupKey): IIssueGroup | null => {
|
|
||||||
const currentIssueStateGroup: IIssueGroup | undefined | null =
|
|
||||||
issueGroups && issueGroups.length > 0 ? issueGroups.find((group) => group.key === issueKey) : null;
|
|
||||||
|
|
||||||
if (currentIssueStateGroup === undefined || currentIssueStateGroup === null) return null;
|
|
||||||
return { ...currentIssueStateGroup };
|
|
||||||
};
|
|
@ -1,20 +1,138 @@
|
|||||||
import { ILayoutDisplayFiltersOptions } from "@/types/issue-filters";
|
// interfaces
|
||||||
|
import {
|
||||||
|
TIssueLayout,
|
||||||
|
TIssueLayoutViews,
|
||||||
|
TIssueFilterKeys,
|
||||||
|
TIssueFilterPriority,
|
||||||
|
TIssueFilterPriorityObject,
|
||||||
|
TIssueFilterState,
|
||||||
|
TIssueFilterStateObject,
|
||||||
|
} from "types/issue";
|
||||||
|
|
||||||
export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
// issue filters
|
||||||
[pageType: string]: { [layoutType: string]: ILayoutDisplayFiltersOptions };
|
export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { [key in TIssueLayout]: Record<"filters", TIssueFilterKeys[]> } = {
|
||||||
} = {
|
list: {
|
||||||
issues: {
|
filters: ["priority", "state", "labels"],
|
||||||
list: {
|
},
|
||||||
filters: ["priority", "state", "labels"],
|
kanban: {
|
||||||
display_properties: null,
|
filters: ["priority", "state", "labels"],
|
||||||
display_filters: null,
|
},
|
||||||
extra_options: null,
|
calendar: {
|
||||||
},
|
filters: ["priority", "state", "labels"],
|
||||||
kanban: {
|
},
|
||||||
filters: ["priority", "state", "labels"],
|
spreadsheet: {
|
||||||
display_properties: null,
|
filters: ["priority", "state", "labels"],
|
||||||
display_filters: null,
|
},
|
||||||
extra_options: null,
|
gantt: {
|
||||||
},
|
filters: ["priority", "state", "labels"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const issueLayoutViews: Partial<TIssueLayoutViews> = {
|
||||||
|
list: {
|
||||||
|
title: "List View",
|
||||||
|
icon: "format_list_bulleted",
|
||||||
|
className: "",
|
||||||
|
},
|
||||||
|
kanban: {
|
||||||
|
title: "Board View",
|
||||||
|
icon: "grid_view",
|
||||||
|
className: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// issue priority filters
|
||||||
|
export const issuePriorityFilters: TIssueFilterPriorityObject[] = [
|
||||||
|
{
|
||||||
|
key: "urgent",
|
||||||
|
title: "Urgent",
|
||||||
|
className: "bg-red-500 border-red-500 text-white",
|
||||||
|
icon: "error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "high",
|
||||||
|
title: "High",
|
||||||
|
className: "text-orange-500 border-custom-border-300",
|
||||||
|
icon: "signal_cellular_alt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "medium",
|
||||||
|
title: "Medium",
|
||||||
|
className: "text-yellow-500 border-custom-border-300",
|
||||||
|
icon: "signal_cellular_alt_2_bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "low",
|
||||||
|
title: "Low",
|
||||||
|
className: "text-green-500 border-custom-border-300",
|
||||||
|
icon: "signal_cellular_alt_1_bar",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "none",
|
||||||
|
title: "None",
|
||||||
|
className: "text-gray-500 border-custom-border-300",
|
||||||
|
icon: "block",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const issuePriorityFilter = (priorityKey: TIssueFilterPriority): TIssueFilterPriorityObject | undefined => {
|
||||||
|
const currentIssuePriority: TIssueFilterPriorityObject | undefined =
|
||||||
|
issuePriorityFilters && issuePriorityFilters.length > 0
|
||||||
|
? issuePriorityFilters.find((_priority) => _priority.key === priorityKey)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (currentIssuePriority) return currentIssuePriority;
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
// issue group filters
|
||||||
|
export const issueGroupColors: {
|
||||||
|
[key in TIssueFilterState]: string;
|
||||||
|
} = {
|
||||||
|
backlog: "#d9d9d9",
|
||||||
|
unstarted: "#3f76ff",
|
||||||
|
started: "#f59e0b",
|
||||||
|
completed: "#16a34a",
|
||||||
|
cancelled: "#dc2626",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const issueGroups: TIssueFilterStateObject[] = [
|
||||||
|
{
|
||||||
|
key: "backlog",
|
||||||
|
title: "Backlog",
|
||||||
|
color: "#d9d9d9",
|
||||||
|
className: `text-[#d9d9d9] bg-[#d9d9d9]/10`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "unstarted",
|
||||||
|
title: "Unstarted",
|
||||||
|
color: "#3f76ff",
|
||||||
|
className: `text-[#3f76ff] bg-[#3f76ff]/10`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "started",
|
||||||
|
title: "Started",
|
||||||
|
color: "#f59e0b",
|
||||||
|
className: `text-[#f59e0b] bg-[#f59e0b]/10`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "completed",
|
||||||
|
title: "Completed",
|
||||||
|
color: "#16a34a",
|
||||||
|
className: `text-[#16a34a] bg-[#16a34a]/10`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "cancelled",
|
||||||
|
title: "Cancelled",
|
||||||
|
color: "#dc2626",
|
||||||
|
className: `text-[#dc2626] bg-[#dc2626]/10`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const issueGroupFilter = (issueKey: TIssueFilterState): TIssueFilterStateObject | undefined => {
|
||||||
|
const currentIssueStateGroup: TIssueFilterStateObject | undefined =
|
||||||
|
issueGroups && issueGroups.length > 0 ? issueGroups.find((group) => group.key === issueKey) : undefined;
|
||||||
|
|
||||||
|
if (currentIssueStateGroup) return currentIssueStateGroup;
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
@ -20,8 +20,6 @@ export const AuthWrapper: FC<TAuthWrapper> = observer((props) => {
|
|||||||
const { isLoading, data: currentUser, fetchCurrentUser } = useUser();
|
const { isLoading, data: currentUser, fetchCurrentUser } = useUser();
|
||||||
const { data: currentUserProfile } = useUserProfile();
|
const { data: currentUserProfile } = useUserProfile();
|
||||||
|
|
||||||
console;
|
|
||||||
|
|
||||||
const { isLoading: isSWRLoading } = useSWR("INSTANCE_INFORMATION", () => fetchCurrentUser(), {
|
const { isLoading: isSWRLoading } = useSWR("INSTANCE_INFORMATION", () => fetchCurrentUser(), {
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
});
|
});
|
||||||
|
@ -17,12 +17,12 @@
|
|||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@headlessui/react": "^1.7.13",
|
"@headlessui/react": "^1.7.13",
|
||||||
"@mui/material": "^5.14.1",
|
"@mui/material": "^5.14.1",
|
||||||
|
"@plane/constants": "*",
|
||||||
"@plane/document-editor": "*",
|
"@plane/document-editor": "*",
|
||||||
"@plane/lite-text-editor": "*",
|
"@plane/lite-text-editor": "*",
|
||||||
"@plane/rich-text-editor": "*",
|
"@plane/rich-text-editor": "*",
|
||||||
"@plane/types": "*",
|
"@plane/types": "*",
|
||||||
"@plane/ui": "*",
|
"@plane/ui": "*",
|
||||||
"@plane/constants": "*",
|
|
||||||
"@sentry/nextjs": "^7.108.0",
|
"@sentry/nextjs": "^7.108.0",
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.3.4",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
@ -34,6 +34,7 @@
|
|||||||
"lucide-react": "^0.378.0",
|
"lucide-react": "^0.378.0",
|
||||||
"mobx": "^6.10.0",
|
"mobx": "^6.10.0",
|
||||||
"mobx-react-lite": "^4.0.3",
|
"mobx-react-lite": "^4.0.3",
|
||||||
|
"mobx-utils": "^6.0.8",
|
||||||
"next": "^14.2.3",
|
"next": "^14.2.3",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
|
@ -29,7 +29,7 @@ export abstract class APIService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(url: string, params = {}) {
|
get(url: string, params = {}) {
|
||||||
return this.axiosInstance.get(url, { params });
|
return this.axiosInstance.get(url, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
post(url: string, data: any, config = {}) {
|
post(url: string, data: any, config = {}) {
|
||||||
|
@ -1,131 +1,162 @@
|
|||||||
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
|
import isEqual from "lodash/isEqual";
|
||||||
|
import set from "lodash/set";
|
||||||
import { action, makeObservable, observable, runInAction, computed } from "mobx";
|
import { action, makeObservable, observable, runInAction, computed } from "mobx";
|
||||||
|
import { computedFn } from "mobx-utils";
|
||||||
// constants
|
// constants
|
||||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
|
||||||
// store
|
// store
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
// types
|
// types
|
||||||
import { TIssueBoardKeys, IIssueFilterOptions, TIssueParams } from "@/types/issue";
|
import {
|
||||||
|
TIssueLayoutOptions,
|
||||||
interface IFiltersOptions {
|
TIssueFilters,
|
||||||
filters: IIssueFilterOptions;
|
TIssueQueryFilters,
|
||||||
}
|
TIssueQueryFiltersParams,
|
||||||
|
TIssueFilterKeys,
|
||||||
|
} from "@/types/issue";
|
||||||
|
|
||||||
export interface IIssueFilterStore {
|
export interface IIssueFilterStore {
|
||||||
// observables
|
// observables
|
||||||
projectIssueFilters: { [projectId: string]: IFiltersOptions } | undefined;
|
layoutOptions: TIssueLayoutOptions;
|
||||||
|
filters: { [projectId: string]: TIssueFilters } | undefined;
|
||||||
// computed
|
// computed
|
||||||
issueFilters: IFiltersOptions | undefined;
|
issueFilters: TIssueFilters | undefined;
|
||||||
appliedFilters: TIssueParams[] | undefined;
|
appliedFilters: TIssueQueryFiltersParams | undefined;
|
||||||
// helpers
|
isIssueFiltersUpdated: (filters: TIssueFilters) => boolean;
|
||||||
issueDisplayFilters: (projectId: string) => IFiltersOptions | undefined;
|
|
||||||
// actions
|
// actions
|
||||||
updateFilters: (projectId: string, filters: IIssueFilterOptions) => Promise<IFiltersOptions>;
|
updateLayoutOptions: (layout: TIssueLayoutOptions) => void;
|
||||||
|
initIssueFilters: (projectId: string, filters: TIssueFilters) => void;
|
||||||
|
updateIssueFilters: <K extends keyof TIssueFilters>(
|
||||||
|
projectId: string,
|
||||||
|
filterKind: K,
|
||||||
|
filterKey: keyof TIssueFilters[K],
|
||||||
|
filters: TIssueFilters[K][typeof filterKey]
|
||||||
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IssueFilterStore implements IIssueFilterStore {
|
export class IssueFilterStore implements IIssueFilterStore {
|
||||||
// observables
|
// observables
|
||||||
projectIssueFilters: { [projectId: string]: IFiltersOptions } | undefined = undefined;
|
layoutOptions: TIssueLayoutOptions = {
|
||||||
// root store
|
list: true,
|
||||||
rootStore;
|
kanban: false,
|
||||||
|
calendar: false,
|
||||||
|
gantt: false,
|
||||||
|
spreadsheet: false,
|
||||||
|
};
|
||||||
|
filters: { [projectId: string]: TIssueFilters } | undefined = undefined;
|
||||||
|
|
||||||
constructor(_rootStore: RootStore) {
|
constructor(private store: RootStore) {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// observables
|
// observables
|
||||||
projectIssueFilters: observable.ref,
|
layoutOptions: observable,
|
||||||
|
filters: observable,
|
||||||
// computed
|
// computed
|
||||||
issueFilters: computed,
|
issueFilters: computed,
|
||||||
appliedFilters: computed,
|
appliedFilters: computed,
|
||||||
// actions
|
// actions
|
||||||
updateFilters: action,
|
updateLayoutOptions: action,
|
||||||
|
initIssueFilters: action,
|
||||||
|
updateIssueFilters: action,
|
||||||
});
|
});
|
||||||
// root store
|
|
||||||
this.rootStore = _rootStore;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper methods
|
// helper methods
|
||||||
computedFilter = (filters: any, filteredParams: any) => {
|
computedFilter = (filters: TIssueQueryFilters, filteredParams: TIssueFilterKeys[]) => {
|
||||||
const computedFilters: any = {};
|
const computedFilters: TIssueQueryFiltersParams = {};
|
||||||
|
|
||||||
Object.keys(filters).map((key) => {
|
Object.keys(filters).map((key) => {
|
||||||
if (filters[key] != undefined && filteredParams.includes(key))
|
const currentFilterKey = key as TIssueFilterKeys;
|
||||||
computedFilters[key] =
|
|
||||||
typeof filters[key] === "string" || typeof filters[key] === "boolean" ? filters[key] : filters[key].join(",");
|
if (filters[currentFilterKey] != undefined && filteredParams.includes(currentFilterKey)) {
|
||||||
|
if (Array.isArray(filters[currentFilterKey]))
|
||||||
|
computedFilters[currentFilterKey] = filters[currentFilterKey]?.join(",");
|
||||||
|
else if (filters[currentFilterKey] && typeof filters[currentFilterKey] === "string")
|
||||||
|
computedFilters[currentFilterKey] = filters[currentFilterKey]?.toString();
|
||||||
|
else if (typeof filters[currentFilterKey] === "boolean")
|
||||||
|
computedFilters[currentFilterKey] = filters[currentFilterKey]?.toString();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return computedFilters;
|
return computedFilters;
|
||||||
};
|
};
|
||||||
|
|
||||||
// helpers
|
// computed
|
||||||
issueDisplayFilters = (projectId: string) => {
|
get issueFilters() {
|
||||||
|
const projectId = this.store.project.project?.id;
|
||||||
if (!projectId) return undefined;
|
if (!projectId) return undefined;
|
||||||
return this.projectIssueFilters?.[projectId] || undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
handleIssueQueryParamsByLayout = (layout: TIssueBoardKeys | undefined, viewType: "issues"): TIssueParams[] | null => {
|
const currentFilters = this.filters?.[projectId];
|
||||||
const queryParams: TIssueParams[] = [];
|
if (!currentFilters) return undefined;
|
||||||
|
|
||||||
if (!layout) return null;
|
return currentFilters;
|
||||||
|
}
|
||||||
|
|
||||||
const layoutOptions = ISSUE_DISPLAY_FILTERS_BY_LAYOUT[viewType][layout];
|
get appliedFilters() {
|
||||||
|
const currentIssueFilters = this.issueFilters;
|
||||||
|
if (!currentIssueFilters) return undefined;
|
||||||
|
|
||||||
// add filters query params
|
const currentLayout = currentIssueFilters?.display_filters?.layout;
|
||||||
layoutOptions.filters.forEach((option: any) => {
|
if (!currentLayout) return undefined;
|
||||||
queryParams.push(option);
|
|
||||||
});
|
|
||||||
|
|
||||||
return queryParams;
|
const currentFilters: TIssueQueryFilters = {
|
||||||
};
|
priority: currentIssueFilters?.filters?.priority || undefined,
|
||||||
|
state: currentIssueFilters?.filters?.state || undefined,
|
||||||
|
labels: currentIssueFilters?.filters?.labels || undefined,
|
||||||
|
};
|
||||||
|
const filteredParams = ISSUE_DISPLAY_FILTERS_BY_LAYOUT?.[currentLayout]?.filters || [];
|
||||||
|
const currentFilterQueryParams: TIssueQueryFiltersParams = this.computedFilter(currentFilters, filteredParams);
|
||||||
|
|
||||||
|
return currentFilterQueryParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
isIssueFiltersUpdated = computedFn((userFilters: TIssueFilters) => {
|
||||||
|
if (!this.issueFilters) return false;
|
||||||
|
const currentUserFilters = cloneDeep(userFilters?.filters || {});
|
||||||
|
const currentIssueFilters = cloneDeep(this.issueFilters?.filters || {});
|
||||||
|
return isEqual(currentUserFilters, currentIssueFilters);
|
||||||
|
});
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
updateFilters = async (projectId: string, filters: IIssueFilterOptions) => {
|
updateLayoutOptions = (options: TIssueLayoutOptions) => set(this, ["layoutOptions"], options);
|
||||||
|
|
||||||
|
initIssueFilters = async (projectId: string, initFilters: TIssueFilters) => {
|
||||||
try {
|
try {
|
||||||
let issueFilters = { ...this.projectIssueFilters };
|
if (!projectId) return;
|
||||||
if (!issueFilters) issueFilters = {};
|
if (this.filters === undefined) runInAction(() => (this.filters = {}));
|
||||||
if (!issueFilters[projectId]) issueFilters[projectId] = { filters: {} };
|
if (this.filters && initFilters) set(this.filters, [projectId], initFilters);
|
||||||
|
|
||||||
const newFilters = {
|
const workspaceSlug = this.store.project.workspace?.slug;
|
||||||
filters: { ...issueFilters[projectId].filters },
|
const currentAppliedFilters = this.appliedFilters;
|
||||||
};
|
|
||||||
|
|
||||||
newFilters.filters = { ...newFilters.filters, ...filters };
|
if (!workspaceSlug) return;
|
||||||
|
await this.store.issue.fetchPublicIssues(workspaceSlug, projectId, currentAppliedFilters);
|
||||||
issueFilters[projectId] = {
|
|
||||||
filters: newFilters.filters,
|
|
||||||
};
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
this.projectIssueFilters = issueFilters;
|
|
||||||
});
|
|
||||||
|
|
||||||
return newFilters;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
get issueFilters() {
|
updateIssueFilters = async <K extends keyof TIssueFilters>(
|
||||||
const projectId = this.rootStore.project.project?.id;
|
projectId: string,
|
||||||
if (!projectId) return undefined;
|
filterKind: K,
|
||||||
|
filterKey: keyof TIssueFilters[K],
|
||||||
|
filterValue: TIssueFilters[K][typeof filterKey]
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
if (!projectId || !filterKind || !filterKey || !filterValue) return;
|
||||||
|
if (this.filters === undefined) runInAction(() => (this.filters = {}));
|
||||||
|
|
||||||
const issueFilters = this.issueDisplayFilters(projectId);
|
runInAction(() => {
|
||||||
if (!issueFilters) return undefined;
|
if (this.filters) set(this.filters, [projectId, filterKind, filterKey], filterValue);
|
||||||
|
});
|
||||||
|
|
||||||
return issueFilters;
|
const workspaceSlug = this.store.project.workspace?.slug;
|
||||||
}
|
const currentAppliedFilters = this.appliedFilters;
|
||||||
|
|
||||||
get appliedFilters() {
|
if (!workspaceSlug) return;
|
||||||
const userFilters = this.issueFilters;
|
await this.store.issue.fetchPublicIssues(workspaceSlug, projectId, currentAppliedFilters);
|
||||||
const layout = this.rootStore.project?.activeLayout;
|
} catch (error) {
|
||||||
if (!userFilters || !layout) return undefined;
|
throw error;
|
||||||
|
}
|
||||||
let filteredRouteParams: any = {
|
};
|
||||||
priority: userFilters?.filters?.priority || undefined,
|
|
||||||
state: userFilters?.filters?.state || undefined,
|
|
||||||
labels: userFilters?.filters?.labels || undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredParams = this.handleIssueQueryParamsByLayout(layout, "issues");
|
|
||||||
if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams);
|
|
||||||
|
|
||||||
return filteredRouteParams;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ export interface IIssueStore {
|
|||||||
// service
|
// service
|
||||||
issueService: any;
|
issueService: any;
|
||||||
// actions
|
// actions
|
||||||
fetchPublicIssues: (workspace_slug: string, project_slug: string, params: any) => void;
|
fetchPublicIssues: (workspace_slug: string, project_slug: string, params: any) => Promise<void>;
|
||||||
getCountOfIssuesByState: (state: string) => number;
|
getCountOfIssuesByState: (state: string) => number;
|
||||||
getFilteredIssuesByState: (state: string) => IIssue[];
|
getFilteredIssuesByState: (state: string) => IIssue[];
|
||||||
}
|
}
|
||||||
|
@ -2,50 +2,40 @@
|
|||||||
import { observable, action, makeObservable, runInAction } from "mobx";
|
import { observable, action, makeObservable, runInAction } from "mobx";
|
||||||
// service
|
// service
|
||||||
import ProjectService from "@/services/project.service";
|
import ProjectService from "@/services/project.service";
|
||||||
|
// store types
|
||||||
|
import { RootStore } from "@/store/root.store";
|
||||||
// types
|
// types
|
||||||
import { TIssueBoardKeys } from "@/types/issue";
|
|
||||||
import { IWorkspace, IProject, IProjectSettings } from "@/types/project";
|
import { IWorkspace, IProject, IProjectSettings } from "@/types/project";
|
||||||
|
|
||||||
export interface IProjectStore {
|
export interface IProjectStore {
|
||||||
|
// observables
|
||||||
loader: boolean;
|
loader: boolean;
|
||||||
error: any | null;
|
error: any | null;
|
||||||
workspace: IWorkspace | null;
|
workspace: IWorkspace | null;
|
||||||
project: IProject | null;
|
project: IProject | null;
|
||||||
settings: IProjectSettings | null;
|
settings: IProjectSettings | null;
|
||||||
activeLayout: TIssueBoardKeys;
|
|
||||||
layoutOptions: Record<TIssueBoardKeys, boolean>;
|
|
||||||
canReact: boolean;
|
canReact: boolean;
|
||||||
canComment: boolean;
|
canComment: boolean;
|
||||||
canVote: boolean;
|
canVote: boolean;
|
||||||
|
// actions
|
||||||
fetchProjectSettings: (workspace_slug: string, project_slug: string) => Promise<void>;
|
fetchProjectSettings: (workspace_slug: string, project_slug: string) => Promise<void>;
|
||||||
setActiveLayout: (value: TIssueBoardKeys) => void;
|
|
||||||
hydrate: (projectSettings: any) => void;
|
hydrate: (projectSettings: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProjectStore implements IProjectStore {
|
export class ProjectStore implements IProjectStore {
|
||||||
|
// observables
|
||||||
loader: boolean = false;
|
loader: boolean = false;
|
||||||
error: any | null = null;
|
error: any | null = null;
|
||||||
// data
|
|
||||||
workspace: IWorkspace | null = null;
|
workspace: IWorkspace | null = null;
|
||||||
project: IProject | null = null;
|
project: IProject | null = null;
|
||||||
settings: IProjectSettings | null = null;
|
settings: IProjectSettings | null = null;
|
||||||
activeLayout: TIssueBoardKeys = "list";
|
|
||||||
layoutOptions: Record<TIssueBoardKeys, boolean> = {
|
|
||||||
list: true,
|
|
||||||
kanban: true,
|
|
||||||
calendar: false,
|
|
||||||
gantt: false,
|
|
||||||
spreadsheet: false,
|
|
||||||
};
|
|
||||||
canReact: boolean = false;
|
canReact: boolean = false;
|
||||||
canComment: boolean = false;
|
canComment: boolean = false;
|
||||||
canVote: boolean = false;
|
canVote: boolean = false;
|
||||||
// root store
|
|
||||||
rootStore;
|
|
||||||
// service
|
// service
|
||||||
projectService;
|
projectService;
|
||||||
|
|
||||||
constructor(_rootStore: any | null = null) {
|
constructor(private store: RootStore) {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// loaders and error observables
|
// loaders and error observables
|
||||||
loader: observable,
|
loader: observable,
|
||||||
@ -54,36 +44,29 @@ export class ProjectStore implements IProjectStore {
|
|||||||
workspace: observable,
|
workspace: observable,
|
||||||
project: observable,
|
project: observable,
|
||||||
settings: observable,
|
settings: observable,
|
||||||
layoutOptions: observable,
|
|
||||||
activeLayout: observable.ref,
|
|
||||||
canReact: observable.ref,
|
canReact: observable.ref,
|
||||||
canComment: observable.ref,
|
canComment: observable.ref,
|
||||||
canVote: observable.ref,
|
canVote: observable.ref,
|
||||||
// actions
|
// actions
|
||||||
fetchProjectSettings: action,
|
fetchProjectSettings: action,
|
||||||
setActiveLayout: action,
|
|
||||||
hydrate: action,
|
hydrate: action,
|
||||||
// computed
|
// computed
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rootStore = _rootStore;
|
// services
|
||||||
this.projectService = new ProjectService();
|
this.projectService = new ProjectService();
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrate = (projectSettings: any) => {
|
hydrate = (projectSettings: any) => {
|
||||||
const { workspace_detail, project_details, views, votes, comments, reactions } = projectSettings;
|
const { workspace_detail, project_details, votes, comments, reactions } = projectSettings;
|
||||||
this.workspace = workspace_detail;
|
this.workspace = workspace_detail;
|
||||||
this.project = project_details;
|
this.project = project_details;
|
||||||
this.layoutOptions = views;
|
|
||||||
this.canComment = comments;
|
this.canComment = comments;
|
||||||
this.canVote = votes;
|
this.canVote = votes;
|
||||||
this.canReact = reactions;
|
this.canReact = reactions;
|
||||||
};
|
};
|
||||||
|
|
||||||
setActiveLayout = (boardValue: TIssueBoardKeys) => {
|
|
||||||
this.activeLayout = boardValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchProjectSettings = async (workspace_slug: string, project_slug: string) => {
|
fetchProjectSettings = async (workspace_slug: string, project_slug: string) => {
|
||||||
try {
|
try {
|
||||||
this.loader = true;
|
this.loader = true;
|
||||||
@ -94,12 +77,11 @@ export class ProjectStore implements IProjectStore {
|
|||||||
if (response) {
|
if (response) {
|
||||||
const currentProject: IProject = { ...response?.project_details };
|
const currentProject: IProject = { ...response?.project_details };
|
||||||
const currentWorkspace: IWorkspace = { ...response?.workspace_detail };
|
const currentWorkspace: IWorkspace = { ...response?.workspace_detail };
|
||||||
const currentViewOptions = { ...response?.views };
|
|
||||||
const currentDeploySettings = { ...response };
|
const currentDeploySettings = { ...response };
|
||||||
|
this.store.issueFilter.updateLayoutOptions(response?.views);
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.project = currentProject;
|
this.project = currentProject;
|
||||||
this.workspace = currentWorkspace;
|
this.workspace = currentWorkspace;
|
||||||
this.layoutOptions = currentViewOptions;
|
|
||||||
this.settings = currentDeploySettings;
|
this.settings = currentDeploySettings;
|
||||||
this.loader = false;
|
this.loader = false;
|
||||||
});
|
});
|
||||||
|
6
space/types/issue-filters.d.ts
vendored
6
space/types/issue-filters.d.ts
vendored
@ -1,6 +0,0 @@
|
|||||||
export interface ILayoutDisplayFiltersOptions {
|
|
||||||
filters: (keyof IIssueFilterOptions)[];
|
|
||||||
display_properties: boolean | null;
|
|
||||||
display_filters: null;
|
|
||||||
extra_options: null;
|
|
||||||
}
|
|
57
space/types/issue.d.ts
vendored
57
space/types/issue.d.ts
vendored
@ -1,30 +1,47 @@
|
|||||||
export type TIssueBoardKeys = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt";
|
export type TIssueLayout = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt";
|
||||||
|
export type TIssueLayoutOptions = {
|
||||||
|
[key in TIssueLayout]: boolean;
|
||||||
|
};
|
||||||
|
export type TIssueLayoutViews = {
|
||||||
|
[key in TIssueLayout]: { title: string; icon: string; className: string };
|
||||||
|
};
|
||||||
|
|
||||||
export interface IIssueBoardViews {
|
export type TIssueFilterPriority = "urgent" | "high" | "medium" | "low" | "none";
|
||||||
key: TIssueBoardKeys;
|
export type TIssueFilterPriorityObject = {
|
||||||
|
key: TIssueFilterPriority;
|
||||||
title: string;
|
title: string;
|
||||||
icon: string;
|
|
||||||
className: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TIssuePriorityKey = "urgent" | "high" | "medium" | "low" | "none";
|
|
||||||
export type TIssuePriorityTitle = "Urgent" | "High" | "Medium" | "Low" | "None";
|
|
||||||
export interface IIssuePriorityFilters {
|
|
||||||
key: TIssuePriorityKey;
|
|
||||||
title: TIssuePriorityTitle;
|
|
||||||
className: string;
|
className: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type TIssueGroupKey = "backlog" | "unstarted" | "started" | "completed" | "cancelled";
|
export type TIssueFilterState = "backlog" | "unstarted" | "started" | "completed" | "cancelled";
|
||||||
export type TIssueGroupTitle = "Backlog" | "Unstarted" | "Started" | "Completed" | "Cancelled";
|
export type TIssueFilterStateObject = {
|
||||||
|
key: TIssueFilterState;
|
||||||
export interface IIssueGroup {
|
title: string;
|
||||||
key: TIssueGroupKey;
|
|
||||||
title: TIssueGroupTitle;
|
|
||||||
color: string;
|
color: string;
|
||||||
className: string;
|
className: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export type TIssueFilterKeys = "priority" | "state" | "labels";
|
||||||
|
|
||||||
|
export type TDisplayFilters = {
|
||||||
|
layout: TIssueLayout;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TFilters = {
|
||||||
|
state: TIssueFilterState[];
|
||||||
|
priority: TIssueFilterPriority[];
|
||||||
|
labels: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueFilters = {
|
||||||
|
display_filters: TDisplayFilters;
|
||||||
|
filters: TFilters;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueQueryFilters = Partial<TFilters>;
|
||||||
|
|
||||||
|
export type TIssueQueryFiltersParams = Partial<Record<keyof TFilters, string>>;
|
||||||
|
|
||||||
export interface IIssue {
|
export interface IIssue {
|
||||||
id: string;
|
id: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user